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({'sky-x-access-token': token}) self.logged_in = True def series(self, id): return self._session.get(CONTENT_URL + id).json() def content(self, section='', sortby='TITLE', text='', title=None, channels='', start=0): params = { 'title': title or '', 'genre': '', 'rating': '', 'text': text, 'sortBy': sortby, 'lastChance': 'true' if sortby == 'LASTCHANCE' else 'false', 'type': '', 'channel': channels, 'section': section, 'size': 100, 'start': start, } return self._session.get(CONTENT_URL, params=params).json() def channels(self): data = self._session.get(CHANNELS_URL).json() return data['entries'] def login(self, username, password): device_id = hashlib.md5(username).hexdigest() data = { "deviceDetails": "test", "deviceID": device_id, "deviceIP": DEVICE_IP, "password": password, "username": username } resp = self._session.post(AUTH_URL, json=data) data = resp.json() if resp.status_code != 200 or 'sessiontoken' not in data: raise APIError(_(_.LOGIN_ERROR, message=data.get('message'))) userdata.set('access_token', data['sessiontoken']) userdata.set('device_id', device_id) if settings.getBool('save_password', False): userdata.set('pswd', password) self._set_authentication() data = self._session.get(SUBSCRIPTIONS_URL.format(data['profileId'])).json() userdata.set('subscriptions', data['onlineSubscriptions']) def _renew_token(self): password = userdata.get('pswd') if password: self.login(userdata.get('username'), password) return data = { "deviceID": userdata.get('device_id'), "deviceIP": DEVICE_IP, "sessionToken": userdata.get('access_token'), } resp = self._session.post(RENEW_URL, json=data) data = resp.json() if resp.status_code != 200 or 'sessiontoken' not in data: raise APIError(_(_.RENEW_TOKEN_ERROR, message=data.get('message'))) userdata.set('access_token', data['sessiontoken']) self._set_authentication() def _get_play_token(self): self._renew_token() params = { 'profileId': userdata.get('device_id'), 'deviceId': userdata.get('device_id'), 'partnerId': 'skygo', 'description': 'ANDROID', } resp = self._session.get(TOKEN_URL, params=params) data = resp.json() if resp.status_code != 200 or 'token' not in data: raise APIError(_(_.TOKEN_ERROR, message=data.get('message'))) return data['token'] def play_media(self, id): token = self._get_play_token() params = { 'form': 'json', 'types': None, 'fields': 'id,content', 'byId': id, } data = self._session.get(PLAY_URL, params=params).json() videos = data['entries'][0]['media$content'] chosen = videos[0] for video in videos: if video['plfile$format'] == 'MPEG-DASH': chosen = video break url = '{}&auth={}&formats=mpeg-dash&tracking=true'.format(chosen['plfile$url'], token) pid = chosen['plfile$url'].split('?')[0].split('/')[-1] license = WIDEVINE_URL.format(token=token, pid=pid, challenge='B{SSM}') url = self._get_location(url) return url, license def _get_location(self, url): resp = self._session.get(url, allow_redirects=False) if resp.status_code != 302: data = resp.json() raise APIError(_(_.PLAY_ERROR, message=data.get('description'))) url = resp.headers.get('location') if 'faxs' in url: raise APIError(_.ADOBE_ERROR) return url def play_channel(self, id): token = self._get_play_token() url = PLAY_CHANNEL_URL.format(id=id, auth=token) url = self._get_location(url) return url def epg(self, ids, start=None, end=None): start = start or arrow.utcnow() end = end or start.shift(days=1) params = { 'startTimestamp': '{}000'.format(start.timestamp), 'endTimestamp': '{}000'.format(end.timestamp), 'channelIds': ','.join(ids), } return self._session.get(EPG_URL, params=params).json()['events'] def logout(self): userdata.delete('device_id') userdata.delete('access_token') userdata.delete('pswd') userdata.delete('subscriptions') self.new_session()
class API(object): def new_session(self): self.logged_in = False self._session = Session(HEADERS) self._set_authentication() def _set_authentication(self): access_token = userdata.get('access_token') if not access_token: return self._session.headers.update( {'authorization': 'Bearer {}'.format(access_token)}) self.logged_in = True def _oauth_token(self, data): token_data = self._session.post( 'https://auth.kayosports.com.au/oauth/token', json=data).json() if 'error' in token_data: raise APIError( _(_.LOGIN_ERROR, msg=token_data.get('error_description'))) account_status = None try: b64_string = token_data['access_token'].split('.')[1] b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #fix padding data = json.loads(b64decode(b64_string)) account_status = data['https://kayosports.com.au/status'][ 'account_status'] except: log.debug('Failed to get account status') if account_status and account_status != 'ACTIVE_SUBSCRIPTION': raise APIError(_.INVALID_SUBSCRIPTION) 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() def _refresh_token(self): if userdata.get('expires', 0) > time(): return payload = { "refresh_token": userdata.get('refresh_token'), "grant_type": "refresh_token", "client_id": CLIENTID, } self._oauth_token(payload) def login(self, username, password): payload = { "audience": "kayosports.com.au", "grant_type": "http://auth0.com/oauth/grant-type/password-realm", "scope": "openid offline_access", "realm": "prod-martian-database", "client_id": CLIENTID, "username": username, "password": password, } self._oauth_token(payload) def profiles(self): self._refresh_token() return self._session.get( 'https://profileapi.kayosports.com.au/user/profile').json() def sport_menu(self): return self._session.get( 'https://resources.kayosports.com.au/production/sport-menu/lists/default.json' ).json() def cdn_selection(self, media_type): return self._session.get( 'https://cdnselectionserviceapi.kayosports.com.au/android/usecdn/mobile/{}' .format(media_type)).json().get('useCDN') #landing has heros and panels def landing(self, name, **kwargs): params = { 'evaluate': 99, 'resourcesEnv': 'production', 'chromecastEnv': 'production', 'statsEnv': 'production', } params.update(**kwargs) return self._session.get( 'https://vccapi.kayosports.com.au/content/types/landing/names/{}'. format(name), params=params).json() #panel has shows and episodes def panel(self, id, **kwargs): params = { 'evaluate': 3, } params.update(**kwargs) return self._session.get( 'https://vccapi.kayosports.com.au/content/types/carousel/keys/{}'. format(id), params=params).json()[0] #show has episodes and panels def show(self, show_id, season_id=None, **kwargs): params = { 'evaluate': 3, 'showCategory': show_id, 'seasonCategory': season_id, } params.update(**kwargs) return self._session.get( 'https://vccapi.kayosports.com.au/content/types/landing/names/show', params=params).json() def event(self, id, **kwargs): params = { 'evaluate': 3, 'event': id, } params.update(**kwargs) return self._session.get( 'https://vccapi.kayosports.com.au/content/types/landing/names/event', params=params).json()[0]['contents'][0]['data']['asset'] def stream(self, asset): self._refresh_token() params = { 'fields': 'alternativeStreams', } data = self._session.post( 'https://vmndplay.kayosports.com.au/api/v1/asset/{}/play'.format( asset), params=params, json={}).json() if 'errors' in data: raise APIError(_(_.ASSET_ERROR, msg=data['errors'][0]['detail'])) return data['data'][0] def logout(self): userdata.delete('access_token') userdata.delete('refresh_token') userdata.delete('expires') userdata.delete('profile') self.new_session()
class API(object): def new_session(self): self.logged_in = False self._session = Session(HEADERS) self._set_authentication() def _set_authentication(self): access_token = userdata.get('access_token') if not access_token: return self._session.headers.update({'authorization': 'Bearer {}'.format(access_token)}) self.logged_in = True def _oauth_token(self, data): token_data = self._session.post('https://auth.kayosports.com.au/oauth/token', json=data).json() if 'error' in token_data: raise APIError(_(_.LOGIN_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']) self._set_authentication() def _refresh_token(self): if userdata.get('expires', 0) > time(): return payload = { "refresh_token": userdata.get('refresh_token'), "grant_type": "refresh_token", "client_id": CLIENTID, } self._oauth_token(payload) def login(self, username, password): payload = { "audience": "kayosports.com.au", "grant_type": "http://auth0.com/oauth/grant-type/password-realm", "scope": "openid offline_access", "realm": "prod-martian-database", "client_id": CLIENTID, "username": username, "password": password, } self._oauth_token(payload) def profiles(self): self._refresh_token() return self._session.get('https://profileapi.kayosports.com.au/user/profile').json() #landing has heros and panels def landing(self, name): params = { 'evaluate': 99, 'profile': userdata.get('profile'), } return self._session.get('https://vccapi.kayosports.com.au/content/types/landing/names/{}'.format(name), params=params).json() #panel has shows and episodes def panel(self, id): params = { #'evaluate': 3, 'profile': userdata.get('profile'), } return self._session.get('https://vccapi.kayosports.com.au/content/types/carousel/keys/{}'.format(id), params=params).json()[0] #show has episodes and panels def show(self, id): params = { 'evaluate': 99, 'profile': userdata.get('profile'), 'showCategory': id, } return self._session.get('https://vccapi.kayosports.com.au/content/types/landing/names/show', params=params).json() def event(self, id): params = { 'evaluate': 1, 'profile': userdata.get('profile'), 'event': id, } return self._session.get('https://vccapi.kayosports.com.au/content/types/landing/names/event', params=params).json()[0]['contents'][0]['data']['asset'] def sport(self, sport=None): params = { 'evaluate': 99, 'profile': userdata.get('profile'), 'sport': sport, } return self._session.get('https://vccapi.kayosports.com.au/content/types/landing/names/sport', params=params).json() def sport_menu(self, sport): return self._session.get('https://resources.kayosports.com.au/production/sport-menu/lists/{}.json'.format(sport)).json() def stream(self, asset): self._refresh_token() params = { 'fields': 'alternativeStreams', } data = self._session.post('https://vmndplay.kayosports.com.au/api/v1/asset/{}/play'.format(asset), params=params, json={}).json() if 'errors' in data: raise APIError(_(_.ASSET_ERROR, msg=data['errors'][0]['detail'])) return data['data'][0] def logout(self): userdata.delete('access_token') userdata.delete('refresh_token') userdata.delete('expires') userdata.delete('profile') self.new_session()
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, #'is_bookmarked': 'true' if value else 'false', } params.update(kwargs) data = self._session.post('/v1/user_media', params=params, json={}).json() print(data) def get_subtitles(self, captions): subtitles = [] for idx, caption in enumerate(captions): try: r = self._session.get(caption['file']) reader = detect_format(r.text) srt = SRTWriter().write(reader().read(r.text)) except: log.debug('Failed to parse subtitle: {}'.format( caption['file'])) else: srtfile = xbmc.translatePath( 'special://temp/curiosity{}.{}.srt'.format( idx, caption['code'])).decode('utf-8') with codecs.open(srtfile, "w", "utf-8") as f: f.write(srt) subtitles.append(srtfile) return subtitles def media(self, id): # params = { # 'showEncodings': 'Android', #limits to 1080p # 'encodingsNew': 'true', #breaks playback # } return self._session.get('/v1/media/{}'.format(id)).json()['data'] def logout(self): userdata.delete('token') mem_cache.empty() self.new_session()
class API(object): def new_session(self): self.logged_in = False 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 {0}'.format(token)}) self.logged_in = True def login(self, username, password): log('API: Login') data = {'response_type': 'token', 'lang': 'eng'} resp = self._session.get(LOGIN_URL, params=data) soup = BeautifulSoup(resp.text, 'html.parser') form = soup.find('form', id='new_signin') for e in form.find_all('input'): data[e.attrs['name']] = e.attrs.get('value') 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: self.logout() raise Error() self.set_access_token(access_token) data = self._session.get('user/current', params={'lang': 'eng'}).json() if 'error_code' in data: raise Error() device_id = hashlib.sha1(username).hexdigest().upper() userdata.set('device_id', device_id) userdata.set('access_token', access_token) userdata.set('user_id', data['user_id']) def catalogue(self, _params): def process_page(start): params = { 'field[]': [ 'id', 'images', 'title', 'items', 'total', 'type', 'description', 'videos' ], 'lang': 'eng', 'showmax_rating': 'adults', '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 shows(self): return self.catalogue({ 'type': 'tv_series', 'exclude_section[]': ['kids'], }) def movies(self): return self.catalogue({ 'type': 'movie', 'exclude_section[]': ['kids'], }) def kids(self): return self.catalogue({ 'section': 'kids', }) def show(self, show_id): params = { 'field[]': [ 'id', 'images', 'title', 'items', 'total', 'type', 'description', 'videos', 'number', 'seasons', 'episodes' ], 'lang': 'eng', 'showmax_rating': 'adults', 'subscription_status': 'full' } return self._session.get('catalogue/tv_series/{}'.format(show_id), params=params).json() def search(self, query): return self.catalogue({ 'q': query, }) def logout(self): log('API: Logout') userdata.delete('device_id') userdata.delete('access_token') userdata.delete('user_id') self.new_session() def play(self, video_id): params = { 'encoding': 'mpd_widevine_modular', 'subscription_status': 'full', 'lang': 'eng', } data = self._session.get('playback/play/{0}'.format(video_id), params=params).json() 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': 'adults', 'lang': 'eng'} data = self._session.post('playback/verify', params=params, data=data).json() license_request = data['license_request'] license_url = API_URL.format( 'drm/widevine_modular?license_request={0}'.format(license_request)) return url, license_url