コード例 #1
0
    def login(self, username, password):
        self.logout()

        s = Session()
        s.headers.update(HEADERS)

        if not password:
            raise APIError(_.LOGIN_ERROR)

        r = s.get(BASE_URL + 'superview/', timeout=20)
        soup = BeautifulSoup(r.text, 'html.parser')

        login_form = soup.find(id="membersignin")
        inputs = login_form.find_all('input')

        data = {}
        for elem in inputs:
            if elem.attrs.get('value'):
                data[elem.attrs['name']] = elem.attrs['value']

        data.update({
            'signinusername': username,
            'signinpassword': password,
        })

        r = s.post(BASE_URL + 'superview/',
                   data=data,
                   allow_redirects=False,
                   timeout=20)
        if r.status_code != 302:
            raise APIError(_.LOGIN_ERROR)

        if settings.getBool('save_password', False):
            userdata.set(PASSWORD_KEY, password)

        for cookie in r.cookies:
            if cookie.name.startswith('wordpress_logged_in'):
                userdata.set('_cookies', {cookie.name: cookie.value})
                break
コード例 #2
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS, base_url=API_URL)
        self._set_authentication()

    def _set_authentication(self):
        id_token = userdata.get('id_token')
        if not id_token:
            return

        self._session.headers.update({'Authorization': id_token})
        self.logged_in = True

    def navigation(self):
        return self._session.get(
            '/metadata/navigations/nav_v1').json()['navigations']

    def page(self, name):
        data = self._session.get('/metadata/pages/{}'.format(name)).json()
        return [x for x in data['panels'] if 'title' in x]

    def editorial(self, name):
        data = self._session.get(
            '/metadata/editorials/v2/{}/mobile'.format(name)).json()
        return data['assets']

    def asset(self, id):
        return self._session.get(
            '/metadata/assets/v2/{}/mobile'.format(id)).json()

    def login(self, username, password):
        self.logout()

        payload = {
            "username": username,
            "password": password,
            "rememberMe": "false"
        }

        r = self._session.post('/userauth/login', json=payload)

        if not r.ok:
            if r.status_code == 403:
                raise APIError(_.GEO_BLOCKED)
            else:
                raise APIError(
                    _(_.LOGIN_ERROR, msg=r.json()['error'].get('description')))

        data = r.json()
        userdata.set('user_id', data['userId'])
        self._parse_token(data['result'])

    def _parse_token(self, data):
        userdata.set('id_token', data['IdToken'])
        userdata.set('expires', int(time.time() + data['ExpiresIn'] - 15))

        if 'RefreshToken' in data:
            userdata.set('refresh_token', data['RefreshToken'])

        self._set_authentication()

    def _check_token(self):
        if userdata.get('expires') > time.time():
            return

        headers = {
            'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
            'X-Amz-User-Agent': 'aws-amplify/0.1.x js',
            'Content-Type': 'application/x-amz-json-1.1',
        }

        payload = {
            'AuthFlow': 'REFRESH_TOKEN_AUTH',
            'AuthParameters': {
                'DEVICE_KEY': None,
                'REFRESH_TOKEN': userdata.get('refresh_token'),
            },
            'ClientId': AWS_CLIENT_ID,
        }

        r = self._session.post(AWS_URL, json=payload, headers=headers)
        data = r.json()
        if 'message' in data:
            raise APIError(_(_.LOGIN_ERROR, msg=data['message']))

        self._parse_token(data['AuthenticationResult'])

    def play(self, asset, from_start=False):
        self._check_token()

        params = {
            'type': 'dash',
            'drm': 'widevine',
            'watchMode': 'startover' if from_start else 'live',
        }

        r = self._session.get(
            '/playback/generalPlayback/mobile/users/{user_id}/assets/{asset_id}'
            .format(user_id=userdata.get('user_id'), asset_id=asset),
            params=params)

        if not r.ok:
            if r.status_code == 403:
                raise APIError(_.GEO_BLOCKED)
            else:
                raise APIError(r.json()['error'].get('description'))

        try:
            data = r.json()
            stream = data['playback']['items']['item']
            if type(stream) is list:
                stream = stream[0]
        except:
            stream = None

        if not stream:
            raise APIError(_.NO_STREAM)

        return stream

    def logout(self):
        userdata.delete('user_id')
        userdata.delete('id_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        self.new_session()
コード例 #3
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(base_url=API_URL, headers=HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        access_token = userdata.get('access_token')
        if not access_token:
            return

        self._session.headers.update({'authorization': access_token})
        self.logged_in = True

    def _refresh_token(self, force=False):
        refresh_token = userdata.get('refresh_token')
        if not refresh_token or (not force
                                 and userdata.get('expires', 0) > time()):
            return

        params = {
            'key': GOOGLE_KEY,
        }

        payload = {
            'grantType': 'refresh_token',
            'refreshToken': refresh_token,
        }

        data = self._session.post(TOKEN_URL, params=params,
                                  json=payload).json()
        if 'error' in data:
            self.logout()
            raise APIError(data['error']['message'])

        userdata.set('access_token', data['access_token'])
        userdata.set('refresh_token', data['refresh_token'])
        userdata.set('expires', int(time()) + int(data['expires_in']) - 30)
        self._set_authentication()

    def channels(self):
        self._refresh_token()

        params = {
            'noGuideData': True,
        }

        return self._session.get('/programGuide', params=params).json()

    def play(self, id):
        self._refresh_token()

        params = {
            'channelId': id,
        }

        return self._session.get('/playbackAuthenticated',
                                 params=params).json()

    def epg(self, id=None):
        self._refresh_token()

        params = {}
        if id:
            params['channelId'] = id

        return self._session.get('/programGuide', params=params).json()

    def login(self, email, password, register=False):
        self.logout()

        params = {
            'key': GOOGLE_KEY,
        }

        payload = {
            'email': email,
            'password': password,
            'returnSecureToken': True,
        }

        data = self._session.post(REGISTER_URL if register else LOGIN_URL,
                                  params=params,
                                  json=payload).json()
        if 'error' in data:
            raise APIError(data['error']['message'])

        userdata.set('refresh_token', data['refreshToken'])
        self._refresh_token(force=True)

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        self.new_session()
コード例 #4
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS, base_url=API_URL)

        if userdata.get('singtel_tv_no'):
            self.logged_in = True

    def login(self, singtel_tv_no, identification_no):
        self.logout()

        device_id = hash_6(singtel_tv_no, length=16)

        payload = {
            'deviceType': APP_DEVICE,
            'deviceId': device_id,
            'identityType': APP_ID_TYPE,
            'identityId': identification_no,
            'iptvNo': singtel_tv_no,
            'appId': APP_ID,
            'appKey': APP_KEY,
            'mode': APP_MODE,
            'ver': APP_VER,
        }

        data = self._session.post('/HomeLoginService.aspx',
                                  data={
                                      'JSONtext': json.dumps(payload)
                                  }).json()['item'][0]
        if data.get('StatusCode'):
            raise APIError(_.LOGIN_ERROR)

        userdata.set('device_id', device_id)
        userdata.set('singtel_tv_no', singtel_tv_no)
        userdata.set('identification_no', identification_no)

        return data

    @mem_cache.cached(60 * 5)
    def channels(self):
        data = self._session.gz_json(DATA_URL)
        channels = [
            x for x in data['getAuthorizationResponse']['channelList']
            if x['isLive'].upper() == 'Y'
        ]

        user_data = self.login(userdata.get('singtel_tv_no'),
                               userdata.get('identification_no'))

        if user_data['OTTAccess'].upper() != 'Y':
            guest_ids = [
                int(x['ChannelID']) for x in user_data['guestPreviewChannels']
            ]
            channels = [x for x in channels if x['id'] in guest_ids]
        else:
            channels = [
                x for x in channels
                if x['shortName'] in user_data['SubscribedCallLetters']
            ]

        return channels

    def _stop_stream(self, channel_id, token):
        start = arrow.utcnow()
        end = start.shift(seconds=10)

        payload = {
            'deviceType': APP_DEVICE,
            'deviceId': userdata.get('device_id'),
            'identityType': APP_ID_TYPE,
            'identityId': userdata.get('identification_no'),
            'iptvNo': userdata.get('singtel_tv_no'),
            'channelID': channel_id,
            'startTime': start.format('YYYY-MM-DD HH:mm:ss'),
            'stopTime': end.format('YYYY-MM-DD HH:mm:ss'),
            'bitRates': '1',
            'token': token,
            'appId': APP_ID,
            'appKey': APP_KEY,
        }

        resp = self._session.post('/LogStopStream.aspx',
                                  data={'JSONtext': json.dumps(payload)})

        return resp.ok

    def play(self, channel_id, call_letter):
        payload = {
            'deviceType': APP_DEVICE,
            'deviceId': userdata.get('device_id'),
            'identityType': APP_ID_TYPE,
            'identityId': userdata.get('identification_no'),
            'iptvNo': userdata.get('singtel_tv_no'),
            'callLetter': call_letter,
            'channelID': channel_id,
            'appId': APP_ID,
            'appKey': APP_KEY,
            'mode': APP_MODE,
            'ver': APP_VER,
        }

        data = self._session.get('/WatchOTTStreaming.aspx',
                                 data={
                                     'JSONtext': json.dumps(payload)
                                 }).json()['item'][0]
        if data.get('StatusCode'):
            raise APIError(_(_.PLAYBACK_ERROR, error=data.get('StatusDesc')))

        self._stop_stream(channel_id, data['UserToken'])

        return data

    def logout(self):
        userdata.delete('singtel_tv_no')
        userdata.delete('identification_no')
        userdata.delete('device_id')
        mem_cache.empty()
        self.new_session()
コード例 #5
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('jwt_token')
        if not token:
            return

        self._session.headers.update({'Authorization': 'Bearer {}'.format(token)})
        self.logged_in = True

    def _device_info(self, username):
        return {
            'uuid': hashlib.sha1(username.encode('utf8')).hexdigest(),
            'platform': 'Android',
            'name': 'KODI',
            'os': 'KODI',
            'model': 'KODI',
            #'version': '27'
        }

    def _query_request(self, query, variables=None, **kwargs):
        data = {
            'query': ' '.join(query.split()), 
            'variables': variables or {},
        }

        return self._session.post(API_URL, json=data, **kwargs).json()

    def _set_token(self, jwt_token):
        userdata.set('jwt_token', jwt_token)
        data = jwt_data(jwt_token)
        userdata.set('expires', data['exp'] - 2592000)
        self._set_authentication()

    def login(self, username, password):
        self.logout()

        variables = {
            'input': {
                'deviceInfo': self._device_info(username),
            },
            'username': username,
            'password': password,
        }

        data = self._query_request(queries.LOGIN, variables)
        if data.get('errors'):
            raise APIError(data['errors'][0].get('message'))

        self._set_token(data['data']['login']['session']['token'])

    def content(self, screen_id):
        self._check_token()

        variables = {
            'screenId': screen_id,
        }

        return self._query_request(queries.CONTENT, variables)['data']['screen']

    def _check_token(self):
        if time.time() < userdata.get('expires', 0):
            return

        self.set_profile(userdata.get('profile_id'))

    def account(self):
        self._check_token()
        
        return self._query_request(queries.ACCOUNT)['data']['account']

    def set_profile(self, profile_id):
        variables = {
            'input': {
                'selectedProfile': profile_id,
            },
            'pin': None,
        }

        data = self._query_request(queries.UPDATE_ACCOUNT, variables)['data']['account']
        self._set_token(data['session']['token'])
        
        for row in data['profiles']:
            if row['id'] == data['selectedProfile']:
                userdata.set('profile_id', row['id'])
                userdata.set('profile_name', row['name'])
                userdata.set('profile_icon', row['avatar']['uri'])
                userdata.set('profile_kids', row['isKid'])
                return

        raise APIError(_.PROFILE_SET_ERROR)

    def search(self, query):
        self._check_token()

        variables = {
            'input': {
                'query': query,
            },
        }

        data = self._query_request(queries.SEARCH, variables)['data']['search']['components']

        results = []
        for row in data:
            results.extend(row['tiles'])

        return results

    def playback_auth(self, contentID):
        self._check_token()

        variables = {
            'contentItemId': contentID,
        }

        return self._query_request(queries.PLAYBACK_AUTH, variables)

    def get_brightcove_src(self, referenceID, jwt_token):
        brightcove_url = BRIGHTCOVE_URL.format(BRIGHTCOVE_ACCOUNT, referenceID, jwt_token)
        data = self._session.get(brightcove_url, headers={'BCOV-POLICY': BRIGHTCOVE_KEY}).json()
        return util.process_brightcove(data)

    def logout(self):
        userdata.delete('jwt_token')
        userdata.delete('expires')
        userdata.delete('profile_id')
        userdata.delete('profile_name')
        userdata.delete('profile_icon')
        userdata.delete('profile_kids')
        self.new_session()
コード例 #6
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(headers=HEADERS)
        self._set_authentication()

        self._default_params = DEFAULT_PARAMS
        self._default_params['signedUp'] = False  #self.logged_in

    def _set_authentication(self):
        access_token = userdata.get('access_token')
        if not access_token:
            self._session.headers.update(
                {'authorization': 'Bearer {}'.format(DEFAULT_TOKEN)})
            return

        self._session.headers.update({
            'authorization':
            'Bearer {}'.format(userdata.get('access_token'))
        })
        self.logged_in = True

    @mem_cache.cached(60 * 5)
    def _market_id(self):
        try:
            return self._session.get(MARKET_ID_URL, params={
                'apikey': 'web'
            }).json()['_id']
        except:
            log.debug('Failed to get market id')
            return '-1'

    def _oauth_token(self, payload):
        data = self._session.post('https://auth2.swm.digital/connect/token',
                                  data=payload,
                                  headers={
                                      'x-swm-apikey': SWM_API_KEY
                                  }).json()
        if 'Errors' in data:
            self.logout()
            raise APIError(data['Errors'][0]['Detail'])

        userdata.set('access_token', data['access_token'])
        userdata.set('expires', int(time() + data['expires_in'] - 30))

        if 'refresh_token' in data:
            userdata.set('refresh_token', data['refresh_token'])

        self._set_authentication()

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires',
                                      0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')

        payload = {
            'platformId': 'android',
            'regsource': '7plus',
            'refreshToken': userdata.get('refresh_token'),
        }

        self._oauth_token(payload)

    @mem_cache.cached(60 * 5)
    def search(self, query):
        params = {
            'searchTerm': query,
            'size': 30,
        }
        params.update(self._default_params)

        return self._session.get(
            'https://searchapi.swm.digital/3.0/api/Search',
            params=params).json()

    @mem_cache.cached(60 * 5)
    def content(self, slug):
        params = self._default_params
        params['market-id'] = self._market_id()
        return self._session.get(
            'https://component-cdn.swm.digital/content/{slug}'.format(
                slug=slug),
            params=params).json()

    @mem_cache.cached(60 * 5)
    def component(self, slug, component_id):
        self._refresh_token()

        params = {
            'component-id': component_id,
        }
        params.update(self._default_params)

        return self._session.get(
            'https://component.swm.digital/component/{slug}'.format(slug=slug),
            params=params).json()

    @mem_cache.cached(60 * 5)
    def video_player(self, slug):
        params = self._default_params
        params['market-id'] = self._market_id()
        return self._session.get(
            'https://component.swm.digital/player/live/{slug}'.format(
                slug=slug),
            params=params).json()['videoPlayer']

    def login(self, username, password):
        self.logout()

        payload = {
            'loginID': username,
            'password': password,
            'apiKey': GIGYA_API_KEY,
            'format': 'json',
        }

        data = self._session.post(
            'https://accounts.au1.gigya.com/accounts.login',
            data=payload).json()
        if 'errorMessage' in data:
            raise APIError(data['errorMessage'])

        cookie = {
            data['sessionInfo']['cookieName']:
            data['sessionInfo']['cookieValue']
        }

        payload = {
            'login_token': data['sessionInfo']['cookieValue'],
            'authMode': 'cookie',
            'apiKey': GIGYA_API_KEY,
            'format': 'json',
        }

        data = self._session.post(
            'https://accounts.au1.gigya.com/accounts.getJWT',
            params=payload,
            cookies=cookie).json()
        if 'errorMessage' in data:
            raise APIError(data['errorMessage'])

        payload = {
            'platformId': 'android',
            'regsource': '7plus',
            'IdToken': data['id_token'],
        }

        self._oauth_token(payload)

    def play(self, account, reference, live):
        self._refresh_token()

        params = {
            'appId': '7plus',
            'deviceType': 'android',
            'platformType': 'app',
            'accountId': account,
            'referenceId': reference,
            'deliveryId': 'csai',
            # 'ppId': '',
            # 'deviceId': '71aa8979-75b0-4393-ad93-239d368ed79d',
            # 'pc': 3350,
            # 'advertid': 'null',
            # 'videoType': 'live',
            # 'tvid': 'null',
            # 'ozid': 'abda324c-31bd-473f-8633-0cdd8bdbf780',
        }

        if live:
            params['videoType'] = 'live'

        data = self._session.get('https://videoservice.swm.digital/playback',
                                 params=params).json()
        if 'media' not in data:
            raise APIError(data[0]['error_code'])

        return process_brightcove(data['media'])

    def logout(self):
        mem_cache.empty()
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        self.new_session()
コード例 #7
0
ファイル: api.py プロジェクト: Raspifan2020/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('access_token')
        if not token:
            return

        self._session.headers.update(
            {'authorization': 'Bearer {}'.format(token)})
        self._session.headers.update(
            {'x-user-profile': userdata.get('profile_id')})
        self.logged_in = True

    def _query_request(self, query, variables=None, **kwargs):
        self._refresh_token()

        data = {
            'query': ' '.join(query.split()),
            'variables': variables or {},
        }

        return self._session.post(GRAPH_URL, json=data, **kwargs).json()

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires',
                                      0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')

        payload = {
            'client_id': CLIENT_ID,
            'refresh_token': userdata.get('refresh_token'),
            'grant_type': 'refresh_token',
            'scope': 'openid profile email offline_access',
        }

        self._oauth_token(payload)

    def _oauth_token(self, payload):
        token_data = self._session.post('https://login.sky.co.nz/oauth/token',
                                        json=payload,
                                        error_msg=_.TOKEN_ERROR).json()

        if 'error' in token_data:
            error = _.REFRESH_TOKEN_ERROR if data.get(
                'grant_type') == 'refresh_token' else _.LOGIN_ERROR
            raise APIError(_(error, msg=token_data.get('error_description')))

        userdata.set('access_token', token_data['access_token'])
        userdata.set('expires', int(time() + token_data['expires_in'] - 15))
        if 'refresh_token' in token_data:
            userdata.set('refresh_token', token_data['refresh_token'])

        #Force 1st profile
        data = jwt_data(token_data['access_token'])
        profile_id = data['https://skygo.co.nz/profiles'][0]['id']
        userdata.set('profile_id', profile_id)
        ####

        self._set_authentication()

    def channels(self):
        ids = []
        channels = []

        groups = self._query_request(
            queries.CHANNELS)['data']['linearChannelGroups']
        for group in groups:
            for row in group.get('channels', []):
                if row['__typename'] == 'LinearChannel' and row[
                        'id'] not in ids:
                    ids.append(row['id'])
                    channels.append(row)

        return sorted(channels, key=lambda x: x['number'])

    def play_channel(self, channel_id):
        variables = {
            'deviceId': '',
            'assetId': channel_id,
            'channelId': channel_id,
            # 'playbackDevice': {
            #     'platform': 'Windows',
            #     'osVersion': '10',
            #     'drmType': 'WIDEVINE',
            #     'drmLevel': 'SW_SECURE_DECODE'
            # }
        }

        data = self._query_request(queries.START_LINEAR,
                                   variables)['data']['startLinearPlayback']

        if data['__typename'] == 'SubscriptionNeeded':
            raise APIError('{} Subscription Required'.format(
                data['subscriptions'][0]['title']))
        elif data['__typename'] == 'Geoblocked':
            raise APIError(_.GEO_ERROR)
        elif data['__typename'] != 'LinearPlaybackSources':
            raise APIError('Unkown error: {}'.format(data['__typename']))

        try:
            self._query_request(queries.STOP_LINEAR,
                                variables)['data']['stopLinearPlayback']
        except:
            log.debug('Stop Linear Failed')

        return data['playbackSource']['streamUri'], data['playbackSource'][
            'drmLicense']['licenseUri']

    def login(self, username, password):
        self.logout()

        params = {
            'client_id': CLIENT_ID,
            'audience': 'https://api.sky.co.nz',
            'redirect_uri': 'https://www.skygo.co.nz',
            'connection': 'Sky-Internal-Connection',
            'scope': 'openid profile email offline_access',
            'response_type': 'code',
        }

        resp = self._session.get('https://login.sky.co.nz/authorize',
                                 params=params,
                                 allow_redirects=False)
        parsed = urlparse(resp.headers['location'])
        payload = dict(parse_qsl(parsed.query))
        payload.update({
            'username': username,
            'password': password,
            'tenant': 'skynz-prod',
            'client_id': CLIENT_ID,
            'client': None,
        })

        resp = self._session.post(
            'https://login.sky.co.nz/usernamepassword/login', json=payload)
        if not resp.ok:
            data = resp.json()
            raise APIError(_(_.LOGIN_ERROR, msg=data['message']))

        soup = BeautifulSoup(resp.text, 'html.parser')

        payload = {}
        for e in soup.find_all('input'):
            if 'name' in e.attrs:
                payload[e.attrs['name']] = e.attrs.get('value')

        resp = self._session.post('https://login.sky.co.nz/login/callback',
                                  data=payload,
                                  allow_redirects=False)
        parsed = urlparse(resp.headers['location'])
        data = dict(parse_qsl(parsed.query))

        payload = {
            'code': data['code'],
            'client_id': CLIENT_ID,
            'grant_type': 'authorization_code',
            'redirect_uri': 'https://www.skygo.co.nz'
        }

        self._oauth_token(payload)

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        userdata.delete('profile_id')
        self.new_session()
コード例 #8
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS, base_url=API_BASE)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('token')
        if not token:
            return

        self._session.headers.update(
            {'Authorization': 'Bearer {}'.format(token)})
        self.logged_in = True

    @contextmanager
    def api_call(self):
        if self.logged_in:
            self.refresh_token()

        try:
            yield
        except Exception as e:
            log.exception(e)
            raise APIError(_.NO_DATA)

    def refresh_token(self):
        if not self.logged_in or time.time() < userdata.get('expires', 0):
            return

        data = self._session.put('/oam/v2/user/tokens').json()

        if 'errorMessage' in data:
            self.logout()
            raise APIError(_(_.TOKEN_ERROR, msg=data['errorMessage']))

        self._set_token(data['sessionToken'])

    def _set_token(self, token):
        data = jwt_data(token)
        expires = min(int(time.time() + 86400), data['exp'] - 10)

        userdata.set('expires', expires)
        userdata.set('token', token)

        self._set_authentication()

    def login(self, username, password):
        self.logout()

        deviceid = str(uuid.uuid3(uuid.UUID(UUID_NAMESPACE), str(username)))

        payload = {
            "username": username,
            "password": password,
            "deviceID": deviceid,
        }

        headers = {'Authorization': 'Bearer {}'.format(DEFAULT_TOKEN)}

        with self.api_call():
            data = self._session.post('/oam/v2/user/tokens',
                                      json=payload,
                                      headers=headers).json()

        if 'errorMessage' in data:
            raise APIError(_(_.LOGIN_ERROR, msg=data['errorMessage']))

        userdata.set('deviceid', deviceid)

        self._set_token(data['sessionToken'])

    def whats_on(self, query=''):
        now = arrow.utcnow()
        later = now.shift(days=21)

        params = {
            'count': 100,
            'offset': 0,
            'language': '*',
            'query': query,
            'sort': 'startTime',
            'sortOrder': 'asc',
            'startTime.lte': later.format('YYYY-MM-DDTHH:mm:ss.000') + 'Z',
            'endTime.gte': now.format('YYYY-MM-DDTHH:mm:ss.000') + 'Z',
            'types': 'live/competitions,live/teamCompetitions,live/events',
        }

        with self.api_call():
            return self._session.get('/ocm/v2/search',
                                     params=params).json()['results']

    def search(self, query):
        params = {
            'count': 100,
            'offset': 0,
            'language': '*',
            'query': query,
            'sort': 'liveEventDate',
            'sortOrder': 'desc',
            'searchMethods': 'prefix,fuzzy',
            'types': 'vod/competitions,vod/teamCompetitions,vod/events',
        }

        with self.api_call():
            return self._session.get('/ocm/v2/search',
                                     params=params).json()['results']

    def sparksport(self):
        with self.api_call():
            return self._session.get(
                'https://d2rhrqdzx7i00p.cloudfront.net/sparksport2').json()

    def page(self, page_id):
        with self.api_call():
            return self._session.get('/ocm/v4/pages/{}'.format(page_id)).json()

    @cached(expires=60 * 10)
    def section(self, section_id):
        with self.api_call():
            return self._session.get(
                '/ocm/v4/sections/{}'.format(section_id)).json()

    def live_channels(self):
        with self.api_call():
            return self._session.get(
                '/ocm/v2/epg/stations').json()['epg/stations']

    def entitiy(self, entity_id):
        with self.api_call():
            data = self._session.get(
                '/ocm/v2/entities/{}'.format(entity_id)).json()

        for key in data:
            try:
                if data[key][0]['id'] == entity_id:
                    return data[key][0]
            except (TypeError, KeyError):
                continue

        return None

    def play(self, entity_id):
        entity = self.entitiy(entity_id)
        if not entity or not entity.get('assetIDs'):
            raise APIError(_.NO_ASSET_ERROR)

        with self.api_call():
            assets = self._session.get('/ocm/v2/assets/{}'.format(
                entity['assetIDs'][0])).json()['assets']

        mpd_url = None
        for asset in assets:
            try:
                urls = asset['liveURLs'] or asset['vodURLs']
                mpd_url = urls['dash']['primary']
                backup = urls['dash'].get('backup')
                if 'dai.google.com' in mpd_url and backup and 'dai.google.com' not in backup:
                    mpd_url = backup
            except (TypeError, KeyError):
                continue
            else:
                break

        if not mpd_url:
            raise APIError(_.NO_MPD_ERROR)

        # Hack until Spark fix their bad second base-url
        # if '/startover/' in mpd_url:
        #     mpd_url = mpd_url.split('/')
        #     mpd_url = "/".join(mpd_url[:-3]) + '/master.mpd'
        #

        payload = {
            'assetID': entity_id,
            'playbackUrl': mpd_url,
            'deviceID': userdata.get('deviceid'),
        }

        data = self._session.post('/oem/v2/entitlement?tokentype=isp-atlas',
                                  json=payload).json()
        token = data.get('entitlementToken')

        if not token:
            raise APIError(_(_.NO_ENTITLEMENT, error=data.get('errorMessage')))

        params = {
            'progress': 0,
            'device': userdata.get('deviceid'),
        }

        self._session.put('/oxm/v1/streams/{}/stopped'.format(entity_id),
                          params=params)

        headers = {'X-ISP-TOKEN': token}

        from_start = True
        if entity.get('customAttributes',
                      {}).get('isLinearChannelInLiveEvent') == 'true':
            from_start = False

        return mpd_url, WV_LICENSE_URL, headers, from_start

    def logout(self):
        userdata.delete('token')
        userdata.delete('deviceid')
        userdata.delete('expires')
        self.new_session()
コード例 #9
0
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS, base_url=BASE_URL)
        self.set_authentication()

    def set_authentication(self):
        token = userdata.get('auth_token')
        if not token:
            return

        self._session.headers.update({'x-auth-token': token})
        self.logged_in = True

    def login(self, username, password):
        self.logout()

        data = {'user': {
            'email': username, 
            'password': password, 
            'remember_me': True
        }}

        data = self._session.post('/services/users/auth/sign_in', json=data).json()
        if 'error' in data:
            raise APIError(data['error'])

        auth_token = data['auth_token']
        user_id = data['account']['user_id']

        userdata.set('auth_token', auth_token)
        userdata.set('user_id', user_id)

        self.set_authentication()

    def my_library(self):
        meta = {}

        items = self._session.get('/services/content/v3/user_library/{}/index'.format(userdata.get('user_id')), params={'sort_by': 'relevance'}).json()
        _meta = self._session.get('/services/meta/v2/film/{}/show_multiple'.format(','.join(str(x['info']['film_id']) for x in items))).json()
        for item in _meta:
            meta[item['film_id']] = item

        for item in items:
            item['meta'] = meta.get(item['info']['film_id'], {})

        return items

    def get_stream(self, film_id):
        play_data = self._session.get('/services/content/v4/media_content/play/film/{}'.format(film_id), params={'encoding_type':'dash', 'drm':'widevine'}).json()
        if 'error' in play_data:
            raise APIError(play_data['error'])

        mpd_url = play_data['streams'][0]['url']
        key_url = BASE_URL.format('/services/license/widevine/cenc?context={}'.format(play_data['streams'][0]['drm_key_encoded'].strip()))

        item = plugin.Item(
            path = play_data['streams'][0]['url'],
            inputstream = inputstream.Widevine(license_key=key_url),
            headers = self._session.headers,
        )

        return item

    def logout(self):
        userdata.delete('auth_token')
        userdata.delete('user_id')
        self.new_session()
コード例 #10
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(base_url=BASE_URL, headers=HEADERS)
        self._set_auth(userdata.get('token'))

    def _set_auth(self, token):
        if not token:
            return

        self._session.cookies.update({
            '_session': token,
            '_device': userdata.get('device_id')
        })
        self.logged_in = True

    def login(self, username, password):
        self.logout(site_logout=False)

        device_id = hash_6(username.lower().strip(), length=20)
        device_id = 'Windows:Chrome:' + device_id[:5] + '_' + device_id[
            5:13] + '_' + device_id[13:]
        self._session.cookies.update({'_device': device_id})

        resp = self._session.get('/login')
        soup = BeautifulSoup(resp.text, 'html.parser')

        found = None
        for form in soup.find_all('form'):
            data = {}
            for e in form.find_all('input'):
                data[e.attrs['name']] = e.attrs.get('value')

            if 'email' in data and 'password' in data:
                found = data
                break

        if not found:
            raise APIError(_.LOGIN_FORM_ERROR)

        found.update({
            'email': username,
            'password': password,
        })

        resp = self._session.post('/login', data=data, allow_redirects=False)
        if resp.status_code != 302 or not resp.cookies.get('_session'):
            raise APIError(_.LOGIN_FAILED)

        token = resp.cookies.get('_session')
        userdata.set('token', token)
        userdata.set('device_id', device_id)

        self._set_auth(token)

    def play(self, slug):
        resp = self._session.get('/videos/{slug}'.format(slug=slug),
                                 allow_redirects=False)

        if resp.status_code == 302 or 'The device limit for your account has been reached' in resp.text:
            raise APIError(_.DEVICE_LIMIT)

        page = resp.text.replace(' ', '').strip()
        play_url = re.search('embed_url:"(.*?)"', page).group(1)

        resp = self._session.get(play_url)
        page = resp.text.replace(' ', '').strip()

        event_id = re.search('eventId:(.*?),', page)

        if event_id:
            config_url = LIVESTREAM_URL.format(event_id=event_id.group(1))
        else:
            config_url = re.search('"config_url":"(.*?)"', page).group(1)
            config_url = config_url.encode().decode('unicode_escape')

        data = self._session.get(config_url,
                                 headers={
                                     'Referer': 'https://embed.vhx.tv/'
                                 }).json()
        if data.get('secure_m3u8_url'):
            return data['secure_m3u8_url'], inputstream.HLS()

        default_cdn = data['request']['files']['dash']['default_cdn']
        mpd_url = data['request']['files']['dash']['cdns'][default_cdn][
            'url']  #.replace('.json?base64_init=1', '.mpd')
        mpd_url = mpd_url.replace('.json', '.mpd')

        if data['request'].get('drm'):
            license_url = self._session.get(
                data['request']['drm']['cdms']['widevine']['license_url']).text
            ia = inputstream.Widevine(license_key=license_url)
        else:
            ia = inputstream.MPD()

        return mpd_url, ia

    def _vhx_token(self):
        token = mem_cache.get('vhx_token')
        if token:
            return token

        page = self._session.get('/').text.replace(' ', '').strip()
        token = re.search('TOKEN="(.*?)"', page).group(1)
        data = jwt_data(token)
        expires_in = int(data['exp'] - time.time()) - 30
        mem_cache.set('vhx_token', token, expires_in)

        return token

    def browse(self):
        params = {
            'product': 'https://api.vhx.tv/products/70302',
            'per_page': 100,
        }

        return self._session.get('https://api.vhx.tv/browse',
                                 params=params,
                                 headers={
                                     'Authorization':
                                     'Bearer {}'.format(self._vhx_token())
                                 }).json()

    def search(self, query, page=1):
        params = {
            'query': query,
            'product': 'https://api.vhx.tv/products/70302',
            'page': 1,
            'per_page': 100,
        }

        return self._session.get('https://api.vhx.tv/collections',
                                 params=params,
                                 headers={
                                     'Authorization':
                                     'Bearer {}'.format(self._vhx_token())
                                 }).json()

    def collection(self, id):
        params = {
            'per_page': 100,
        }

        return self._session.get(
            'https://api.vhx.tv/collections/{}/items'.format(id),
            params=params,
            headers={
                'Authorization': 'Bearer {}'.format(self._vhx_token())
            }).json()

    def logout(self, site_logout=True):
        if site_logout:
            self._session.get('/logout')

        userdata.delete('token')
        userdata.delete('device_id')
        mem_cache.delete('vhx_token')

        self.new_session()
コード例 #11
0
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS, base_url=API_BASE)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('token')
        if not token:
            return

        self._session.cookies.update({TOKEN_COOKIE_KEY: token})
        self.logged_in = True

    def require_token(self):
        if TOKEN_COOKIE_KEY in self._session.cookies:
            return

        app_version = self._session.get(APP_VERSION_URL).text.strip()

        payload = {
            "ClientVersion": app_version,
            "DeviceBrand": "Nvidia",
            "DeviceFirmwareVersion": "27",
            "DeviceId": "4jhrpoUfUr1Dxmbf",
            "DeviceManufacturer": "Nvidia",
            "DeviceModel": "Shield",
            "DeviceName": "AndroidTv",
            "IsRoot": "false",
            "MacAddress": "00:00:00:00:00:00",
        }

        token = self._session.post('/api/configuration/appconfig',
                                   json=payload).json()['Data']['AccessToken']
        self._session.cookies.update({TOKEN_COOKIE_KEY: token})

    def device_code(self):
        self.require_token()
        return self._session.post(
            '/api/account/generateauthcode').json()['Data']

    def device_login(self, code):
        self.require_token()

        payload = {
            'AuthCode': code,
        }

        data = self._session.post('/api/account/loginwithauthcode',
                                  json=payload).json()['Data']
        if not data:
            return

        token = data['User']['AccessToken']
        userdata.set('token', token)
        self._set_authentication()

        return True

    def login(self, username, password):
        self.require_token()

        payload = {
            'Action':
            '/View/Account/SubmitLogin',
            'jsonModel':
            json.dumps({
                'Username': username,
                'Password': password,
                'IsOnboarding': False,
                'IsVoucher': False,
            }),
            'captcha':
            '',
        }

        resp = self._session.post('/View/Account/SubmitLogin', json=payload)
        token = resp.cookies.get(TOKEN_COOKIE_KEY)

        if not token:
            raise APIError(_.LOGIN_ERROR)

        userdata.set('token', token)
        self._set_authentication()

    def live_channels(self):
        items = []

        for i in range(10):
            payload = {
                'Page': i,
                'PageSize': PAGESIZE,
            }

            data = self._session.post('/api/broadcast/channels',
                                      json=payload).json()['Data']
            items.extend(data['Items'])

            if len(data['Items']) < PAGESIZE:
                break

        return items

    def epg(self, days=3):
        start = arrow.utcnow()
        end = start.shift(days=days)

        payload = {
            'StartTime': start.format('YYYY-MM-DDTHH:mm:00.000') + 'Z',
            'EndTime': end.format('YYYY-MM-DDTHH:mm:00.000') + 'Z',
            'OnlyLiveEvents': False,
            #  'ChannelId': 'beinsports1',
        }

        return self._session.post('/api/broadcast/tvguide',
                                  json=payload).json()['Data']['Items']

    def catch_up(self, _type=0, catalog_id='CATCHUP'):
        items = []

        for i in range(10):
            payload = {
                'Page': i,
                'PageSize': PAGESIZE,
                'Type': _type,
                'CatalogId': catalog_id,
            }

            data = self._session.post('/api/content/catchups',
                                      json=payload).json()['Data']

            rows = data.get('Items', [])
            if not rows:
                break

            items.extend(rows)
            if len(rows) < PAGESIZE:
                break

        return items

    def play(self, channel_id=None, vod_id=None):
        payload = {
            'ChannelId': channel_id,
            'VodId': vod_id,
        }

        data = self._session.post('/api/play/play',
                                  json=payload).json()['Data']
        if not data:
            raise APIError(_.NO_STREAM)

        return data

    def logout(self):
        userdata.delete('token')
        self.new_session()
コード例 #12
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS, base_url=API_URL)
        self._set_authentication()

    def _set_authentication(self):
        self.logged_in = userdata.get('token') != None

    def nav_items(self, key):
        data = self.page('sitemap')

        for row in data['navs']['browse']:
            if row['path'] == '/' + key:
                return row['items']

        return []

    def page(self, key):
        return self.url('/pages/v6/{}.json'.format(key))

    def url(self, url):
        self._check_token()

        params = {
            'feedTypes': 'posters,landscapes,hero',
            'jwToken': userdata.get('token'),
        }

        return self._session.get(url, params=params).json()

    def search(self, query, page=1, limit=50):
        self._check_token()

        params = {
            'q': query,
            'limit': limit,
            'offset': (page - 1) * limit,
            'jwToken': userdata.get('token'),
        }

        if userdata.get('profile_kids', False):
            url = '/search/v12/kids/search'
        else:
            url = '/search/v12/search'

        return self._session.get(url, params=params).json()

    def login(self, username, password):
        self.logout()

        payload = {
            'email': username,
            'password': password,
            'rnd': str(int(time.time())),
            'stanName': 'Stan-Android',
            'type': 'mobile',
            'os': 'Android',
            'stanVersion': STAN_VERSION,
            #   'clientId': '',
            # 'model': '',
            #  'sdk': '',
            # 'manufacturer': '',
        }

        payload['sign'] = self._get_sign(payload)

        self._login('/login/v1/sessions/mobile/account', payload)

    def _check_token(self, force=False):
        if not force and userdata.get('expires') > time.time():
            return

        params = {
            'type': 'mobile',
            'os': 'Android',
            'stanVersion': STAN_VERSION,
        }

        payload = {
            'jwToken': userdata.get('token'),
        }

        self._login('/login/v1/sessions/mobile/app', payload, params)

    def _login(self, url, payload, params=None):
        data = self._session.post(url, data=payload, params=params).json()

        if 'errors' in data:
            try:
                msg = data['errors'][0]['code']
                if msg == 'Streamco.Login.VPNDetected':
                    msg = _.IP_ADDRESS_ERROR
            except:
                msg = ''

            raise APIError(_(_.LOGIN_ERROR, msg=msg))

        userdata.set('token', data['jwToken'])
        userdata.set('expires',
                     int(time.time() + (data['renew'] - data['now']) - 30))
        userdata.set('user_id', data['userId'])

        userdata.set('profile_id', data['profile']['id'])
        userdata.set('profile_name', data['profile']['name'])
        userdata.set('profile_icon', data['profile']['iconImage']['url'])
        userdata.set('profile_kids',
                     int(data['profile'].get('isKidsProfile', False)))

        self._set_authentication()

        try:
            log.debug('Token Data: {}'.format(
                json.dumps(jwt_data(userdata.get('token')))))
        except:
            pass

    def watchlist(self):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
        }

        url = '/watchlist/v1/users/{user_id}/profiles/{profile_id}/watchlistitems'.format(
            user_id=userdata.get('user_id'),
            profile_id=userdata.get('profile_id'))
        return self._session.get(url, params=params).json()

    def history(self, program_ids=None):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
            'limit': 100,
        }

        if program_ids:
            params['programIds'] = program_ids

        url = '/history/v1/users/{user_id}/profiles/{profile_id}/history'.format(
            user_id=userdata.get('user_id'),
            profile_id=userdata.get('profile_id'))
        return self._session.get(url, params=params).json()

    # def resume_series(self, series_id):
    #     params = {
    #         'jwToken': userdata.get('token'),
    #     }

    #     url = '/resume/v1/users/{user_id}/profiles/{profile_id}/resumeSeries/{series_id}'.format(user_id=userdata.get('user_id'), profile_id=userdata.get('profile_id'), series_id=series_id)
    #     return self._session.get(url, params=params).json()

    # def resume_program(self, program_id):
    #     params = {
    #         'jwToken': userdata.get('token'),
    #     }

    #     url = '/resume/v1/users/{user_id}/profiles/{profile_id}/resume/{program_id}'.format(user_id=userdata.get('user_id'), profile_id=userdata.get('profile_id'), program_id=program_id)
    #     return self._session.get(url, params=params).json()

    def set_profile(self, profile_id):
        self._check_token()

        params = {
            'type': 'mobile',
            'os': 'Android',
            'stanVersion': STAN_VERSION,
        }

        payload = {
            'jwToken': userdata.get('token'),
            'profileId': profile_id,
        }

        self._login('/login/v1/sessions/mobile/app', payload, params)

    def profiles(self):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
        }

        return self._session.get(
            '/accounts/v1/users/{user_id}/profiles'.format(
                user_id=userdata.get('user_id')),
            params=params).json()

    def add_profile(self, name, icon_set, icon_index, kids=False):
        self._check_token()

        payload = {
            'jwToken': userdata.get('token'),
            'name': name,
            'isKidsProfile': kids,
            'iconSet': icon_set,
            'iconIndex': icon_index,
        }

        return self._session.post(
            '/accounts/v1/users/{user_id}/profiles'.format(
                user_id=userdata.get('user_id')),
            data=payload).json()

    def delete_profile(self, profile_id):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
            'profileId': profile_id,
        }

        return self._session.delete(
            '/accounts/v1/users/{user_id}/profiles'.format(
                user_id=userdata.get('user_id')),
            params=params).ok

    def profile_icons(self):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
        }

        return self._session.get('/accounts/v1/accounts/icons',
                                 params=params).json()

    def program(self, program_id):
        self._check_token()

        params = {
            'jwToken': userdata.get('token'),
        }

        if userdata.get('profile_kids', False):
            url = '/cat/v12/kids/programs/{program_id}.json'
        else:
            url = '/cat/v12/programs/{program_id}.json'

        return self._session.get(url.format(program_id=program_id),
                                 params=params).json()

    def play(self, program_id):
        self._check_token(force=True)

        program_data = self.program(program_id)
        if 'errors' in program_data:
            try:
                msg = program_data['errors'][0]['code']
                if msg == 'Streamco.Concurrency.OutOfRegion':
                    msg = _.IP_ADDRESS_ERROR
                elif msg == 'Streamco.Catalogue.NOT_SAFE_FOR_KIDS':
                    msg = _.KIDS_PLAY_DENIED
            except:
                msg = ''

            raise APIError(_(_.PLAYBACK_ERROR, msg=msg))

        jw_token = userdata.get('token')

        params = {
            'programId': program_id,
            'jwToken': jw_token,
            'format': 'dash',
            'capabilities.drm': 'widevine',
            'quality': 'high',
        }

        data = self._session.get('/concurrency/v1/streams',
                                 params=params).json()

        if 'errors' in data:
            try:
                msg = data['errors'][0]['code']
                if msg == 'Streamco.Concurrency.OutOfRegion':
                    msg = _.IP_ADDRESS_ERROR
            except:
                msg = ''

            raise APIError(_(_.PLAYBACK_ERROR, msg=msg))

        play_data = data['media']
        play_data['drm']['init_data'] = self._init_data(
            play_data['drm']['keyId'])
        play_data['videoUrl'] = API_URL.format(
            '/manifest/v1/dash/androidtv.mpd?url={url}&audioType=all&version=88'
            .format(url=quote_plus(play_data['videoUrl']), ))

        params = {
            'form': 'json',
            'schema': '1.0',
            'jwToken': jw_token,
            '_id': data['concurrency']['lockID'],
            '_sequenceToken': data['concurrency']['lockSequenceToken'],
            '_encryptedLock': 'STAN',
        }

        self._session.get('/concurrency/v1/unlock', params=params).json()

        return program_data, play_data

    def _init_data(self, key):
        key = key.replace('-', '')
        key_len = '{:x}'.format(len(bytearray.fromhex(key)))
        key = '12{}{}'.format(key_len, key)
        key = bytearray.fromhex(key)

        return cenc_init(key)

    def _get_sign(self, payload):
        module_version = 214

        f3757a = bytearray(
            (144, 100, 149, 1, 2, 8, 36, 208, 209, 51, 103, 131, 240, 66,
             module_version, 20, 195, 170, 44, 194, 17, 161, 118, 71, 105, 42,
             76, 116, 230, 87, 227, 40, 115, 5, 62, 199, 66, 7, 251, 125, 238,
             123, 71, 220, 179, 29, 165, 136, 16, module_version, 117, 10, 100,
             222, 41, 60, 103, 2, 121, 130, 217, 75, 220, 100, 59, 35, 193, 22,
             117, 27, 74, 50, 85, 40, 39, 31, 180, 81, 34, 155, 172, 202, 71,
             162, 202, 234, 91, 176, 199, 207, 131, 229, 125, 105, 9, 227, 188,
             234, 61, 33, 17, 113, 222, 173, 182, 120, 34, 80, 135, 219, 8, 97,
             176, 62, 137, 126, 222, 139, 136, 77, 243, 37, 11, 234, 82, 244,
             222, 44))

        f3758b = bytearray(
            (120, 95, 52, 175, 139, 155, 151, 35, 39, 184, 141, 27, 55, 215,
             102, 173, 2, 37, 141, 164, 236, 217, 173, 194, 94, 67, 195, 24,
             221, 66, 233, 11, 226, 91, 33, 249, 225, 54, 88, 54, 118, 101, 31,
             248, 11, 208, 206, 226, 68, 20, 143, 37, 104, 159, 184, 22, 53,
             179, 104, 152, 170, 29, 26, 6, 163, 45, 87, 193, 136, 226, 128,
             245, 231, 238, 154, 211, 71, 134, 232, 99, 35, 54, 170, 128, 1,
             218, 249, 70, 182, 145, 125, 211, 16, 43, 118, 177, 64, 128, 111,
             73, 234, 22, 21, 165, 67, 23, 15, 5, 11, 70, 48, 97, 134, 185, 11,
             28, 167, 140, 123, 81, 240, 247, 77, 187, 23, 243, 89, 54))

        msg = ''
        for key in sorted(payload.keys()):
            if msg: msg += '&'
            msg += key + '=' + quote_plus(payload[key], safe="_-!.~'()*")

        bArr = bytearray(len(f3757a))
        for i in range(len(bArr)):
            bArr[i] = f3757a[i] ^ f3758b[i]

        bArr2 = bytearray(int(len(bArr) / 2))
        for i in range(len(bArr2)):
            bArr2[i] = bArr[i] ^ bArr[len(bArr2) + i]

        signature = hmac.new(bArr2,
                             msg=msg.encode('utf8'),
                             digestmod=hashlib.sha256).digest()

        return base64.b64encode(signature).decode('utf8')

    def logout(self):
        userdata.delete('token')
        userdata.delete('expires')
        userdata.delete('user_id')

        userdata.delete('profile_id')
        userdata.delete('profile_icon')
        userdata.delete('profile_name')
        userdata.delete('profile_kids')

        self.new_session()
コード例 #13
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(headers=HEADERS, base_url=BASE_URL)
        self.logged_in = userdata.get('key', None) != None

        #LEGACY
        userdata.delete('token')
        userdata.delete('user_id')

    def _set_auth(self, token):
        self._session.headers.update({'Authorization': 'Bearer {}'.format(token)})

    def login(self, username, password):
        self.logout()

        payload = {
            'email': username,
            'password': password,
        }

        data = self._session.post('https://api.watchnebula.com/api/v1/auth/login/', json=payload).json()
        if 'key' not in data or not data['key']:
            msg = data['non_field_errors'][0]
            raise APIError(msg)

        userdata.set('key', data['key'])

    def _refresh_token(self, force=False):
        token = userdata.get('auth_token')
        if token and not force and userdata.get('expires', 0) > time():
            self._set_auth(token)
            return

        log.debug('Refreshing token' if token else 'Fetching token')

        key = userdata.get('key')
        data = self._session.post('https://api.watchnebula.com/api/v1/authorization/', params={'from': 'Android'}, json={}, headers={'Authorization': 'Token {}'.format(key)}).json()

        token = data['token']
        jwt = jwt_data(token)

        userdata.set('auth_token', token)
        userdata.set('expires', jwt['exp'] - 10)

        self._set_auth(token)

    @mem_cache.cached(expires=60*5)
    def categories(self):
        self._refresh_token()
        return self._session.get('/video/categories/').json()['results']

    @mem_cache.cached(expires=60*5)
    def podcast_categories(self):
        self._refresh_token()
        return self._session.get('/podcast/categories/').json()['results']

    @mem_cache.cached(expires=60*5)
    def podcast_creators(self, category='', page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        if category:
            params['category'] = category

        return self._session.get('/podcast/channels/', params=params).json()

    @mem_cache.cached(expires=60*5)
    def podcasts(self, slug, page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        return self._session.get('/podcast/channels/{slug}/'.format(slug=slug), params=params).json()

    @mem_cache.cached(expires=60*5)
    def search_videos(self, query, page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
            'text': query,
        }

        return self._session.get('/search/video/', params=params).json()

    @mem_cache.cached(expires=60*5)
    def search_creators(self, query):
        self._refresh_token()

        params = {
            'text': query,
        }

        return self._session.get('/search/channel/video/', params=params).json()  

    @mem_cache.cached(expires=60*5)
    def search_podcasts(self, query):
        self._refresh_token()

        params = {
            'text': query,
        }

        return self._session.get('/search/channel/podcast/', params=params).json()  

    @mem_cache.cached(expires=60*5)
    def featured(self):
        self._refresh_token()
        return self._session.get('/featured/').json()

    @mem_cache.cached(expires=60*5)
    def videos(self, category='', page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        if category:
            params['category'] = category

        return self._session.get('/video/', params=params).json()

    def my_videos(self, page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        return self._session.get('/library/video/', params=params).json()

    def my_creators(self, page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        return self._session.get('/library/video/channels/', params=params).json()

    @mem_cache.cached(expires=60*5)
    def creator(self, slug, page=1, page_size=100):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        return self._session.get('/video/channels/{slug}/'.format(slug=slug), params=params).json()

    @mem_cache.cached(expires=60*5)
    def creators(self, category='', page=1, page_size=100, random=False):
        self._refresh_token()

        params = {
            'page': page,
            'page_size': page_size,
        }

        if category:
            params['category'] = category

        if random:
            params['random'] = 'true'

        return self._session.get('/video/channels/', params=params).json()

    def follow_creator(self, slug):
        self._refresh_token()

        payload = {
            'channel_slug': slug,
        }

        if not self._session.post('/engagement/video/follow/', json=payload).ok:
            raise APIError('Failed to follow creator')

    def unfollow_creator(self, slug):
        self._refresh_token()

        payload = {
            'channel_slug': slug,
        }

        if not self._session.post('/engagement/video/unfollow/', json=payload).ok:
            raise APIError('Failed to unfollow creator')

    def play(self, slug):
        self._refresh_token()
        
        data = self._session.get('/video/{slug}/stream/'.format(slug=slug)).json()
        resp = self._session.head(data['iframe'])
        url = resp.headers.get('Location').replace('.html', '')

        data = Session(headers=HEADERS).get(url).json()
        if 'response' not in data:
            raise APIError('{}'.format(data.get('message', 'unknown error getting playdata')))

        play_url = data['response']['body']['outputs'][0]['url']
        subtitles = data['response']['body'].get('subtitles', [])

        return play_url, subtitles

    def play_podcast(self, channel, episode):
        self._refresh_token()
        return self._session.get('/podcast/channels/{channel}/episodes/{episode}/'.format(channel=channel, episode=episode)).json()

    def logout(self):
        userdata.delete('key')
        userdata.delete('auth_token')
        userdata.delete('expires')
        mem_cache.empty()
        self.new_session()
コード例 #14
0
ファイル: api.py プロジェクト: Raspifan2020/slyguy.addons
class API(object):
    def new_session(self):
        self._session = Session(HEADERS, base_url=API_URL)
        self.logged_in = userdata.get('token') != None

    def login(self, username, password):
        data = {
            'username': username,
            'password': password,
            'cookielink': 'true',
            'format': 'json',
        }

        r = self._session.post('/secure/authenticate', data=data)
        data = r.json()
        token = r.cookies.get('nllinktoken')

        if not token:
            raise APIError(data.get('code'))

        userdata.set('token', token)

    def get_play_url(self, game, game_type):
        params = {
            'id': game.id,
            'gs': game.state,
            'gt': game_type,
            'type': 'game',
            'format': 'json',
        }

        if game.state == Game.PROCESSING:
            params['st'] = game.start * 1000
            params['dur'] = game.duration * 1000

        cookies = {
            'nllinktoken': userdata.get('token'),
            'RugbyLoggedIn': userdata.get('username')
        }

        resp = self._session.get('/service/publishpoint',
                                 params=params,
                                 cookies=cookies)
        if not resp.ok:
            data = self._session.get('/game/{}'.format(game.slug),
                                     params={
                                         'format': 'json',
                                         'purchases': True
                                     },
                                     cookies=cookies).json()
            if data.get('noAccess'):
                raise APIError(_.NO_ACCESS)
            elif 'blackout' in data:
                raise APIError(_.GEO_ERROR, heading=_.GEO_HEADING)
            else:
                raise APIError(_.PLAY_ERROR)

        return resp.json()['path']

    def _parse_game(self, item):
        def get_timestamp(key):
            if key in item:
                return arrow.get(item[key]).timestamp
            else:
                return 0

        info = {'home': item['homeTeam'], 'away': item['awayTeam']}
        game = Game(id=int(item['id']),
                    state=int(item['gameState']),
                    start=get_timestamp('dateTimeGMT'),
                    end=get_timestamp('endDateTimeGMT'),
                    slug=str(item['seoName']),
                    info=info)

        return game

    def update_games(self):
        to_create = []

        data = self._session.get('/scoreboard', params={
            'format': 'json'
        }).json()
        if 'games' not in data:
            if data.get('code') == 'failedgeo':
                raise APIError(_.GEO_ERROR, heading=_.GEO_HEADING)
            else:
                raise APIError(_.GAMES_ERROR)

        for row in data['games']:
            game = self._parse_game(row)
            to_create.append(game)

        Game.truncate()
        Game.bulk_create(to_create, batch_size=100)

    def fetch_game(self, slug):
        data = self._session.get('/game/{}'.format(slug),
                                 params={
                                     'format': 'json'
                                 }).json()
        return self._parse_game(data)

    def channels(self):
        return self._session.get('/channels', params={'format': 'json'}).json()

    def search(self, cat_id, query, page=1):
        params = {
            'param': '*{}*'.format(query),
            'fq': 'catId2:{}'.format(cat_id),
            'pn': page,
            'format': 'json',
        }

        return self._session.get('/search', params=params).json()

    def logout(self):
        userdata.delete('token')
        self.new_session()
コード例 #15
0
class API(object):
    def new_session(self):
        self.logged_in = False

        self._session = Session(HEADERS)
        self.set_authentication()

    def set_authentication(self):
        ob_session = userdata.get('ob_session')
        if not ob_session:
            return

        self._session.headers.update({
            'X-OB-Channel': 'I',
            'X-OB-SESSION': ob_session
        })

        self._session.cookies.clear()
        self._session.cookies.update({
            'OB-SESSION': ob_session,
            'OB-PERSIST': '1'
        })

        self.logged_in = True

    def login(self, username, password):
        self.logout()

        data = {"username": username, "password": password}

        r = self._session.post(
            'https://auth.tab.co.nz/identity-service/api/v1/assertion/by-credentials',
            json=data)

        if r.status_code == 403:
            raise APIError(_.GEO_ERROR)
        elif r.status_code != 201:
            raise APIError(_.LOGIN_ERROR)

        userdata.set('ob_session', self._session.cookies['OB-SESSION'])

        if settings.getBool('save_password', False):
            userdata.set('pswd', password)
        else:
            userdata.set('ob_tgt', self._session.cookies['OB-TGT'])

        self.set_authentication()

        return r.json()['data']['ticket']

    def _set_ob_token(self):
        password = userdata.get('pswd')

        if password:
            ticket = self.login(userdata.get('username'), password)
        else:
            resp = self._session.post(
                'https://auth.tab.co.nz/identity-service/api/v1/assertion/by-token',
                cookies={'OB-TGT': userdata.get('ob_tgt')})

            if resp.status_code == 403:
                raise APIError(_.GEO_ERROR)
            elif resp.status_code != 201:
                raise APIError(_.AUTH_ERROR)
            else:
                ticket = resp.json()['data']['ticket']

        resp = self._session.get(
            'https://api.tab.co.nz/account-service/api/v1/account/header',
            headers={'Authentication': ticket})

        if 'OB-TOKEN' not in self._session.cookies:
            raise APIError(_.AUTH_ERROR)

        userdata.set('ob_session', self._session.cookies['OB-SESSION'])

    def access(self, type, id):
        self._set_ob_token()

        url = 'https://api.tab.co.nz/sports-service/api/v1/streams/access/{}/{}'.format(
            type, id)
        r = self._session.post(url)

        if r.status_code == 403:
            raise APIError(_.GEO_ERROR)

        data = r.json()

        if data['errors']:
            raise APIError(data['errors'][0]['text'])

        return data['data'][0]['streams'][0]['accessInfo']['contentUrl']

    def live_events(self):
        r = self._session.get(
            'https://content.tab.co.nz/content-service/api/v1/q/event-list?liveNow=true&hasLiveStream=true'
        )

        if r.status_code == 403:
            raise APIError(_.GEO_ERROR)

        return r.json()['data']['events']

    def logout(self):
        userdata.delete('ob_session')
        userdata.delete('ob_tgt')
        self.new_session()
コード例 #16
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(headers=HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        access_token = userdata.get('access_token')
        if not access_token:
            return

        self._session.headers.update({'authorization': access_token})
        self.logged_in = True

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires', 0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')

        data = self._session.get(API_URL+'identity/refresh/{}'.format(userdata.get('refresh_token'))).json()
        self._process_token(data)

    def _process_token(self, data):
        if 'error' in data:
            raise APIError(data['error'])

        userdata.set('access_token', data['authorizationToken'])
        if 'refreshToken' in data:
            userdata.set('refresh_token', data['refreshToken'])

        token_data = jwt_data(data['authorizationToken'])
        userdata.set('expires', int(time()+(token_data['exp']-token_data['iat']-30)))

        self._set_authentication()

    def login(self, username, password):
        self.logout()

        deviceid = str(uuid.uuid3(uuid.UUID(UUID_NAMESPACE), str(username.lower().strip())))

        params = {
            'site': 'live-rugby',
            'deviceId': 'browser-{}'.format(deviceid),
        }

        payload = {
            'email': username,
            'password': password,
        }

        data = self._session.post(API_URL+'identity/signin', json=payload, params=params).json()
        self._process_token(data)

    @mem_cache.cached(60*5)
    def page(self, slug):
        params = {
            'site': 'live-rugby',
            'path': slug,
            'includeContent': 'true',
            #'moduleOffset': 0,
            #'moduleLimit': 8,
            'languageCode': 'default',
            'countryCode': 'NZ',
        }

        return self._session.get(CACHED_API_URL+'content/pages', params=params).json()

    @mem_cache.cached(60*5)
    def search(self, query):
        params = {
            'site': 'live-rugby',
            'searchTerm': query,
            'types': 'VIDEO,SERIES,EVENT',#,ARTICLE,PERSON,BUNDLE',
            'languageCode': 'default',
        }

        return self._session.get(CACHED_API_URL+'search/v1', params=params).json()

    def play(self, id):
        self._refresh_token()

        params = {
            'id': id,
            'deviceType': 'web_browser',
            'contentConsumption': 'web',
        }

        data = self._session.get(API_URL+'entitlement/video/status', params=params).json()

        if not data['success']:
            raise APIError(data['errorMessage'])

        return data['video']['streamingInfo']['videoAssets']['hls']

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        self.new_session()
コード例 #17
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS, timeout=30)
        self._set_authentication(userdata.get('access_token'))

    def _set_authentication(self, access_token):
        if not access_token:
            return

        self._session.headers.update(
            {'Authorization': 'Bearer {}'.format(access_token)})
        self.logged_in = True

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires', 0) > time():
            return

        payload = {
            'refresh_token': userdata.get('refresh_token'),
            'grant_type': 'refresh_token',
            'scope': 'browse video_playback device',
        }

        self._oauth_token(payload, {'Authorization': None})

    def url(self, name, path=''):
        config = self._client_config()

        if name == 'tokens':
            try:
                if config['endpoints']['getTokens']['domain'] == 'userGateway':
                    return 'https://gateway{userSubdomain}.{domain}.hbo.com'.format(
                        **config['routeKeys']
                    ) + config['endpoints']['getTokens']['path']
            except KeyError:
                pass

            return 'https://oauth{globalUserSubdomain}.{domain}.hbo.com/auth/tokens'.format(
                **config['routeKeys'])

        elif name == 'gateway':
            return 'https://gateway{userSubdomain}.{domain}.hbo.com'.format(
                **config['routeKeys']) + path

        elif name == 'comet':
            return 'https://comet{contentSubdomain}.{domain}.hbo.com'.format(
                **config['routeKeys']) + path

        elif name == 'artist':
            return 'https://artist.{cdnDomain}.hbo.com'.format(
                **config['routeKeys']) + path

        else:
            return None

    def _oauth_token(self, payload, headers=None):
        data = self._session.post(self.url('tokens'),
                                  data=payload,
                                  headers=headers).json()
        self._check_errors(data)

        self._set_authentication(data['access_token'])
        userdata.set('access_token', data['access_token'])
        userdata.set('expires', int(time() + data['expires_in'] - 15))

        if 'refresh_token' in data:
            userdata.set('refresh_token', data['refresh_token'])

    def _device_serial(self):
        def _format_id(string):
            try:
                mac_address = uuid.getnode()
                if mac_address != uuid.getnode():
                    mac_address = ''
            except:
                mac_address = ''

            system, arch = get_system_arch()
            return str(
                string.format(mac_address=mac_address, system=system).strip())

        return str(
            uuid.uuid3(uuid.UUID(UUID_NAMESPACE),
                       _format_id(settings.get('device_id'))))

    def _guest_login(self):
        serial = self._device_serial()

        payload = {
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_ID,
            'scope': 'browse video_playback_free account_registration',
            'grant_type': 'client_credentials',
            'deviceSerialNumber': serial,
            'clientDeviceData': {
                'paymentProviderCode': 'google-play'
            }
        }

        data = self._session.post(GUEST_AUTH,
                                  json=payload,
                                  headers={
                                      'Authorization': None
                                  }).json()
        if 'code' in data and data['code'] == 'invalid_credentials':
            raise APIError(_.BLOCKED_IP)

        self._check_errors(data)
        self._set_authentication(data['access_token'])
        return serial

    @mem_cache.cached(60 * 30)
    def _client_config(self):
        serial = self._guest_login()

        payload = {
            'contract': 'hadron:1.1.2.0',
            'preferredLanguages': ['en-us'],
        }

        data = self._session.post(CONFIG_URL, json=payload).json()
        self._set_authentication(userdata.get('access_token'))

        self._check_errors(data)
        if data['features']['currentRegionOutOfFootprint']['enabled']:
            raise APIError(_.GEO_LOCKED)

        return data

    def device_code(self):
        self.logout()

        serial = self._guest_login()

        payload = {
            'model': DEVICE_MODEL,
            'serialNumber': serial,
            'userIntent': 'login',
        }

        data = self._session.post(self.url('comet', '/devices/activationCode'),
                                  json=payload).json()
        self._check_errors(data)

        return serial, data['activationCode']

    def set_profile(self, profile_id):
        self._refresh_token()

        payload = {
            'grant_type': 'user_refresh_profile',
            'profile_id': profile_id,
            'refresh_token': userdata.get('refresh_token'),
        }

        self._oauth_token(payload)

    def device_login(self, serial, code):
        payload = {
            'model': DEVICE_MODEL,
            'code': code,
            'serialNumber': serial,
            'grant_type': 'user_activation_code',
            'scope':
            'browse video_playback device elevated_account_management',
        }

        try:
            self._oauth_token(payload)
        except NotPairedError:
            return False
        else:
            return True

    def _check_errors(self, data, error=_.API_ERROR):
        if not data:
            raise APIError(_.BLOCKED_IP)

        if 'code' in data:
            if data['code'] == 'not_paired':
                raise NotPairedError()
            else:
                error_msg = data.get('message') or data.get('code')
                raise APIError(_(error, msg=error_msg))

    def profiles(self):
        self._refresh_token()
        payload = [
            {
                'id': 'urn:hbo:profiles:mine'
            },
        ]
        return self._session.post(self.url('comet', '/content'),
                                  json=payload).json()[0]['body']['profiles']

    def _age_category(self):
        month, year = userdata.get('profile', {}).get('birth', [0, 0])

        i = arrow.now()
        n = i.year - year
        if i.month < month:
            n -= 1
        age = max(0, n)

        group = AGE_CATS[0][1]
        for cat in AGE_CATS:
            if age >= cat[0]:
                group = cat[1]

        return group

    def content(self, slug, tab=None):
        self._refresh_token()

        headwaiter = ''
        for key in sorted(self._client_config()['payloadValues']):
            headwaiter += '{}:{},'.format(
                key,
                self._client_config()['payloadValues'][key])

        headers = {
            'x-hbo-headwaiter': headwaiter.rstrip(','),
        }

        params = {
            'device-code': DEVICE_MODEL,
            'product-code': 'hboMax',
            'api-version': 'v9.0',
            'brand': 'HBO MAX',
            'country-code': 'US',
            'profile-type': 'default',
            # 'territory': 'MEXICO',
            'signed-in': True,
        }

        if userdata.get('profile', {}).get('child', 0):
            params.update({
                'profile-type': 'child',
                'age-category': self._age_category(),
            })

        data = self._session.get(self.url('comet',
                                          '/express-content/{}'.format(slug)),
                                 params=params,
                                 headers=headers).json()
        self._check_errors(data)

        _data = {}
        for row in data:
            _data[row['id']] = row['body']

        return self._process(_data, tab or slug)

    def _process(self, data, slug):
        main = data[slug]
        if len(data) == 1 and 'message' in main:
            raise APIError(_(_.API_ERROR, msg=main['message']))

        def process(element):
            element['items'] = []
            element['tabs'] = []
            element['edits'] = []
            element['seasons'] = []
            element['episodes'] = []
            element['target'] = None

            for key in element.get('references', {}):
                if key in ('items', 'tabs', 'edits', 'seasons', 'episodes'):
                    for id in element['references'][key]:
                        if id == '$dataBinding':
                            continue

                        item = {'id': id}
                        if id in data:
                            item.update(data[id])
                        process(item)
                        element[key].append(item)
                else:
                    element[key] = element['references'][key]

            element.pop('references', None)

        process(main)
        return main

    def search(self, query):
        self._refresh_token()

        payload = [{
            'id': 'urn:hbo:flexisearch:{}'.format(query),
        }]

        data = self._session.post(self.url('comet', '/content'),
                                  json=payload).json()
        self._check_errors(data)

        keys = {}
        key = None
        for row in data:
            keys[row['id']] = row['body']
            if row['id'].startswith(
                    'urn:hbo:grid:search') and row['id'].endswith('-all'):
                key = row['id']

        if not key:
            return None

        return self._process(keys, key)

    def play(self, slug):
        self._refresh_token()

        content_data = self.content(slug)
        if not content_data['edits']:
            raise APIError(_.NO_VIDEO_FOUND)

        selected = content_data['edits'][0]
        for edit in content_data['edits']:
            for row in edit.get('textTracks', []):
                if row.get('language') == 'en-US':
                    selected = edit
                    break

        payload = [{
            'id': selected['video'],
            'headers': {
                'x-hbo-preferred-blends': 'DASH_WDV,HSS_PR',
                'x-hbo-video-mlp': True,
            }
        }]

        data = self._session.post(self.url('comet', '/content'),
                                  json=payload).json()[0]['body']

        for row in data.get('manifests', []):
            if row['type'] == 'urn:video:main':
                return row, content_data

        raise APIError(_.NO_VIDEO_FOUND)

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('expires')
        userdata.delete('refresh_token')
        userdata.delete('config')
        mem_cache.empty()
        self.new_session()
コード例 #18
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self, config):
        self.logged_in = False
        self._config = config
        self._session = Session(base_url=self._config.api_url, headers=HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        auth_cookies = userdata.get('auth_cookies')
        if not auth_cookies:
            return

        self._session.cookies.update(auth_cookies)
        self.logged_in = True

    def _device_id(self):
        device_id = userdata.get('device_id')
        if device_id:
            return device_id

        device_id = settings.get('device_id')

        try:
            mac_address = uuid.getnode()
            if mac_address != uuid.getnode():
                mac_address = ''
        except:
            mac_address = ''

        system, arch = get_system_arch()
        device_id = device_id.format(username=userdata.get('username'),
                                     mac_address=mac_address,
                                     system=system).strip()

        if not device_id:
            device_id = uuid.uuid4()

        log.debug('Raw device id: {}'.format(device_id))
        device_id = hash_6(device_id, length=16)
        log.debug('Hashed device id: {}'.format(device_id))

        userdata.set('device_id', device_id)
        return device_id

    def device_code(self):
        payload = {'deviceId': self._device_id()}
        return self._session.post('/v2.0/androidtv/ott/auth/code.json',
                                  params=self._params(),
                                  data=payload).json()

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires',
                                      0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')
        self._set_profile(userdata.get('profile_id'))

    def login(self, username, password):
        payload = {
            'j_password': password,
            'j_username': username,
            'deviceId': self._device_id(),
        }
        resp = self._session.post('/v2.0/androidtv/auth/login.json',
                                  params=self._params(),
                                  data=payload)
        data = resp.json()

        if not data['success']:
            raise APIError(data.get('message'))

        self._save_auth(resp.cookies)
        self._set_profile_data(self.user()['activeProfile'])

    def device_login(self, code, device_token):
        payload = {
            'activationCode': code,
            'deviceToken': device_token,
            'deviceId': self._device_id(),
        }

        resp = self._session.post('/v2.0/androidtv/ott/auth/status.json',
                                  params=self._params(),
                                  data=payload)
        data = resp.json()

        if data.get('regenerateCode'):
            return -1
        elif not data['success']:
            return False

        self._save_auth(resp.cookies)
        self._set_profile_data(self.user()['activeProfile'])

        return True

    def _save_auth(self, cookies):
        expires = None
        for cookie in cookies:
            if expires is None or cookie.expires < expires:
                expires = cookie.expires

        userdata.set('expires', min(expires, int(time() + 86400)))
        userdata.set('auth_cookies', cookies.get_dict())

        self._set_authentication()

    def set_profile(self, profile_id):
        self._set_profile(profile_id)
        mem_cache.empty()

    def _set_profile(self, profile_id):
        resp = self._session.post(
            '/v2.0/androidtv/user/account/profile/switch/{}.json'.format(
                profile_id),
            params=self._params())
        data = resp.json()

        if not data['success']:
            raise APIError('Failed to set profile: {}'.format(profile_id))

        self._set_profile_data(data['profile'])
        self._save_auth(resp.cookies)

    def _set_profile_data(self, profile):
        userdata.set('profile_id', profile['id'])
        userdata.set('profile_name', profile['name'])
        userdata.set('profile_img', profile['profilePicPath'])

    def _params(self, params=None):
        _params = {'locale': 'en-us', 'at': self._config.tv_token}
        #_params = {'locale': 'en-us', 'at': self._at_token(secret), 'LOCATEMEIN': 'us'}
        if params:
            _params.update(params)
        return _params

    # def _at_token(self, secret):
    #     payload = '{}|{}'.format(int(time())*1000, self._config.tv_secret)

    #     try:
    #         #python3
    #         key = bytes.fromhex(self._config.aes_key)
    #     except AttributeError:
    #         #python2
    #         key = str(bytearray.fromhex(self._config.aes_key))

    #     iv = os.urandom(16)
    #     encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))

    #     ciphertext = encrypter.feed(payload)
    #     ciphertext += encrypter.feed()
    #     ciphertext = b'\x00\x10' + iv + ciphertext

    #     return b64encode(ciphertext).decode('utf8')

    # def app_config(self):
    #     selected_region = None

    #     for region in REGIONS:
    #         params = {'locale': 'en-us', 'at': region['tv_token']}
    #         resp = self._session.get('{}/apps-api/v2.0/androidtv/app/status.json'.format(region['base_url']), params=params)
    #         if resp.ok:
    #             data = resp.json()
    #             if data['appVersion']['availableInRegion']:
    #                 selected_region = [region, data]
    #                 break

    #     if not selected_region:
    #         raise Exception('Unable to find a region for your location')

    @mem_cache.cached(60 * 10)
    def carousel(self, url, params=None):
        params = params or {}
        params.update({
            '_clientRegion': self._config.country_code,
            'start': 0,
        })

        for key in params:
            if type(params[key]) is list:
                params[key] = ','.join(params[key])

        return self._session.get(
            '/v3.0/androidphone{}'.format(url),
            params=self._params(params)).json()['carousel']

    @mem_cache.cached(60 * 10)
    def featured(self):
        params = {
            'minProximity': 1,
            'minCarouselItems': 5,
            'maxCarouselItems': 20,
            'rows': 15,
        }
        return self._session.get('/v3.0/androidphone/home/configurator.json',
                                 params=self._params(params)).json()['config']

    @mem_cache.cached(60 * 10)
    def trending_movies(self):
        return self._session.get('/v3.0/androidphone/movies/trending.json',
                                 params=self._params()).json()

    @mem_cache.cached(60 * 10)
    def movies(self, genre=None, num_results=12, page=1):
        params = {
            'includeTrailerInfo': False,
            'packageCode': 'CBS_ALL_ACCESS_AD_FREE_PACKAGE',
            'platformType': 'androidphone',
            'start': (page - 1) * num_results,
            'rows': num_results,
            'includeContentInfo': True,
        }

        if genre:
            params['genre'] = genre

        return self._session.get('/v3.0/androidphone/movies.json',
                                 params=self._params(params)).json()

    @mem_cache.cached(60 * 10)
    def movie_genres(self):
        return self._session.get('/v3.0/androidphone/movies/genre.json',
                                 params=self._params()).json()['genres']

    @mem_cache.cached(60 * 10)
    def show_groups(self):
        params = {'includeAllShowGroups': 'true'}
        return self._session.get(
            '/v2.0/androidphone/shows/groups.json',
            params=self._params(params)).json()['showGroups']

    @mem_cache.cached(60 * 10)
    def show_group(self, group_id):
        params = {'includeAllShowGroups': 'true'}
        return self._session.get(
            '/v2.0/androidphone/shows/group/{}.json'.format(group_id),
            params=self._params(params)).json()['group']

    @mem_cache.cached(60 * 10)
    def show(self, show_id):
        return self._session.get(
            '/v3.0/androidphone/shows/{}.json'.format(show_id),
            params=self._params()).json()

    @mem_cache.cached(60 * 10)
    def episodes(self, show_id, season):
        params = {
            'platformType': 'apps',
            'rows': 1,
            'begin': 0,
        }

        section_id = self._session.get(
            '/v2.0/androidphone/shows/{}/videos/config/{}.json'.format(
                show_id, self._config.episodes_section),
            params=self._params(
                params)).json()['section_display_seasons'][0]['sectionId']

        params = {
            'rows': 999,
            'params': 'seasonNum={}'.format(season),
            'begin': 0,
            'seasonNum': season,
        }

        return self._session.get(
            '/v2.0/androidphone/videos/section/{}.json'.format(section_id),
            params=self._params(params)).json()['sectionItems']['itemList']

    @mem_cache.cached(60 * 10)
    def seasons(self, show_id):
        return self._session.get(
            '/v3.0/androidphone/shows/{}/video/season/availability.json'.
            format(show_id),
            params=self._params()).json()['video_available_season']['itemList']

    @mem_cache.cached(60 * 10)
    def search(self, query):
        params = {
            'term': query,
            'termCount': 50,
            'showCanVids': 'true',
        }
        return self._session.get(
            '/v3.0/androidphone/contentsearch/search.json',
            params=self._params(params)).json()['terms']

    def user(self):
        self._refresh_token()
        return self._session.get('/v3.0/androidtv/login/status.json',
                                 params=self._params()).json()

    def play(self, video_id):
        self._refresh_token()

        def get_data(device):
            video_data = self._session.get(
                '/v2.0/{}/video/cid/{}.json'.format(device, video_id),
                params=self._params()).json()['itemList'][0]

            if 'pid' not in video_data:
                raise APIError('Check your subscription is valid')

            params = {
                #'formats': 'mpeg-dash',
                'Tracking': 'true',
                'format': 'SMIL',
                #'sig': '0060cbe3920bcb86969e8c733a9cdcdb203d6e57beae30781c706f63',
            }

            resp = self._session.get(LINK_PLATFORM_URL.format(
                account=video_data['cmsAccountId'], pid=video_data['pid']),
                                     params=params)

            root = ET.fromstring(resp.text)
            strip_namespaces(root)

            if root.find("./body/seq/ref/param[@name='exception']") != None:
                error_msg = root.find("./body/seq/ref").attrib.get('abstract')
                raise APIError(_(error_msg))

            ref = root.find(".//video")
            return ref.attrib['src'], video_data

        device = 'androidtv'
        url, video_data = get_data(device)
        if 'cenc_fmp4_dash' in url and not settings.getBool(
                'wv_secure', False):
            try:
                split = url.split('/')
                year = int(split[4])
                month = int(split[5])
            except:
                year = 2021
                month = 6

            if year >= 2021 and month >= 6:
                device = 'androidphone'
                url, video_data = get_data(device)

        params = {'contentId': video_id}
        data = self._session.get(
            '/v3.0/{}/irdeto-control/session-token.json'.format(device),
            params=self._params(params)).json()

        return url, data['url'], data['ls_session'], video_data

    def _ip(self):
        return self._session.get(self._config.ip_url,
                                 params=self._params()).json()['ip']

    def live_channels(self):
        if not self._config.has_live_tv:
            return []

        self._refresh_token()
        dma = self.dma()

        params = {
            'start': 0,
            'rows': 30,
            '_clientRegion': self._config.country_code,
            'dma': dma['dma'] if dma else None,
            'showListing': 'true',
        }

        data = self._session.get('/v3.0/androidphone/live/channels.json',
                                 params=self._params(params)).json()

        channels = []
        for row in data['channels']:
            if row['dma'] and dma:
                row['dma'] = dma['tokenDetails']

            channels.append(row)

        return sorted(channels, key=lambda x: x['displayOrder'])

    def epg(self, channel, page=1, rows=25):
        params = {
            'start': (page - 1) * rows,
            'rows': rows,
            '_clientRegion': self._config.country_code,
            'showListing': 'true',
        }

        return self._session.get(
            '/v3.0/androidphone/live/channels/{slug}/listings.json'.format(
                slug=channel),
            params=self._params(params)).json()['listing']

    def dma(self):
        self._refresh_token()

        ip = settings.get('region_ip')
        if not ip or ip == '0.0.0.0':
            ip = self._ip()

        params = {
            'ipaddress': ip,
            'dtp': 8,  #controls quality
            'syncBackVersion': '3.0',
            'mvpdId': 'AllAccess',
            'is60FPS': 'true',
            'did': self._device_id(),
        }

        data = self._session.get('/v3.0/androidphone/dma.json',
                                 params=self._params(params)).json()
        if not data['success']:
            log.warning(
                'Failed to get local CBS channel for IP address ({}). Server message: {}'
                .format(ip, data.get('message')))
            return None

        return data['dmas'][0]

    def logout(self):
        userdata.delete('profile_img')
        userdata.delete('profile_name')
        userdata.delete('profile_id')
        userdata.delete('auth_cookies')
        userdata.delete('device_id')
        mem_cache.empty()
        self.new_session(self._config)
コード例 #19
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('access_token')
        if not token:
            return

        self._session.headers.update(
            {'authorization': 'Bearer {}'.format(token)})
        self._session.headers.update(
            {'x-user-profile': userdata.get('profile_id')})
        self.logged_in = True

    def _query_request(self, query, variables=None, **kwargs):
        self._refresh_token()

        data = {
            'query': ' '.join(query.split()),
            'variables': variables or {},
        }

        data = self._session.post(GRAPH_URL, json=data, **kwargs).json()
        if 'errors' in data:
            raise APIError(data['errors'][0]['message'])

        return data

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires',
                                      0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')

        payload = {
            'client_id': CLIENT_ID,
            'refresh_token': userdata.get('refresh_token'),
            'grant_type': 'refresh_token',
            'scope': 'openid profile email offline_access',
        }

        self._oauth_token(payload)

    def _oauth_token(self, payload):
        token_data = self._session.post('https://login.sky.co.nz/oauth/token',
                                        json=payload,
                                        error_msg=_.TOKEN_ERROR).json()

        if 'error' in token_data:
            self.logout()
            error = _.REFRESH_TOKEN_ERROR if token_data.get(
                'grant_type') == 'refresh_token' else _.LOGIN_ERROR
            raise APIError(_(error, msg=token_data.get('error_description')))

        userdata.set('access_token', token_data['access_token'])
        userdata.set('expires', int(time() + token_data['expires_in'] - 15))
        if 'refresh_token' in token_data:
            userdata.set('refresh_token', token_data['refresh_token'])

        #Force 1st profile
        data = jwt_data(token_data['access_token'])
        profile_id = data['https://skygo.co.nz/profiles'][0]['id']
        userdata.set('profile_id', profile_id)
        ####

        self._set_authentication()

    @mem_cache.cached(60 * 5)
    def home(self):
        return self._query_request(queries.HOME)['data']['section']['home']

    @mem_cache.cached(60 * 5)
    def group(self, id):
        return self._query_request(queries.GROUP,
                                   variables={'railId': id})['data']['group']

    @mem_cache.cached(60 * 5)
    def channels(self):
        ids = []
        channels = []

        variables = {
            'from':
            arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.000') + 'Z',
            'to':
            arrow.utcnow().shift(hours=6).format('YYYY-MM-DDTHH:mm:ss.000') +
            'Z',
        }

        groups = self._query_request(
            queries.CHANNELS,
            variables=variables)['data']['linearChannelGroups']
        for group in groups:
            for row in group.get('channels', []):
                if row['__typename'] == 'LinearChannel' and row[
                        'id'] not in ids:
                    ids.append(row['id'])
                    channels.append(row)

        return sorted(channels, key=lambda x: x['number'])

    @mem_cache.cached(60 * 5)
    def search(self, query):
        return self._query_request(queries.SEARCH,
                                   variables={'term': query
                                              })['data']['search']['results']

    @mem_cache.cached(60 * 5)
    def show(self, id):
        return self._query_request(queries.SHOW,
                                   variables={'brandId': id})['data']['show']

    @mem_cache.cached(60 * 5)
    def collection(self,
                   collection_id,
                   filters="",
                   limit=36,
                   after=None,
                   sort="ALPHABETICAL",
                   tv_upcoming=False):
        context = 'VOD,CATCHUP'
        if tv_upcoming:
            context += ',LINEAR'

        filters = 'namedFilters: "{}"'.format(filters) if filters else ''
        limit = 'first: {}'.format(limit) if limit else ''
        sort = 'sort: {}'.format(sort) if sort else ''
        after = 'after: "{}"'.format(after) if after else ''

        query = queries.COLLECTION % (after, limit, context, filters, sort)

        return self._query_request(query,
                                   variables={'collectionId': collection_id
                                              })['data']['collection']

    @mem_cache.cached(60 * 10)
    def vod_categories(self):
        return self._query_request(
            queries.VOD_CATEGORIES,
            variables={'excludeViewingContexts': [
                'DOWNLOAD',
            ]})['data']['section']['home']['categories']

    def play(self, asset_id, is_linear=False):
        variables = {
            'deviceId': '',
            'assetId': asset_id,
            'channelId': asset_id,
            # 'playbackDevice': {
            #     'platform': 'Windows',
            #     'osVersion': '10',
            #     'drmType': 'WIDEVINE',
            #     'drmLevel': 'SW_SECURE_DECODE'
            # }
        }

        if is_linear:
            data = self._query_request(
                queries.LINEAR_START, variables)['data']['startLinearPlayback']
        else:
            data = self._query_request(queries.VOD_START,
                                       variables)['data']['startVodPlayback']

        if data['__typename'] == 'SubscriptionNeeded':
            raise APIError(
                _(_.SUBSCRIPTION_REQUIRED,
                  subscription=data['subscriptions'][0]['title']))
        elif data['__typename'] == 'Geoblocked':
            raise APIError(_.GEO_ERROR)
        elif data['__typename'] == 'ConcurrentStreamsExceeded':
            raise APIError(_.CONCURRENT_STREAMS)
        elif data['__typename'] not in ('LinearPlaybackSources',
                                        'VodPlaybackSources'):
            raise APIError('Unkown error: {}'.format(data['__typename']))

        try:
            if is_linear:
                self._query_request(queries.LINEAR_STOP,
                                    variables)['data']['stopLinearPlayback']
            else:
                self._query_request(queries.VOD_STOP,
                                    variables)['data']['stopVodPlayback']
        except:
            log.debug('Stop Linear / VOD Failed')

        return data['playbackSource']['streamUri'], data['playbackSource'][
            'drmLicense']['licenseUri']

    def login(self, username, password):
        self.logout()

        params = {
            'client_id': CLIENT_ID,
            'audience': 'https://api.sky.co.nz',
            'redirect_uri': 'https://www.skygo.co.nz',
            'connection': 'Sky-Internal-Connection',
            'scope': 'openid profile email offline_access',
            'response_type': 'code',
        }

        resp = self._session.get('https://login.sky.co.nz/authorize',
                                 params=params,
                                 allow_redirects=False)
        parsed = urlparse(resp.headers['location'])
        payload = dict(parse_qsl(parsed.query))
        payload.update({
            'username': username,
            'password': password,
            'tenant': 'skynz-prod',
            'client_id': CLIENT_ID,
            'client': None,
        })

        resp = self._session.post(
            'https://login.sky.co.nz/usernamepassword/login', json=payload)
        if not resp.ok:
            data = resp.json()
            raise APIError(_(_.LOGIN_ERROR, msg=data['message']))

        soup = BeautifulSoup(resp.text, 'html.parser')

        payload = {}
        for e in soup.find_all('input'):
            if 'name' in e.attrs:
                payload[e.attrs['name']] = e.attrs.get('value')

        resp = self._session.post('https://login.sky.co.nz/login/callback',
                                  data=payload)
        parsed = urlparse(resp.url)
        data = dict(parse_qsl(parsed.query))

        payload = {
            'code': data['code'],
            'client_id': CLIENT_ID,
            'grant_type': 'authorization_code',
            'redirect_uri': 'https://www.skygo.co.nz'
        }

        self._oauth_token(payload)

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        userdata.delete('profile_id')
        self.new_session()
コード例 #20
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS, base_url=API_URL)
        self._set_authentication()

    def _set_authentication(self):
        access_token = userdata.get('token')
        if not access_token:
            return

        self._session.headers.update({'X-Auth-Token': access_token})
        self.logged_in = True

    def login(self, username, password):
        self.logout()

        payload = {
            'email': username,
            'password': password,
            'platform': 'google',
        }

        data = self._session.post('/v1/login/', data=payload).json()
        if 'error' in data:
            try:
                msg = data['error']['message']['base'][0]
            except:
                msg = ''

            raise APIError(_(_.LOGIN_ERROR, msg=msg))

        userdata.set('token', data['message']['auth_token'])
        self._set_authentication()

    @mem_cache.cached(CACHE_TIME)
    def categories(self):
        return self._session.get('/v1/categories').json()['data']

    def series(self, id):
        return self._session.get('/v2/series/{}'.format(id)).json()['data']

    @mem_cache.cached(CACHE_TIME)
    def featured(self):
        return self._session.get('/v2/featured').json()

    def sections(self, id, page=1):
        params = {
            'cache': False,
            'collections': True,
            'media_limit': 36,
            'page': page,
        }

        return self._session.get(
            '/v1/sections/{}/mobile'.format(id)).json()['data']['groups']

    def collection(self, id, flattened=False):
        params = {
            'flattened': flattened,
        }

        return self._session.get('/v2/collections/{}'.format(id),
                                 params=params).json()['data']

    @mem_cache.cached(CACHE_TIME)
    def collections(self, flattened=False, excludeMedia=True, page=1):
        params = {
            'flattened': flattened,
            'excludeMedia': excludeMedia,
            'limit': 20,
            'page': page,
        }

        return self._session.get('/v2/collections', params=params).json()

    def filter_media(self, filterby, term=None, collections=True, page=1):
        params = {
            'filterBy': filterby,
            'collections': collections,
            'limit': 20,
            'page': page,
        }

        if term:
            params['term'] = term

        return self._session.get('/v1/media', params=params).json()

    def set_user_media(self, id, **kwargs):
        params = {
            'media_id': id,
        }

        params.update(kwargs)

        data = self._session.post('/v1/user_media', params=params,
                                  json={}).json()
        return data['status'] == 'success'

    def set_user_collection(self, id, **kwargs):
        params = {
            'collection_id': id,
        }

        params.update(kwargs)

        data = self._session.post('/v1/user_collection',
                                  params=params,
                                  json={}).json()
        return data['status'] == 'success'

    def media(self, id):
        params = {
            #  'showEncodings': 'Android', #limits to 1080p
            'encodingsNew': 'true',
            'encodingsFormat': 'mpd',
        }

        return self._session.get('/v1/media/{}'.format(id),
                                 params=params).json()['data']

    def logout(self):
        userdata.delete('token')
        mem_cache.empty()
        self.new_session()
コード例 #21
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._auth_headers = {}
        self._config = {}

        self._session = Session(headers=HEADERS)
        self._session.after_request = self._check_response

        self._set_authentication()

    @mem_cache.cached(60*60, key='config')
    def get_config(self):
        try:
            return self._session.get(APP_DATA_URL).json()['settings']
        except Exception as e:
            raise APIError('Failed to download app settings')

    @mem_cache.cached(60*60)
    def _app_version(self):
        return self._session.get(APP_VERSION_URL).text.strip()

    def _set_authentication(self):
        device_token = userdata.get('device_token')
        auth_token = userdata.get('auth_token')

        if not device_token or not auth_token:
            return

        self._auth_headers = {
            'X-AN-WebService-CustomerAuthToken': auth_token,
            'X-AN-WebService-DeviceAuthToken': device_token,
        }

        self.logged_in = True

    def _create_session(self, force=False):
        self._config = self.get_config()
        platform = self._config['alpha_networks_dash'][REGION]

        self._session._base_url = platform['platform_url']+'{}'
        self._session.headers.update({
            'X-AN-WebService-IdentityKey': platform['hss_key'], # hss_key, hls_key, chromecast_key
        })

        if not self.logged_in or (not force and time.time() < userdata.get('token_expires')):
            return

        login_type = settings.getEnum('login_type', choices=LOGIN_TYPE, default=LOGIN_MULTI_IP)

        if login_type == LOGIN_MULTI_IP:
            # Single device, changing IP address (same as app)
            data = self._session.post('proxy/loginDevice', headers=self._auth_headers).json()

        elif login_type == LOGIN_MULTI_DEVICE:
            # Multiple device, static IP address
            data = self._session.post('proxy/casAvailableDevice', headers=self._auth_headers).json()

        elif login_type == LOGIN_PASSWORD:
            # Supports multiple devices and multiple IP address as long (as others also using password)
            data = {
                'password': userdata.get('password'),
                'deviceId': userdata.get('device_id'),
                'email': userdata.get('username'),
            }

            data = self._session.post('proxy/login', data=data).json()

        if data['error']:
            error = _(_.TOKEN_ERROR, msg=data['error']['message'])

            if data['error']['code'] == -1:
                self.logout()
                gui.refresh()

                if login_type == LOGIN_MULTI_IP:
                    error = _.LOGIN_MULTI_IP_ERROR
                elif login_type == LOGIN_MULTI_DEVICE:
                    error = _.LOGIN_MULTI_DEVICE_ERROR

            raise APIError(error)

        if 'deviceAuthToken' in data['result']:
            userdata.set('device_token', data['result']['deviceAuthToken'])

        self._set_auth(data['result']['newAuthToken'])

    def _set_auth(self, auth_token):
        token_data = jwt_data(auth_token)
        userdata.set('auth_token', auth_token)
        userdata.set('token_expires', token_data['exp'] - 30)
        self._set_authentication()

    def _select_device(self, token):
        data = self._session.post('proxy/casAvailableDevice', headers={'X-AN-WebService-CustomerAuthToken': token}).json()
        devices = data['result'].get('device', [])

        while True:
            if devices:
                options = []
                values = []
                for row in devices:
                    options.append(_(_.DEVICE_LABEL, name=row['name'], last_login=arrow.get(row['lastLoginDate']).to('local').format('D MMMM YYYY')))
                    values.append(row)

                options.append(_.NEW_DEVICE)
                values.append('new')

                options.append(_.REMOVE_DEVICE)
                values.append('remove')

                index = gui.select(_.SELECT_DEVICE, options=options)
                if index == -1:
                    return

                selected = values[index]
            else:
                selected = 'new'

            if selected == 'new':
                device_name = gui.input(_.DEVICE_NAME).strip()

                if not device_name or not gui.yes_no(_(_.NEW_CONFIRM, device_name=device_name)):
                    if devices:
                        continue
                    else:
                        return

                return {
                    'uniqueDeviceId': hash_6('{}{}'.format(int(time.time()), device_name), length=16),
                    'name': device_name, 
                    'type': 'Android',
                }

            elif selected == 'remove':
                options = []
                values = []

                for row in devices:
                    options.append(row['name'])
                    values.append(row)

                to_remove = None
                while not to_remove:
                    index = gui.select(_.SELECT_REMOVE_DEVICE, options=options)
                    if index == -1:
                        break

                    if gui.yes_no(_(_.REMOVE_CONFIRM, device_name=values[index]['name'])):
                        to_remove = values[index]

                if not to_remove:
                    continue

                data = {
                    'casDeviceId':  to_remove['uniqueDeviceId'],
                }

                data = self._session.post('proxy/casRemoveDevice', data=data, headers={'X-AN-WebService-CustomerAuthToken': token}).json()
                if data['error']:
                    gui.error(data['error']['message'])
                    continue

                return self._select_device(data['result']['newAuthToken'])
            else:
                return selected

    def _check_response(self, resp):
        if resp.ok:
            return

        if resp.status_code == 451:
            raise APIError(_.GEO_BLOCKED)
        else:
            raise APIError(_(_.HTTP_ERROR, code=resp.status_code))

    def login(self, username, password):
        self.logout()
        self._create_session()

        data = {
            'password': password,
            'email': username,
        }

        data = self._session.post('proxy/login', data=data).json()
        if data['error']:
            raise APIError(_(_.LOGIN_ERROR, msg=data['error']['message']))

        auth_token = data['result']['newAuthToken']

        while True:
            selected = self._select_device(auth_token)
            if not selected:
                return

            data = {
                'password': password,
                'deviceId': selected['uniqueDeviceId'],
                'email': username,
            }

            data = self._session.post('proxy/login', data=data).json()
            if data['error']:
                gui.error(data['error']['message'])
            else:
                break

        auth_token   = data['result']['newAuthToken']
        device_token = data['result']['deviceAuthToken']
        userdata.set('device_token', device_token)

        data = {
            'name': selected['name'],
            'casDeviceId': selected['uniqueDeviceId'],
            'type': selected['type'],
        }

        data = self._session.post('proxy/casAuth', data=data, headers={'X-AN-WebService-CustomerAuthToken': auth_token, 'X-AN-WebService-DeviceAuthToken': device_token}).json()
        if data['error']:
            raise APIError(data['error']['message'])

        self._set_auth(data['result']['newAuthToken'])
        mem_cache.delete('channels')

        if settings.getEnum('login_type', choices=LOGIN_TYPE, default=LOGIN_MULTI_IP) == LOGIN_PASSWORD:
            userdata.set('password', password)
            userdata.set('device_id', selected['uniqueDeviceId'])

    @mem_cache.cached(60*10, key='channels')
    def channels(self):
        self._create_session()

        _channels = {}
        for row in self._config['channels_mapping'][REGION]:
            if 'sso_hls_channel_id' in row:
                _channels[row['sso_hls_channel_id']] = row

        channels = []
        for row in self._session.post('proxy/listChannels').json()['result']['channels']:
            row.update(_channels.get(row['idChannel'], {}))
            channels.append(row)

        return sorted(channels, key=lambda x: x.get('sorting', x['localizeNumber']))

    def license_request(self, channel_id, challenge):
        self._create_session()

        app_version = self._app_version()
        params = {
            'wskey': self._session.headers['X-AN-WebService-IdentityKey'],
            'playerName': PLAYER_NAME,
            'playerVersion': app_version,
            'checksum': self._checksum(channel_id, app_version),
            'idChannel': channel_id,
        }

        resp = self._session.post('arkena/askLicenseWV', params=params, data=challenge, headers=self._auth_headers)
        if not resp.ok or '<LICENSE>' not in resp.text:
            raise APIError('Failed to get Widevine license')

        return resp.text.split('</LICENSE>')[0].split('<LICENSE>')[1]

    def play(self, channel_id):
        self._create_session(force=True)

        app_version = self._app_version()
        payload = {
            'idChannel': channel_id,
            'playerName': PLAYER_NAME,
            'playerVersion': app_version,
            'checksum': self._checksum(channel_id, app_version),
            'languageId': 'eng',
            'authToken': userdata.get('auth_token'),
        }

        data = self._session.post('proxy/channelStream', data=payload, headers=self._auth_headers).json()
        if data.get('error'):
            raise APIError(data['error']['message'])
        
        if 'newAuthToken' in data['result']:
            self._set_auth(data['result']['newAuthToken'])

        return data['result']['url']

    def epg(self, ids, start, end):
        self._create_session()

        data = {
            '$and': [
                {'id_channel': {'$in': ids}},
                {'startutc': {'$ge': start.timestamp}},
                {'startutc': {'$lt': end.timestamp}},
            ]
        }

        params = {
            'languageId': 'eng',
            'filter': json.dumps(data, separators=(',', ':')),
        }

        data = self._session.get('cms/epg/filtered', params=params).json()
        if data['error']:
            raise APIError(data['error']['message'])

        return data['result']['epg']

    def _checksum(self, channel_id, app_version):
        checksum = userdata.get('auth_token', '') + str(channel_id) + app_version
        checksum = hmac.new(SECRET_KEY.encode('utf8'), msg=checksum.encode('utf8'), digestmod=hashlib.sha256).digest()
        return base64.b64encode(checksum).decode('utf8')

    def logout(self):
        userdata.delete('password')
        userdata.delete('device_id')
        userdata.delete('device_token')
        userdata.delete('auth_token')
        userdata.delete('token_expires')

        mem_cache.delete('config')
        mem_cache.delete('channels')

        self.new_session()
コード例 #22
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self._session = Session(HEADERS, base_url=API_URL)
        self.logged_in = userdata.get('nllinktoken') != None

    def login(self, username, password):
        self.logout()

        data = {
            'username': username,
            'password': password,
            'cookielink': 'true',
            'format': 'json',
        }

        r = self._session.post('/secure/authenticate', data=data)

        data = r.json()
        nllinktoken = r.cookies.get_dict().pop('nllinktoken', None)
        code = data.get('code') or _.UNKNOWN_ERROR

        if code != 'loginsuccess' or not nllinktoken:
            if code == 'failedgeo':
                raise APIError(_.GEO_ERROR)
            else:
                raise APIError(_(_.LOGIN_ERROR, code=code))

        userdata.set('nllinktoken', nllinktoken)
        self.new_session()

    def deviceid(self):
        return hashlib.sha1(
            userdata.get('username').encode('utf8')).hexdigest()[:8]

    def play(self, media_id, media_type, start=None, duration=None):
        payload = {
            'id': media_id,
            'nt': 1,
            'type': media_type,
            'format': 'json',
            'drmtoken': True,
            'deviceid': self.deviceid(),
        }

        if start:
            payload['st'] = '{}000'.format(start)

        if duration:
            payload['dur'] = '{}000'.format(duration)

        login_cookies = {
            'nllinktoken': userdata.get('nllinktoken'),
            'UserName': userdata.get('username')
        }
        data = self._session.post('/service/publishpoint',
                                  data=payload,
                                  cookies=login_cookies).json()

        if 'path' not in data:
            code = data.get('code') or _.UNKNOWN_ERROR
            if code == 'failedgeo':
                raise APIError(_.GEO_ERROR)
            else:
                raise APIError(_(_.PLAYBACK_ERROR, code=code))

        return data

    def schedule(self, date):
        schedule = []

        data = self._session.get(
            EPG_URL.format(date=date.format('YYYY/MM/DD'))).json()
        for channel in data:
            for event in channel['items']:
                start = arrow.get(event['su'])
                stop = start.shift(seconds=event['ds'])
                duration = int(event['ds'])
                title = event.get('e')
                desc = event.get('ed')
                schedule.append({
                    'channel': channel['channelId'],
                    'start': start,
                    'stop': stop,
                    'duration': duration,
                    'title': title,
                    'desc': desc
                })

        return sorted(schedule, key=lambda channel: channel['start'])

    def logout(self):
        # self._session.post('service/logout', data={'format': 'json'})
        userdata.delete('nllinktoken')
        self.new_session()

    def highlights(self, page=1, pagesize=100):
        params = {
            'type': '0',
            'format': 'json',
            'ps': pagesize,
            'pn': page,
        }

        data = self._session.get('/service/search', params=params).json()

        try:
            code = data.get('code')
        except:
            code = None

        if code:
            if code == 'failedgeo':
                raise APIError(_.GEO_ERROR)
            else:
                raise APIError(
                    _(_.PLAYBACK_ERROR, code=code or _.UNKNOWN_ERROR))

        return data

    def channels(self):
        params = {
            'format': 'json',
        }

        data = self._session.get('/channels', params=params).json()

        try:
            code = data.get('code')
        except:
            code = None

        if code:
            if code == 'failedgeo':
                raise APIError(_.GEO_ERROR)
            else:
                raise APIError(
                    _(_.PLAYBACK_ERROR, code=code or _.UNKNOWN_ERROR))

        return data
コード例 #23
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def __init__(self):
        self.new_session()

    def new_session(self):
        self._logged_in = False
        self._session = Session(HEADERS, base_url=API_URL, timeout=TIMEOUT)
        self._set_access_token(userdata.get('access_token'))

    def _set_access_token(self, token):
        if token:
            self._session.headers.update({'Authorization': token})
            self._logged_in = True

            profile_id = userdata.get('profile')
            if profile_id:
                self._session.headers.update({'x-profile-id': profile_id})

    @property
    def logged_in(self):
        return self._logged_in

    def _parse_tokens(self, access_token, id_token):
        jwt = jwt_data(access_token)

        userdata.set('access_token', access_token)
        userdata.set('id_token', id_token)
        userdata.set('token_expires',
                     int(time.time()) + (jwt['exp'] - jwt['iat'] - 30))
        userdata.set('country', jwt['country'])
        userdata.set('package', jwt['package'])

        self._set_access_token(access_token)

    def _refresh_token(self, force=False):
        if not self._logged_in:
            raise APIError(_.PLUGIN_LOGIN_REQUIRED)

        expires = userdata.get('token_expires', 0)
        if not force and expires > time.time():
            return

        payload = {
            'idToken': userdata.get('id_token'),
            'accessToken': userdata.get('access_token'),
        }

        data = self._session.post(REFRESH_TOKEN_URL, json=payload).json()
        if not data.get('accessToken') or not data.get('idToken'):
            raise APIError(_(_.REFRESH_TOKEN_ERROR, msg=data.get('reason')))

        self._parse_tokens(data['accessToken'], data['idToken'])

    def profiles(self):
        return self._request_json('v6/profiles')['items']

    def _get_auth(self):
        data = self._request_json(
            'user-manager/v5/vod-authorisation;productId={product};platformId={platform};deviceType={device_type};'
            .format(product=PRODUCT_ID,
                    platform=PLATFORM_ID,
                    device_type=DEVICE_TYPE),
            type='post')

        return data

    def _request_json(self,
                      url,
                      type='get',
                      timeout=30,
                      attempts=3,
                      refresh_token=True,
                      **kwargs):
        if refresh_token:
            self._refresh_token(force=refresh_token == 'force')

        data = {}
        for i in range(attempts):
            if i > 0:
                log.debug("Try {}/{}".format(i + 1, attempts))

            r = self._session.request(type,
                                      url,
                                      timeout=timeout,
                                      attempts=1,
                                      **kwargs)
            try:
                data = r.json()
            except:
                continue

            if 'errorCode' in data:
                break

            if r.ok:
                return data
            elif not str(r.status_code).startswith('5'):
                break

        if 'errorMessage' in data:
            raise APIError(_(_.API_ERROR, msg=data['errorMessage']))
        elif 'reason' in data:
            raise APIError(_(_.API_ERROR, msg=data['reason']))
        else:
            raise APIError(
                _(_.REQUEST_ERROR, url=url.split(';')[0], code=r.status_code))

    def content(self, tags, sort, category='', page=0, pagesize=24):
        category = 'filter={};'.format(category) if category else ''

        data = self._request_json(
            'now-content/v7/catalogueByPackageAndCountry;productId={product};platformId={platform};tags={tags};subscriptionPackage={package};country={country};{category}sort={sort};page={page};pageSize={pagesize}'
            .format(
                product=PRODUCT_ID,
                platform=PLATFORM_ID,
                tags=tags,
                country=userdata.get('country', DEFAULT_COUNTRY),
                package=userdata.get('package', DEFAULT_PACKAGE),
                sort=sort,
                category=category,
                page=page,
                pagesize=pagesize,
            ))

        return data

    def search(self, query):
        data = self._request_json(
            'now-content/v6/search;platformId={platform};searchTerm={query}'.
            format(platform=PLATFORM_ID, query=query))

        return data['items'][0]['editorialItems']

    def channels(self, events=2):
        data = self._request_json(
            'v7/epg-service/channels/events;genre={genre};platformId={platform};country={country};packageId={package};count={events};utcOffset=+00:00'
            .format(genre='ALL',
                    platform=PLATFORM_ID,
                    country=userdata.get('country', DEFAULT_COUNTRY),
                    package=userdata.get('package', DEFAULT_PACKAGE),
                    events=events))

        return data['items']

    def epg(self, tag, start_date, end_date=None, attempts=3):
        end_date = end_date or start_date.shift(hours=24)

        for i in range(attempts):
            try:
                data = self._request_json(
                    'epg/v7/getEpgSchedulesByTag;channelTags={tag};startDate={start};endDate={end}'
                    .format(tag=tag,
                            start=start_date.format('YYYY-MM-DDT00:00:00ZZ'),
                            end=end_date.format('YYYY-MM-DDT00:00:00ZZ')),
                    attempts=1)
            except:
                continue

            if len(data.get('items', 0)) > 0:
                break

        return data['items']

    def series(self, id):
        data = self._request_json(
            'now-content/v6/getCatalogue;productId={product};platformId={platform};programId={id}'
            .format(product=PRODUCT_ID, platform=PLATFORM_ID, id=id))

        return data['items'][0]['program']

    def get_video(self, id):
        data = self._request_json(
            'now-content/v6/getCatalogue;productId={product};platformId={platform};videoId={id}'
            .format(product=PRODUCT_ID, platform=PLATFORM_ID, id=id))

        return data['items'][0]['video']

    def get_channel(self, id):
        for channel in self.channels():
            if channel['id'] == id:
                return channel

        raise APIError(_(_.CHANNEL_NOT_FOUND, id=id))

    def play_channel(self, id):
        channel = self.get_channel(id)

        stream_url = None
        for stream in channel['streams']:
            if stream['streamType'] in ('MobileAlt', 'WebAlt'):
                stream_url = stream['playerUrl']
                break

        if not stream_url:
            raise APIError(_.STREAM_ERROR)

        parsed = urlparse(stream_url)
        content_id = parse_qs(parsed.query)['contentId'][0]

        return self.play_asset(stream_url, content_id)

    def play_video(self, id):
        video = self.get_video(id)
        if not video.get('videoAssets'):
            raise APIError(_.STREAM_ERROR)

        stream_url = video['videoAssets'][0]['url']
        content_id = video['videoAssets'][0]['manItemId']

        return self.play_asset(stream_url, content_id)

    def play_asset(self, stream_url, content_id):
        auth = self._get_auth()
        session = auth.get('irdetoSession')

        if '.isml' in stream_url:
            stream_url = stream_url.replace('.isml', '.isml/.mpd')
        elif '.ism' in stream_url:
            stream_url = stream_url.replace('.ism', '.ism/.mpd')

        if not session:
            raise APIError(data.get('errorMessage'))

        session_id = session['sessionId']
        ticket = session['ticket']

        license_url = LICENSE_URL.format(content_id, session_id, ticket)

        return stream_url, license_url, self._session.headers

    def _device_id(self):
        def _format_id(string):
            try:
                mac_address = uuid.getnode()
                if mac_address != uuid.getnode():
                    mac_address = ''
            except:
                mac_address = ''

            system, arch = get_system_arch()
            return str(
                string.format(mac_address=mac_address, system=system).strip())

        return str(
            uuid.uuid3(uuid.UUID(UUID_NAMESPACE),
                       _format_id(settings.get('device_id'))))

    @contextmanager
    def device_login(self):
        device_id = self._device_id()

        payload = {
            'deviceId': device_id,
        }

        data = self._request_json(DEVICE_REGISTER,
                                  type='post',
                                  json=payload,
                                  refresh_token=False)
        code = data['userCode']

        log.debug('Device ID: {} | Device Code: {}'.format(device_id, code))

        login = DeviceLogin(device_id, code)

        try:
            yield login
        finally:
            login.stop()

        if login.result:
            token_data = login.token_data()
            self._parse_tokens(token_data['accessToken'],
                               token_data['idToken'])

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('id_token')
        userdata.delete('token_expires')
        userdata.delete('country')
        userdata.delete('package')
        userdata.delete('profile')
        self.new_session()
コード例 #24
0
class API(object):
    def new_session(self):
        self._session = Session(HEADERS)
        self.logged_in = userdata.get('userid') != None

    def login(self, username, password):
        self.logout()

        payload = {
            'action':
            'login',
            'email':
            username,
            'password':
            password,
            'authToken':
            hashlib.md5('{}::{}::{}'.format(
                MD5_KEY, username, password).encode('utf8')).hexdigest(),
        }

        data = self._session.post(LOGIN_URL, data=payload).json()

        if data['result']['status'] != 'success':
            raise APIError(_(_.LOGIN_ERROR, msg=data['result'].get('message')))

        userdata.set('userid', data['result']['userId'])

    def live_matches(self):
        return self._session.get(LIVE_URL).json()

    @cached(60 * 10)
    def played_series(self):
        return self._session.get(ARCHIVE_URL).json()

    @cached(60 * 10)
    def match(self, match_id):
        return self._session.get(
            ARCHIVES_MATCH_URL.format(match_id=match_id)).json()

    def upcoming_matches(self):
        return self._session.get(UPCOMING_URL).json()

    def get_series(self, series_id):
        data = self.played_series()

        for row in data['vod']:
            if row['sid'] == series_id:
                return row

        return None

    def play_live(self, match_id, priority):
        payload = {
            'mid': match_id,
            'type': 'live',
            'devType': DEV_TYPE,
            'pr': priority,
            'wuid': userdata.get('userid'),
        }

        return self.play(payload)

    def play_replay(self, match_id, content_id):
        payload = {
            'mid': match_id,
            'type': 'replay',
            'devType': DEV_TYPE,
            'title': content_id,
            'wuid': userdata.get('userid'),
        }

        return self.play(payload)

    def play_highlight(self, match_id, content_id):
        payload = {
            'mid': match_id,
            'type': 'highlight',
            'devType': DEV_TYPE,
            'id': content_id,
            'wuid': userdata.get('userid'),
        }

        return self.play(payload)

    def play(self, payload):
        data = self._session.post(PLAYBACK_URL, data=payload).json()

        if 'error' in data:
            raise APIError(_(_.PLAYBACK_ERROR, msg=data['error'].get('title')))
        elif 'subscribe' in data:
            raise APIError(
                _(_.SUBSCRIBE_ERROR, msg=data['subscribe'].get('description')))
        elif not data.get('Videos'):
            raise APIError(_.NO_VIDEOS)

        return data['Videos'][0]['Url']

    def logout(self):
        userdata.delete('userid')
        self.new_session()
コード例 #25
0
ファイル: api.py プロジェクト: Raspifan2020/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False

        ## Legacy ##
        userdata.delete('pswd')
        userdata.delete('access_token')
        ############

        self._session = Session(HEADERS, base_url=API_URL)
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('sky_token')
        if not token:
            return

        self._session.cookies.update({'sky-access': token})
        self.logged_in = True

    def series(self, id):
        return self._session.get(CONTENT_URL + id).json()

    def content(self, section='', text='', genre='', channels='', start=0):
        params = {
            'genre': genre,
            'rating': '',
            'text': text,
            'sortBy': 'TITLE',
            'title': '',
            'lastChance': 'false',
            'type': '',
            'channel': channels,
            'section': section,
            'size': 200,
            'start': start,
        }

        return self._session.get(CONTENT_URL, params=params).json()

    def channels(self):
        return self._session.get(CHANNELS_URL).json()['entries']

    def login(self, username, password):
        session = self._session.get('/login/initSession').json()

        data = {
            'authType': 'signIn',
            'rememberMe': True,
            'sessionJwt': session['sessionJwt'],
            'username': username,
            'password': password,
        }

        resp = self._session.post('/login/signin', json=data)
        self._process_login(resp)

    def _process_login(self, resp):
        data = resp.json()

        if not resp.ok or 'sky-access' not in resp.cookies:
            raise APIError(_(_.LOGIN_ERROR, message=data.get('message')))

        token = resp.cookies['sky-access']

        userdata.set('sky_token', token)
        userdata.set('device_id', data['deviceId'])

        if 'profileId' in data:
            userdata.set('profile_id', data['profileId'])

        jwt = jwt_data(token)
        userdata.set('token_expires', jwt['exp'])

        self._set_authentication()
        self._get_play_token()
        self._subscriptions()

    def _renew_token(self):
        if time.time() < userdata.get('token_expires'):
            return

        data = {
            'authType': 'renew',
            'deviceID': userdata.get('device_id'),
            'profileId': userdata.get('profile_id'),
            'rememberMe': True,
        }

        resp = self._session.post('/login/renew', json=data)
        self._process_login(resp)

    def _subscriptions(self):
        data = self._session.get('/entitlements/v2/onlineSubscriptions',
                                 params={
                                     'profileId': userdata.get('profile_id')
                                 }).json()
        userdata.set('subscriptions', data['onlineSubscriptions'])

    def _get_play_token(self):
        params = {
            'profileId': userdata.get('profile_id'),
            'deviceId': userdata.get('device_id'),
            'partnerId': 'skygo',
            'description': 'undefined undefined undefined',
        }

        resp = self._session.get('/mpx/v1/token', params=params)
        data = resp.json()

        if not resp.ok or 'token' not in data:
            raise APIError(_(_.TOKEN_ERROR, message=data.get('message')))

        userdata.set('play_token', data['token'])

    def _concurrency_unlock(self, root):
        concurrency_url = root.find(
            "./head/meta[@name='concurrencyServiceUrl']").attrib['content']
        lock_id = root.find("./head/meta[@name='lockId']").attrib['content']
        lock_token = root.find(
            "./head/meta[@name='lockSequenceToken']").attrib['content']
        lock = root.find("./head/meta[@name='lock']").attrib['content']

        params = {
            'schema': '1.0',
            'form': 'JSON',
            '_clientId': 'playerplayerHTML',
            '_id': lock_id,
            '_sequenceToken': lock_token,
            '_encryptedLock': lock,
            'httpError': False,
        }

        return self._session.get(
            '{}/web/Concurrency/unlock'.format(concurrency_url),
            params=params).json()

    def play_media(self, id):
        self._renew_token()

        params = {
            'form': 'json',
            'types': None,
            'fields': 'id,content',
            'byId': id,
        }

        data = self._session.get(PLAY_URL, params=params).json()

        if not data['entries']:
            raise APIError(_.VIDEO_UNAVAILABLE)

        videos = data['entries'][0]['media$content']

        chosen = videos[0]
        for video in videos:
            if video['plfile$format'].upper() == 'MPEG-DASH':
                chosen = video
                break

        if chosen['plfile$format'].upper() == 'F4M':
            raise APIError(_.ADOBE_ERROR)

        params = {
            'auth': userdata.get('play_token'),
            'formats': 'mpeg-dash',
            'tracking': True,
            'format': 'SMIL'
        }

        resp = self._session.get(chosen['plfile$url'], params=params)

        root = ET.fromstring(resp.text)
        strip_namespaces(root)

        if root.find("./body/seq/ref/param[@name='exception']") != None:
            error_msg = root.find("./body/seq/ref").attrib.get('abstract')
            raise APIError(_(_.PLAY_ERROR, message=error_msg))

        try:
            data = self._concurrency_unlock(root)
        except Exception as e:
            log.debug(
                'Failed to get concurrency lock. Attempting to continue without it...'
            )
            log.exception(e)

        ref = root.find(".//switch/ref")
        url = ref.attrib['src']

        tracking = {}
        for item in ref.find(
                "./param[@name='trackingData']").attrib['value'].split('|'):
            key, value = item.split('=')
            tracking[key] = value

        license = WIDEVINE_URL.format(token=userdata.get('play_token'),
                                      pid=tracking['pid'],
                                      challenge='B{SSM}')

        return url, license

    def logout(self):
        userdata.delete('device_id')
        userdata.delete('profile_id')
        userdata.delete('play_token')
        userdata.delete('token_expires')
        userdata.delete('sky_token')
        userdata.delete('subscriptions')
        self.new_session()
コード例 #26
0
class API(object):
    def new_session(self):
        self._logged_in = False
        self._language  = COMM_LANG
        self._session   = Session(HEADERS, base_url=API_URL)
        self._set_access_token(userdata.get('access_token'))

    def _set_access_token(self, token):
        if token:
            self._session.headers.update({'Authorization': 'Bearer {}'.format(token)})
            self._logged_in = True

    @property
    def logged_in(self):
        return self._logged_in

    def login(self, username, password):
        self.logout()

        data = {
            'response_type': 'token',
            'lang': self._language,
        }

        resp = self._session.get(LOGIN_URL, params=data)

        soup = BeautifulSoup(resp.text, 'html.parser')
        for form in soup.find_all('form'):
            data = {}

            for e in form.find_all('input'):
                data[e.attrs['name']] = e.attrs.get('value')

            if 'signin[email]' in data:
                break

        data.update({
            'signin[email]': username,
            'signin[password]': password,
        })

        resp = self._session.post(LOGIN_URL, data=data, allow_redirects=False)
        access_token = resp.cookies.get('showmax_oauth')

        if not access_token:
            raise APIError('Failed to login')

        self._set_access_token(access_token)

        data = self._session.get('user/current', params={'lang': self._language}).json()
        if 'error_code' in data:
            raise APIError('Failed to login')

        device_id = hashlib.sha1(username.lower().strip().encode('utf8')).hexdigest().upper()

        userdata.set('device_id', device_id)
        userdata.set('access_token', access_token)
        userdata.set('user_id', data['user_id'])

    def logout(self):
        userdata.delete('device_id')
        userdata.delete('access_token')
        userdata.delete('user_id')
        self.new_session()

    def _catalogue(self, _params):
        def process_page(start):
            params = {
                'field[]': ['id'],
                'lang': self._language,
                'showmax_rating': '18-plus',
                'sort': 'alphabet',
                'start': start,
                'subscription_status': 'full',
            }

            params.update(_params)

            data = self._session.get('catalogue/assets', params=params).json()
            items = data['items']

            count     = int(data.get('count', 0))
            remaining = int(data.get('remaining', 0))
            if count > 0 and remaining > 0:
                items.extend(process_page(start + count))

            return items

        return process_page(start=0)

    def series(self):
        return self._catalogue({
            'field[]': ['id', 'images', 'title', 'items', 'total', 'description', 'videos', 'type'],
            'type': 'tv_series',
        })

    def movies(self):
        return self._catalogue({
            'field[]': ['id', 'images', 'title', 'items', 'total', 'description', 'videos', 'type'],
            'type': 'movie',
        })

    def kids(self):
        return self._catalogue({
            'field[]': ['id', 'images', 'title', 'items', 'total', 'description', 'videos', 'type'],
            'showmax_rating': '5-6',
            'types[]': ['tv_series', 'movie'],
        })

    def seasons(self, series_id):
        params = {
            'field[]': ['id', 'images', 'title', 'items', 'total', 'description', 'number', 'seasons', 'type'],
            'lang': self._language,
            'showmax_rating': '18-plus',
            'subscription_status': 'full',
        }

        return self._session.get('catalogue/tv_series/{}'.format(series_id), params=params).json()

    def episodes(self, season_id):
        params = {
            'field[]': ['id', 'images', 'title', 'items', 'total', 'description', 'number', 'tv_series', 'episodes', 'videos', 'type'],
            'lang': self._language,
            'showmax_rating': '18-plus',
            'subscription_status': 'full',
        }

        return self._session.get('catalogue/season/{}'.format(season_id), params=params).json()

    def search(self, query):
        return self._catalogue({
            'field[]': ['id', 'images', 'title', 'items', 'total', 'type', 'description', 'type', 'videos'],
            'types[]': ['tv_series', 'movie'],
            'showmax_rating': '18-plus',
            'subscription_status': 'full',
            'q': query,
        })

    def asset(self, asset_id):
        params = {
            'field[]': ['videos',],
            'exclude[]': 'episodes',
            'lang': self._language,
            'showmax_rating': '18-plus',
            'subscription_status': 'full',
        }

        return self._session.get('catalogue/asset/{}'.format(asset_id), params=params).json()['videos']

    def play(self, video_id):
        codecs = ''

        if settings.getBool('vp9', False):
            codecs += 'vp9+'
        if settings.getBool('h265', False):
            codecs += 'hevc+'
        if settings.getBool('h264', True):
            codecs += 'h264+'

        codecs = codecs.rstrip('+')

        params = {
            'capabilities[]': ['codecs={}'.format(codecs), 'multiaudio'],
            'encoding': 'mpd_widevine_modular',
            'subscription_status': 'full',
            'mode': 'paid',
            'showmax_rating': '18-plus',
            'lang': self._language,
         #   'content_country': 'ZA',
        }

        ## Temp below use old api until Proxy quality player fixed (showmax blocks too quick calls)
        data = self._session.get('playback/play/{}'.format(video_id), params=params).json()
        if 'url' not in data:
            raise APIError(data.get('message'))

        url        = data['url']
        task_id    = data['packaging_task_id']
        session_id = data['session_id']

        data = {
            'user_id': userdata.get('user_id'),
            'video_id': video_id,
            'hw_code': userdata.get('device_id'),
            'packaging_task_id': task_id,
            'session_id': session_id,
        }

        params = {
            'showmax_rating': '18-plus',
            'mode': 'paid',
            'subscription_status': 'full',
            'lang': self._language,
        }

        data = self._session.post('playback/verify', params=params, data=data).json()

        if 'license_request' not in data:
            raise APIError(data.get('message'))

        license_request = data['license_request']
        license_url = API_URL.format('drm/widevine_modular?license_request={}'.format(license_request))

        return url, license_url
コード例 #27
0
class API(object):
    def new_session(self):
        self.logged_in = False
        self._session = Session(HEADERS, timeout=30)
        self._set_authentication(userdata.get('access_token'))
        self._set_languages()

    @mem_cache.cached(60 * 60, key='config')
    def get_config(self):
        return self._session.get(CONFIG_URL).json()

    def _set_languages(self):
        self._app_language = 'en'
        self._playback_language = 'en'
        self._subtitle_language = 'en'
        self._kids_mode = False
        self._maturity_rating = 9999
        self._region = None

        if not self.logged_in:
            return

        token = userdata.get('access_token')
        if '..' in token:  #JWE Token
            return

        data = jwt_data(token)['context']

        #   self._maturity_rating = data['preferred_maturity_rating']['implied_maturity_rating']
        #   self._region = data['location']['country_code']

        for profile in data['profiles']:
            if profile['id'] == data['active_profile_id']:
                self._app_language = profile['language_preferences'][
                    'app_language']
                self._playback_language = profile['language_preferences'][
                    'playback_language']
                self._subtitle_language = profile['language_preferences'][
                    'subtitle_language']
                self._kids_mode = profile['kids_mode_enabled']
                return

    @mem_cache.cached(60 * 60, key='transaction_id')
    def _transaction_id(self):
        return str(uuid.uuid4())

    @property
    def session(self):
        return self._session

    def _set_authentication(self, access_token):
        if not access_token:
            return

        ## JWT requires Bearer
        if '..' not in access_token:
            access_token = 'Bearer {}'.format(access_token)

        self._session.headers.update({'Authorization': access_token})
        self._session.headers.update(
            {'x-bamsdk-transaction-id': self._transaction_id()})
        self.logged_in = True

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires', 0) > time():
            return

        payload = {
            'refresh_token': userdata.get('refresh_token'),
            'grant_type': 'refresh_token',
            'platform': 'android',
        }

        self._oauth_token(payload)

    def _oauth_token(self, payload):
        headers = {
            'Authorization': 'Bearer {}'.format(API_KEY),
        }

        endpoint = self.get_config(
        )['services']['token']['client']['endpoints']['exchange']['href']
        token_data = self._session.post(endpoint,
                                        data=payload,
                                        headers=headers).json()

        self._check_errors(token_data)

        self._set_authentication(token_data['access_token'])

        userdata.set('access_token', token_data['access_token'])
        userdata.set('expires', int(time() + token_data['expires_in'] - 15))

        if 'refresh_token' in token_data:
            userdata.set('refresh_token', token_data['refresh_token'])

    def login(self, username, password):
        self.logout()

        try:
            self._do_login(username, password)
        except:
            self.logout()
            raise

    def _check_errors(self, data, error=_.API_ERROR):
        if not type(data) is dict:
            return

        if data.get('errors'):
            error_msg = ERROR_MAP.get(
                data['errors'][0].get('code')) or data['errors'][0].get(
                    'description') or data['errors'][0].get('code')
            raise APIError(_(error, msg=error_msg))

        elif data.get('error'):
            error_msg = ERROR_MAP.get(data.get('error_code')) or data.get(
                'error_description') or data.get('error_code')
            raise APIError(_(error, msg=error_msg))

        elif data.get('status') == 400:
            raise APIError(_(error, msg=data.get('message')))

    def _do_login(self, username, password):
        headers = {
            'Authorization': 'Bearer {}'.format(API_KEY),
        }

        payload = {
            'deviceFamily': 'android',
            'applicationRuntime': 'android',
            'deviceProfile': 'tv',
            'attributes': {},
        }

        endpoint = self.get_config()['services']['device']['client'][
            'endpoints']['createDeviceGrant']['href']
        device_data = self._session.post(endpoint,
                                         json=payload,
                                         headers=headers,
                                         timeout=20).json()

        self._check_errors(device_data)

        payload = {
            'subject_token': device_data['assertion'],
            'subject_token_type': 'urn:bamtech:params:oauth:token-type:device',
            'platform': 'android',
            'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
        }

        self._oauth_token(payload)

        payload = {
            'email': username,
            'password': password,
        }

        endpoint = self.get_config()['services']['bamIdentity']['client'][
            'endpoints']['identityLogin']['href']
        login_data = self._session.post(endpoint, json=payload).json()

        self._check_errors(login_data)

        endpoint = self.get_config()['services']['account']['client'][
            'endpoints']['createAccountGrant']['href']
        grant_data = self._session.post(endpoint,
                                        json={
                                            'id_token': login_data['id_token']
                                        }).json()

        payload = {
            'subject_token': grant_data['assertion'],
            'subject_token_type':
            'urn:bamtech:params:oauth:token-type:account',
            'platform': 'android',
            'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
        }

        self._oauth_token(payload)

    def _json_call(self, endpoint, variables=None):
        self._refresh_token()
        params = {'variables': json.dumps(variables)} if variables else None
        data = self._session.get(endpoint, params=params).json()
        self._check_errors(data)
        return data

    def profiles(self):
        self._refresh_token(force=True)
        endpoint = self.get_config()['services']['account']['client'][
            'endpoints']['getUserProfiles']['href']
        return self._json_call(endpoint)

    def active_profile(self):
        endpoint = self.get_config()['services']['account']['client'][
            'endpoints']['getActiveUserProfile']['href']
        return self._json_call(endpoint)

    def set_profile(self, profile, pin=None):
        self._refresh_token()

        endpoint = self.get_config()['services']['account']['client'][
            'endpoints']['setActiveUserProfile']['href'].format(
                profileId=profile['profileId'])

        payload = {}
        if pin:
            payload['entryPin'] = str(pin)

        grant_data = self._session.put(endpoint, json=payload).json()
        self._check_errors(grant_data)

        payload = {
            'subject_token': grant_data['assertion'],
            'subject_token_type':
            'urn:bamtech:params:oauth:token-type:account',
            'platform': 'android',
            'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
        }

        self._oauth_token(payload)

        userdata.set(
            'profile_language',
            profile['attributes']['languagePreferences']['appLanguage'])

    def search(self, query, page=1, page_size=PAGE_SIZE):
        variables = {
            'preferredLanguage': [self._app_language],
            'index': 'disney_global',
            'q': query,
            'page': page,
            'pageSize': page_size,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['searchPersisted']['href'].format(
                queryId='core/disneysearch')
        return self._json_call(endpoint, variables)['data']['disneysearch']

    def avatar_by_id(self, ids):
        variables = {
            'preferredLanguage': [self._app_language],
            'avatarId': ids,
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['searchPersisted']['href'].format(
                queryId='core/AvatarByAvatarId')
        return self._json_call(endpoint, variables)['data']['AvatarByAvatarId']

    def video_bundle(self, family_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'familyId': family_id,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/DmcVideoBundle')
        return self._json_call(endpoint, variables)['data']['DmcVideoBundle']

    def extras(self, family_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'familyId': family_id,
            'page': 1,
            'pageSize': 999,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(queryId='core/DmcExtras')
        return self._json_call(endpoint, variables)['data']['DmcExtras']

    def series_bundle(self, series_id, page=1, page_size=PAGE_SIZE):
        variables = {
            'preferredLanguage': [self._app_language],
            'seriesId': series_id,
            'episodePage': page,
            'episodePageSize': page_size,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/DmcSeriesBundle')
        return self._json_call(endpoint, variables)['data']['DmcSeriesBundle']

    def episodes(self, season_ids, page=1, page_size=PAGE_SIZE_EPISODES):
        variables = {
            'preferredLanguage': [self._app_language],
            'seasonId': season_ids,
            'episodePage': page,
            'episodePageSize': page_size,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/DmcEpisodes')
        return self._json_call(endpoint, variables)['data']['DmcEpisodes']

    def collection_by_slug(self, slug, content_class):
        variables = {
            'preferredLanguage': [self._app_language],
            'contentClass': content_class,
            'slug': slug,
            'contentTransactionId': self._transaction_id(),
        }

        #endpoint = self.get_config()['services']['content']['client']['endpoints']['dmcVideos']['href'].format(queryId='disney/CollectionBySlug')
        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/CompleteCollectionBySlug')
        return self._json_call(endpoint,
                               variables)['data']['CompleteCollectionBySlug']

    def set_by_id(self, set_id, set_type, page=1, page_size=PAGE_SIZE):
        variables = {
            'preferredLanguage': [self._app_language],
            'setId': set_id,
            'setType': set_type,
            'page': page,
            'pageSize': page_size,
            'contentTransactionId': self._transaction_id(),
        }

        #endpoint = self.get_config()['services']['content']['client']['endpoints']['dmcVideos']['href'].format(queryId='disney/SetBySetId')
        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(queryId='core/SetBySetId')
        return self._json_call(endpoint, variables)['data']['SetBySetId']

    def add_watchlist(self, content_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'contentIds': content_id,
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/AddToWatchlist')
        return self._json_call(endpoint, variables)['data']['AddToWatchlist']

    def delete_watchlist(self, content_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'contentIds': content_id,
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(
                queryId='core/DeleteFromWatchlist')
        data = self._json_call(endpoint,
                               variables)['data']['DeleteFromWatchlist']
        xbmc.sleep(500)
        return data

    def up_next(self, content_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'contentId': content_id,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(queryId='core/UpNext')
        return self._json_call(endpoint, variables)['data']['UpNext']

    def videos(self, content_id):
        variables = {
            'preferredLanguage': [self._app_language],
            'contentId': content_id,
            'contentTransactionId': self._transaction_id(),
        }

        endpoint = self.get_config()['services']['content']['client'][
            'endpoints']['dmcVideos']['href'].format(queryId='core/DmcVideos')
        return self._json_call(endpoint, variables)['data']['DmcVideos']

    def update_resume(self, media_id, fguid, playback_time):
        payload = [{
            "server": {
                "fguid": fguid,
                "mediaId": media_id,
            },
            "client": {
                "event": "urn:dss:telemetry-service:event:stream-sample",
                "timestamp": str(int(time() * 1000)),
                "play_head": playback_time,
                # "playback_session_id": str(uuid.uuid4()),
                # "interaction_id": str(uuid.uuid4()),
                # "bitrate": 4206,
            },
        }]

        self._refresh_token()
        endpoint = self.get_config(
        )['services']['telemetry']['client']['endpoints']['postEvent']['href']
        return self._session.post(endpoint, json=payload).status_code

    def playback_data(self, playback_url):
        self._refresh_token(force=True)

        config = self.get_config()
        scenario = config['services']['media']['extras'][
            'restrictedPlaybackScenario']

        if settings.getBool('wv_secure', False):
            scenario = config['services']['media']['extras'][
                'playbackScenarioDefault']

            if settings.getBool('h265', False):
                scenario += '-h265'

                if settings.getBool('dolby_vision', False):
                    scenario += '-dovi'
                elif settings.getBool('hdr10', False):
                    scenario += '-hdr10'

                if settings.getBool('dolby_atmos', False):
                    scenario += '-atmos'

        headers = {
            'accept': 'application/vnd.media-service+json; version=4',
            'authorization': userdata.get('access_token')
        }

        endpoint = playback_url.format(scenario=scenario)
        playback_data = self._session.get(endpoint, headers=headers).json()
        self._check_errors(playback_data)

        return playback_data

    def continue_watching(self):
        set_id = CONTINUE_WATCHING_SET_ID
        set_type = CONTINUE_WATCHING_SET_TYPE
        data = self.set_by_id(set_id, set_type, page_size=999)

        continue_watching = {}
        for row in data['items']:
            if row['meta']['bookmarkData']:
                play_from = row['meta']['bookmarkData']['playhead']
            else:
                play_from = 0

            continue_watching[row['contentId']] = play_from

        return continue_watching

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('expires')
        userdata.delete('refresh_token')

        mem_cache.delete('transaction_id')
        mem_cache.delete('config')

        self.new_session()
コード例 #28
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self._session = Session(HEADERS, base_url=API_URL)
        self.logged_in = userdata.get('token') != None

    def _refresh_token(self):
        payload = {
            'username': userdata.get('username'),
            'loginToken': userdata.get('token'),
            'deviceId': userdata.get('deviceid'),
            'accountType': 'foxtel',
            'format': 'json',
            'appID': 'GO2',
            'plt': PLT_DEVICE,
        }

        password = userdata.get('pswd')
        if password:
            log.debug('Using Password Login')
            payload['password'] = self._hex_password(password,
                                                     userdata.get('deviceid'))
            del payload['loginToken']
        else:
            log.debug('Using Token Login')

        data = self._session.post(
            '/auth.class.api.php/logon/{site_id}'.format(site_id=VOD_SITEID),
            data=payload).json()

        response = data['LogonResponse']
        error = response.get('Error')
        success = response.get('Success')

        if error:
            self.logout()
            raise APIError(_(_.TOKEN_ERROR, msg=error.get('Message')))

        userdata.set('token', success['LoginToken'])
        userdata.set('deviceid', success['DeviceId'])
        userdata.set('entitlements', success.get('Entitlements', ''))

        self.logged_in = True

    def login(self, username, password, kickdevice=None):
        self.logout()

        raw_id = self._format_id(settings.get('device_id')).lower()
        device_id = hashlib.sha1(raw_id.encode('utf8')).hexdigest()

        log.debug('Raw device id: {}'.format(raw_id))
        log.debug('Hashed device id: {}'.format(device_id))

        hex_password = self._hex_password(password, device_id)

        payload = {
            'username': username,
            'password': hex_password,
            'deviceId': device_id,
            'accountType': 'foxtel',
            'format': 'json',
            'appID': 'GO2',
            'plt': PLT_DEVICE,
        }

        if kickdevice:
            payload['deviceToKick'] = kickdevice
            log.debug('Kicking device: {}'.format(kickdevice))

        data = self._session.post(
            '/auth.class.api.php/logon/{site_id}'.format(site_id=VOD_SITEID),
            data=payload).json()

        response = data['LogonResponse']
        devices = response.get('CurrentDevices', [])
        error = response.get('Error')
        success = response.get('Success')

        if error:
            if not devices or kickdevice:
                raise APIError(_(_.LOGIN_ERROR, msg=error.get('Message')))

            options = [d['Nickname'] for d in devices]
            index = gui.select(_.DEREGISTER_CHOOSE, options)
            if index < 0:
                raise APIError(_(_.LOGIN_ERROR, msg=error.get('Message')))

            kickdevice = devices[index]['DeviceID']

            return self.login(username, password, kickdevice=kickdevice)

        userdata.set('token', success['LoginToken'])
        userdata.set('deviceid', success['DeviceId'])
        userdata.set('entitlements', success.get('Entitlements', ''))

        if settings.getBool('save_password', False):
            userdata.set('pswd', password)
            log.debug('Password Saved')

        self.logged_in = True

    def _format_id(self, string):
        try:
            mac_address = uuid.getnode()
            if mac_address != uuid.getnode():
                mac_address = ''
        except:
            mac_address = ''

        system, arch = get_system_arch()

        return string.format(username=userdata.get('username'),
                             mac_address=mac_address,
                             system=system).strip()

    def _hex_password(self, password, device_id):
        nickname = self._format_id(settings.get('device_name'))
        log.debug('Device nickname: {}'.format(nickname))

        payload = {
            'deviceId': device_id,
            'nickName': nickname,
            'format': 'json',
            'appID': 'GO2',
            'accountType': 'foxtel',
            'plt': PLT_DEVICE,
        }

        secret = self._session.post(
            '/auth.class.api.php/prelogin/{site_id}'.format(
                site_id=VOD_SITEID),
            data=payload).json()['secret']
        log.debug('Pass Secret: {}{}'.format(secret[:5],
                                             'x' * len(secret[5:])))

        try:
            #python3
            iv = bytes.fromhex(AES_IV)
        except AttributeError:
            #python2
            iv = str(bytearray.fromhex(AES_IV))

        encrypter = pyaes.Encrypter(
            pyaes.AESModeOfOperationCBC(secret.encode('utf8'), iv))

        ciphertext = encrypter.feed(password)
        ciphertext += encrypter.feed()

        try:
            #python3
            hex_password = ciphertext.hex()
        except AttributeError:
            #python2
            hex_password = ciphertext.encode('hex')

        log.debug('Hex password: {}{}'.format(hex_password[:5],
                                              'x' * len(hex_password[5:])))

        return hex_password

    def assets(self, asset_type, _filter=None, showall=False):
        params = {
            'showall': showall,
            'plt': PLT_DEVICE,
            'entitlementToken': self._entitlement_token(),
            'sort': 'latest',
            'format': 'json',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        if _filter:
            params['filters'] = _filter

        return self._session.get(
            '/categoryTree.class.api.php/GOgetAssets/{site_id}/{asset_type}'.
            format(site_id=VOD_SITEID, asset_type=asset_type),
            params=params,
            timeout=20).json()

    def live_channels(self, _filter=None):
        params = {
            'plt': PLT_DEVICE,
            'entitlementToken': self._entitlement_token(),
            'format': 'json',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        if _filter:
            params['filter'] = _filter

        return self._session.get(
            '/categoryTree.class.api.php/GOgetLiveChannels/{site_id}'.format(
                site_id=LIVE_SITEID),
            params=params).json()

    def show(self, show_id):
        params = {
            'showId': show_id,
            'plt': PLT_DEVICE,
            'format': 'json',
            'dateFormat': 'ISO8601',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        return self._session.get(
            '/asset.class.api.php/GOgetAssetData/{site_id}/0'.format(
                site_id=VOD_SITEID),
            params=params).json()

    def asset(self, media_type, id):
        params = {
            'plt': PLT_DEVICE,
            'format': 'json',
            'dateFormat': 'ISO8601',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        if media_type == TYPE_VOD:
            site_id = VOD_SITEID
        else:
            site_id = LIVE_SITEID

        return self._session.get(
            '/asset.class.api.php/GOgetAssetData/{site_id}/{id}'.format(
                site_id=site_id, id=id),
            params=params).json()

    def bundle(self, mode=''):
        params = {
            'plt': PLT_DEVICE,
            'entitlementToken': self._entitlement_token(),
            'apiVersion': 2,
            'filter': '',
            'mode': mode,
            'format': 'json',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        return self._session.get(BUNDLE_URL, params=params).json()

    def _sync_token(self, site_id, catalog_name):
        self._refresh_token()

        params = {
            'serviceID': 'PLAY',
        }

        payload = {
            'loginToken': userdata.get('token'),
            'deviceId': userdata.get('deviceid'),
            'format': 'json',
        }

        vod_token = None
        live_token = None

        data = self._session.post(
            '/userCatalog.class.api.php/getSyncTokens/{site_id}'.format(
                site_id=VOD_SITEID),
            params=params,
            data=payload).json()

        for token in data.get('tokens', []):
            if token['siteId'] == site_id and token[
                    'catalogName'] == catalog_name:
                return token['token']

        return None

    def user_catalog(self, catalog_name, site_id=VOD_SITEID):
        token = self._sync_token(site_id, catalog_name)
        if not token:
            return

        params = {
            'syncToken': token,
            'platform': PLT_DEVICE,
            'limit': 100,
            'format': 'json',
            'appID': 'GO2',
            'serviceID': 'PLAY',
        }

        return self._session.get(
            '/userCatalog.class.api.php/getCarousel/{site_id}/{catalog_name}'.
            format(site_id=site_id, catalog_name=catalog_name),
            params=params).json()

    def epg(self, channel_codes, starttime=None, endtime=None):
        now = arrow.utcnow()

        starttime = starttime or now.shift(hours=-2)
        endtime = endtime or starttime.shift(days=1)

        params = {
            'filter_starttime': starttime.timestamp,
            'filter_endtime': endtime.timestamp,
            'filter_channels': ','.join(channel_codes),
            'filter_fields':
            'EventTitle,ShortSynopsis,StartTimeUTC,EndTimeUTC,RawStartTimeUTC,RawEndTimeUTC,ProgramTitle,EpisodeTitle,Genre,HighDefinition,ClosedCaption,EpisodeNumber,SeriesNumber,ParentalRating,MergedShortSynopsis',
            'format': 'json',
        }

        return self._session.get(EPG_URL, params=params).json()

    def search(self, query, _type='VOD'):
        params = {
            'prod': 'FOXTELGO',
            'idm': '04',
            'BLOCKED': 'YES',
            'fx': '"{}"'.format(query),
            'sfx': 'type:{}'.format(_type),  #VOD OR LINEAR
            'limit': 100,
            'offset': 0,
            'dpg': 'R18+',
            'ao': 'N',
            'dopt': '[F0:11]',
            'hwid': '_',
            'REGION': '_',
            'utcOffset': '+1200',
            'swver': '3.3.7',
            'aid': '_',
            'fxid': '_',
            'rid': 'SEARCH5',
        }

        return self._session.get(SEARCH_URL, params=params).json()

    def play(self, media_type, id):
        self._refresh_token()

        payload = {
            'deviceId': userdata.get('deviceid'),
            'loginToken': userdata.get('token'),
        }

        if media_type == TYPE_VOD:
            endpoint = 'GOgetVODConfig'
            site_id = VOD_SITEID
        else:
            endpoint = 'GOgetLiveConfig'
            site_id = LIVE_SITEID

        params = {
            'rate':
            'WIREDHIGH',
            'plt':
            'ipstb',
            'appID':
            'PLAY2',
            'deviceCaps':
            hashlib.md5('TR3V0RwAZH3r3L00kingA7SumStuFF{}'.format('L1').encode(
                'utf8')).hexdigest().lower(),
            'format':
            'json',
        }

        data = self._session.post(PLAY_URL.format(endpoint=endpoint,
                                                  site_id=site_id,
                                                  id=id),
                                  params=params,
                                  data=payload).json()

        error = data.get('errorMessage')

        if error:
            raise APIError(_(_.PLAYBACK_ERROR, msg=error))

        streams = sorted(data['media'].get('streams', []),
                         key=lambda s: STREAM_PRIORITY.get(
                             s['profile'].upper(), STREAM_PRIORITY['DEFAULT']),
                         reverse=True)
        if not streams:
            raise APIError(_.NO_STREAM_ERROR)

        playback_url = streams[0]['url']
        playback_url = playback_url.replace(
            'cm=yes&', '')  #without this = bad widevine key

        ## Get L3 License URL
        params['plt'] = 'andr_phone'
        params['appID'] = 'PLAY2'
        data = self._session.post(LICENSE_URL.format(endpoint=endpoint,
                                                     site_id=site_id,
                                                     id=id),
                                  params=params,
                                  data=payload).json()
        license_url = data['fullLicenceUrl']
        #######

        params = {
            'sessionId': data['general']['sessionID'],
            'deviceId': userdata.get('deviceid'),
            'loginToken': userdata.get('token'),
            'sessionStatus': 'FINISHED',
            'appID': 'GO2',
            'serviceID': 'GO',
            'format': 'json',
        }

        url = '/playback.class.api.php/GOupdateSession/{}/{}'.format(
            data['general']['siteID'], data['general']['assetID'])
        self._session.get(url, params=params).json()

        return playback_url, license_url

    def asset_for_program(self, show_id, program_id):
        show = self.show(show_id)

        if show.get('programId') == program_id:
            return show

        if 'childAssets' not in show:
            return None

        for child in show['childAssets']['items']:
            if child.get('programId') == program_id:
                return child

            if 'childAssets' not in child:
                return None

            for subchild in child['childAssets']['items']:
                if subchild.get('programId') == program_id:
                    return subchild

        return None

    def _entitlement_token(self):
        entitlements = userdata.get('entitlements')
        if not entitlements:
            return None

        return hashlib.md5(entitlements.encode('utf8')).hexdigest()

    def logout(self):
        userdata.delete('token')
        userdata.delete('deviceid')
        userdata.delete('pswd')
        userdata.delete('entitlements')
        self.new_session()
コード例 #29
0
class API(object):
    def new_session(self):
        self.logged_in = False

        host = settings.get('business_host') if settings.getBool(
            'business_account', False) else DEFAULT_HOST
        if host != userdata.get('host', DEFAULT_HOST):
            userdata.delete('access_token')
            userdata.set('host', host)

        self._session = Session(HEADERS, base_url=API_URL.format(host))
        self._set_authentication()

    def _set_authentication(self):
        token = userdata.get('access_token')
        if not token:
            return

        self._session.headers.update(
            {'Authorization': 'Bearer {}'.format(token)})
        self.logged_in = True

    def my_courses(self, page=1, query=None):
        params = {
            'page':
            page,
            'page_size':
            PAGE_SIZE,
            'ordering':
            'title',
            'fields[course]':
            'id,title,image_480x270,image_750x422,headline,num_published_lectures,content_info,completion_ratio',
        }

        if query:
            params['search'] = query

        return self._session.get('users/me/subscribed-courses',
                                 params=params).json()

    def chapters(self, course_id, page=1):
        params = {
            'page': page,
            'page_size': PAGE_SIZE,
            'fields[course]': 'image_480x270',
            'fields[chapter]': 'description,object_index,title,course',
            'fields[lecture]': 'id',
            'fields[practice]': 'id',
            'fields[quiz]': 'id',
        }

        data = self._session.get(
            'courses/{}/cached-subscriber-curriculum-items'.format(course_id),
            params=params).json()
        rows = [r for r in data['results'] if r['_class'] == 'chapter']
        return rows, data['next']

    def lectures(self, course_id, chapter_id, page=1):
        params = {
            'page': page,
            'page_size': PAGE_SIZE,
            'fields[course]': 'image_480x270,title',
            'fields[chapter]': 'id',
            'fields[lecture]':
            'title,object_index,description,is_published,course,id,asset',
            'fields[asset]': 'asset_type,length,status',
            'fields[practice]': 'id',
            'fields[quiz]': 'id',
        }

        data = self._session.get(
            'courses/{}/cached-subscriber-curriculum-items'.format(course_id),
            params=params).json()

        lectures = []
        found = False
        for row in data['results']:
            if not found and row['_class'] == 'chapter' and row['id'] == int(
                    chapter_id):
                found = True

            elif found and row['_class'] == 'lecture' and row[
                    'is_published'] and row['asset']['asset_type'] in (
                        'Video', 'Audio'):
                lectures.append(row)

            elif found and row['_class'] == 'chapter':
                break

        return lectures, data['next']

    def get_stream_data(self, asset_id):
        params = {
            'fields[asset]': '@all',
        }

        data = self._session.get('assets/{0}'.format(asset_id),
                                 params=params).json()
        if 'detail' in data:
            raise APIError(data['detail'])

        return data

    def login(self, username, password):
        data = {
            'email': username,
            'password': password,
            'upow': self._get_upow(username, 'login')
        }

        params = {
            'fields[user]': 'title,image_100x100,name,access_token',
        }

        r = self._session.post('auth/udemy-auth/login/',
                               params=params,
                               data=data)
        try:
            data = r.json()
        except:
            raise APIError(_(_.LOGIN_ERROR, msg=r.status_code))

        access_token = data.get('access_token')
        if not access_token:
            raise APIError(_(_.LOGIN_ERROR, msg=data.get('detail', '')))

        userdata.set('access_token', access_token)
        self._set_authentication()

    def logout(self):
        userdata.delete('access_token')
        self.new_session()

    def _get_upow(self, message, secret):
        date = datetime.datetime.today().strftime('%Y%m%d')

        def get_token(email, date, secret):
            message = email + date
            i = 0

            for x in range(0, 20):
                i3 = i * 50

                while True:
                    i2 = i + 1
                    if i3 >= i2 * 50:
                        break

                    i4 = i3 * 1000
                    i3 += 1
                    token = hash_calc(i4, i3 * 1000, message, secret)
                    if token:
                        return token

                i = i2

            return None

        def m26785a(i):
            f19175e = ""
            while i >= 0:
                f19175e += chr(((i % 26) + 65))
                i = int(i / 26) - 1
            return f19175e[::-1]

        def hash_calc(i, i2, message, password):
            a = m26785a(i)
            _bytes = bytearray(message + a, 'utf8')
            password = password.encode()

            while i < i2:
                _i = i
                if (_i % 26 == 0):
                    _bytes = bytearray(message + m26785a(_i), 'utf8')
                else:
                    _bytes[len(_bytes) - 1] = (_bytes[len(_bytes) - 1] + 1)

                doFinal = hmac.new(password, _bytes,
                                   digestmod=hashlib.sha256).hexdigest()
                if doFinal[0:2] == '00' and doFinal[2:4] == '00':
                    return m26785a(i)

                i += 1

            return None

        return date + get_token(message, date, secret)
コード例 #30
0
ファイル: api.py プロジェクト: matthuisman/slyguy.addons
class API(object):
    def new_session(self):
        self.logged_in = False
        self._auth_header = {}

        self._session = Session(HEADERS)
        self._set_authentication()

    @mem_cache.cached(60 * 10)
    def _config(self):
        return self._session.get(CONFIG_URL).json()

    def _set_authentication(self):
        access_token = userdata.get('access_token')
        if not access_token:
            return

        self._auth_header = {'authorization': 'Bearer {}'.format(access_token)}
        self.logged_in = True

    def _oauth_token(self, data, _raise=True):
        token_data = self._session.post(
            'https://auth.streamotion.com.au/oauth/token',
            json=data,
            headers={
                'User-Agent': 'okhttp/3.10.0'
            },
            error_msg=_.TOKEN_ERROR).json()

        if 'error' in token_data:
            error = _.REFRESH_TOKEN_ERROR if data.get(
                'grant_type') == 'refresh_token' else _.LOGIN_ERROR
            if _raise:
                raise APIError(
                    _(error, msg=token_data.get('error_description')))
            else:
                return False, token_data

        userdata.set('access_token', token_data['access_token'])
        userdata.set('expires', int(time() + token_data['expires_in'] - 15))

        if 'refresh_token' in token_data:
            userdata.set('refresh_token', token_data['refresh_token'])

        self._set_authentication()
        return True, token_data

    def refresh_token(self):
        self._refresh_token()

    def _refresh_token(self, force=False):
        if not force and userdata.get('expires',
                                      0) > time() or not self.logged_in:
            return

        log.debug('Refreshing token')

        payload = {
            'client_id':
            CLIENT_ID,
            'refresh_token':
            userdata.get('refresh_token'),
            'grant_type':
            'refresh_token',
            'scope':
            'openid offline_access drm:{} email'.format(
                'high' if settings.getBool('wv_secure', False) else 'low'),
        }

        self._oauth_token(payload)

    def device_code(self):
        payload = {
            'client_id':
            CLIENT_ID,
            'audience':
            'streamotion.com.au',
            'scope':
            'openid offline_access drm:{} email'.format(
                'high' if settings.getBool('wv_secure', False) else 'low'),
        }

        return self._session.post(
            'https://auth.streamotion.com.au/oauth/device/code',
            data=payload).json()

    def device_login(self, device_code):
        payload = {
            'client_id':
            CLIENT_ID,
            'device_code':
            device_code,
            'scope':
            'openid offline_access drm:{}'.format(
                'high' if settings.getBool('wv_secure', False) else 'low'),
            'grant_type':
            'urn:ietf:params:oauth:grant-type:device_code',
        }

        result, token_data = self._oauth_token(payload, _raise=False)
        if result:
            self._refresh_token(force=True)
            return True

        if token_data.get('error') != 'authorization_pending':
            raise APIError(
                _(_.LOGIN_ERROR, msg=token_data.get('error_description')))
        else:
            return False

    def login(self, username, password):
        payload = {
            'client_id':
            CLIENT_ID,
            'username':
            username,
            'password':
            password,
            'audience':
            'streamotion.com.au',
            'scope':
            'openid offline_access drm:{} email'.format(
                'high' if settings.getBool('wv_secure', False) else 'low'),
            'grant_type':
            'http://auth0.com/oauth/grant-type/password-realm',
            'realm':
            'prod-martian-database',
        }

        self._oauth_token(payload)
        self._refresh_token(force=True)

    def profiles(self):
        self._refresh_token()
        return self._session.get('{host}/user/profile'.format(
            host=self._config()['endPoints']['profileAPI']),
                                 headers=self._auth_header).json()

    def add_profile(self, name, avatar_id):
        self._refresh_token()

        payload = {
            'name': name,
            'avatar_id': avatar_id,
            'onboarding_status': 'welcomeScreen',
        }

        return self._session.post('{host}/user/profile'.format(
            host=self._config()['endPoints']['profileAPI']),
                                  json=payload,
                                  headers=self._auth_header).json()

    def delete_profile(self, profile):
        self._refresh_token()

        return self._session.delete('{host}/user/profile/{profile_id}'.format(
            host=self._config()['endPoints']['profileAPI'],
            profile_id=profile['id']),
                                    headers=self._auth_header)

    def profile_avatars(self):
        return self._session.get(
            '{host}/production/avatars/avatars.json'.format(
                host=self._config()['endPoints']['resourcesAPI'])).json()

    def sport_menu(self):
        return self._session.get(
            '{host}/production/sport-menu/lists/default.json'.format(
                host=self._config()['endPoints']['resourcesAPI'])).json()

    def use_cdn(self, live=False):
        return self._session.get('{host}/web/usecdn/unknown/{media}'.format(
            host=self._config()['endPoints']['cdnSelectionServiceAPI'],
            media='LIVE' if live else 'VOD'),
                                 headers=self._auth_header).json()

    #landing has heros and panels
    def landing(self, name, sport=None):
        params = {
            'evaluate': 3,
            'profile': userdata.get('profile_id'),
        }

        if sport:
            params['sport'] = sport

        return self._session.get(
            '{host}/content/types/landing/names/{name}'.format(
                host=self._config()['endPoints']['contentAPI'], name=name),
            params=params,
            headers=self._auth_header).json()

    #panel has shows and episodes
    def panel(self, id, sport=None):
        params = {
            'evaluate': 3,
            'profile': userdata.get('profile_id'),
        }

        if sport:
            params['sport'] = sport

        return self._session.get(
            '{host}/content/types/carousel/keys/{id}'.format(
                host=self._config()['endPoints']['contentAPI'], id=id),
            params=params,
            headers=self._auth_header).json()[0]

    #show has episodes and panels
    def show(self, show_id, season_id=None):
        params = {
            'evaluate': 3,
            'showCategory': show_id,
            'seasonCategory': season_id,
            'profile': userdata.get('profile_id'),
        }

        return self._session.get(
            '{host}/content/types/landing/names/show'.format(
                host=self._config()['endPoints']['contentAPI']),
            params=params,
            headers=self._auth_header).json()

    def search(self, query, page=1, size=250):
        params = {
            'q': query,
            'size': size,
            'page': page,
        }

        return self._session.get('{host}/v2/search'.format(
            host=self._config()['endPoints']['contentAPI']),
                                 params=params).json()

    def event(self, id):
        params = {
            'evaluate': 3,
            'event': id,
        }

        return self._session.get(
            '{host}/content/types/landing/names/event'.format(
                host=self._config()['endPoints']['contentAPI']),
            params=params).json()[0]['contents'][0]['data']['asset']

    def stream(self, asset_id):
        self._refresh_token()

        params = {
            'fields':
            'alternativeStreams,assetType,markers,metadata.isStreaming',
        }

        data = self._session.post('{host}/api/v1/asset/{asset_id}/play'.format(
            host=self._config()['endPoints']['vimondPlayAPI'],
            asset_id=asset_id),
                                  params=params,
                                  json={},
                                  headers=self._auth_header).json()
        if ('status' in data and data['status'] != 200) or 'errors' in data:
            msg = data.get('detail') or data.get('errors',
                                                 [{}])[0].get('detail')
            raise APIError(_(_.ASSET_ERROR, msg=msg))

        return data['data'][0]

    def logout(self):
        userdata.delete('access_token')
        userdata.delete('refresh_token')
        userdata.delete('expires')
        self.new_session()