def play(params): try: import drmhelper if not drmhelper.check_inputstream(drm=False): return except ImportError: utils.log("Failed to import drmhelper") utils.dialog_message('DRM Helper is needed for inputstream.adaptive ' 'playback. For more information, please visit: ' 'http://aussieaddons.com/drm') return try: stream = comm.get_stream(params['video_id']) utils.log('Attempting to play: {0} {1}'.format(stream['name'], stream['url'])) item = xbmcgui.ListItem(label=stream['name'], path=stream['url']) item.setProperty('inputstreamaddon', 'inputstream.adaptive') item.setProperty('inputstream.adaptive.manifest_type', 'hls') item.setMimeType('application/vnd.apple.mpegurl') item.setContentLookup(False) xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=item) except Exception: utils.handle_error('Unable to play video')
def router(paramstring): """ Router function that calls other functions depending on the provided paramstring :param paramstring: """ params = dict(parse_qsl(paramstring)) utils.log('Running with params: {0}'.format(params)) if params: if params['action'] == 'listcategories': if params['category'] == 'Live Matches': menu.list_matches(params, live=True) elif params['category'] == 'Settings': addon.openSettings() else: menu.list_videos(params) elif params['action'] in ['listvideos', 'listmatches']: play.play_video(params) elif params['action'] == 'clearticket': stream_auth.clear_ticket() elif params['action'] == 'open_ia_settings': try: import drmhelper if drmhelper.check_inputstream(drm=False): ia = drmhelper.get_addon() ia.openSettings() else: utils.dialog_message( "Can't open inputstream.adaptive settings") except Exception: utils.dialog_message( "Can't open inputstream.adaptive settings") else: menu.list_categories()
def get_embed_token(pai, bearer, video_id): """ send our user token to get our embed token, including api key """ url = config.TELSTRA_AUTH_URL.format(code=video_id, pai=pai) sess.headers = {} sess.headers.update({'Authorization': 'Bearer {0}'.format(bearer)}) try: req = sess.get(url) data = req.text json_data = json.loads(data) if json_data.get('ErrorCode') is not None: raise AussieAddonsException() embed_token = json_data.get('token') except requests.exceptions.HTTPError as e: utils.log('Error getting embed token. ' 'Response: {0}'.format(e.response.text)) cache.delete('SOCCERTICKET') if e.response.status_code == 401: raise AussieAddonsException('Login token has expired, ' 'please try again.') else: raise e return urllib.quote_plus(embed_token)
def get_media_auth_token(ticket, video_id): """ send our user token to get our embed token, including api key """ url = config.MEDIA_AUTH_URL.format(video_id=video_id) sess.headers = {} sess.headers.update({ 'X-YinzCam-Ticket': ticket, 'Accept': 'application/json' }) try: req = sess.get(url) data = req.text json_data = json.loads(data) if json_data.get('Fault'): raise AussieAddonsException( json_data.get('fault').get('faultstring')) media_auth_token = json_data.get('VideoToken') except requests.exceptions.HTTPError as e: utils.log('Error getting embed token. ' 'Response: {0}'.format(e.response.text)) cache.delete('NETBALLTICKET') if e.response.status_code == 401: raise AussieAddonsException('Login token has expired, ' 'please try again.') else: raise e return media_auth_token
def get_embed_token(user_token, video_id): """ send our user token to get our embed token, including api key """ url = config.EMBED_TOKEN_URL.format(video_id) sess.headers.update({ 'X-YinzCam-Ticket': user_token, 'Accept': 'application/json' }) try: req = sess.get(url) data = req.text json_data = json.loads(data) if json_data.get('ErrorCode') is not None: raise AussieAddonsException() video_token = json_data.get('VideoToken') except requests.exceptions.HTTPError as e: utils.log('Error getting embed token. ' 'Response: {0}'.format(e.response.text)) cache.delete('NETBALLTICKET') if e.response.status_code == 401: raise AussieAddonsException('Login token has expired, ' 'please try again.') else: raise e return urllib.quote(video_token)
def get_matches(): video_list = [] data = fetch_url(config.MATCHES_URL) try: video_data = json.loads(data) except ValueError: utils.log('Failed to load JSON. Data is: {0}'.format(data)) raise Exception('Failed to retrieve video data. Service may be ' 'currently unavailable.') for match in video_data['matchList']['matches']: live_streams = match['liveStreams'] # Use the thumb from the match article if available thumbnail = None if 'matchWrapArticle' in match: thumbnail = match['matchWrapArticle'].get('image') for ls in live_streams: # Only consider streams available in AU if 'AU' in [c['countryName'] for c in ls['streamCountriesList']]: name = "%s: %s v %s" % (match['series']['name'], match['homeTeam']['shortName'], match['awayTeam']['shortName']) video_list.append({ 'video_id': ls['id'], 'name': name, 'thumbnail': thumbnail or ls.get('thumbnailUrl') }) return video_list
def make_series_list(params): utils.log('Showing series list') try: series_list = comm.get_series_list(params) series_list.sort() ok = True for s in series_list: url = '{0}?action=list_series&{1}'.format(sys.argv[0], s.make_kodi_url()) listitem = xbmcgui.ListItem(s.title, iconImage=s.get_thumb(), thumbnailImage=s.get_thumb()) listitem.setInfo('video', {'plot': ''}) # add the item to the media list ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='tvshows') except Exception: utils.handle_error("Unable to fetch series listing")
def upload_log(): """Upload our full Kodi log as a GitHub gist""" try: log_content = get_kodi_log() except Exception as e: utils.log("Failed to read log: %s" % e) return utils.log('Sending log file...') try: data = { "files": { "kodi.log": { "content": log_content } } } response = urlopen(make_request(GIST_API_URL), json.dumps(data).encode('utf-8')) except HTTPError as e: utils.log("Failed to save log: HTTPError %s" % e.code) return False except URLError as e: utils.log("Failed to save log: URLError %s" % e.reason) return False try: return json.load(response)['html_url'] except Exception: utils.log("Failed to parse API response: %s" % response.read())
def get_series(): series_list = [] json_data = get_index() category_data = None for c in json_data['get']['response']['Menu']['children']: if c['name'] == 'Programs': category_data = c['children'] break series_data = None for s in category_data: if s['name'] == 'Programs A-Z': series_data = s['children'] break for entry in series_data: try: series = classes.Series() series.title = entry.get('name') series.id = entry.get('dealcodes') series.thumbnail = entry.get('thumbnail') series.num_episodes = int(entry.get('totalMpegOnly', 0)) if series.num_episodes > 0: series_list.append(series) except Exception: utils.log('Error parsing entry: %s' % entry) return series_list
def get_media_auth_token(pai, video_id): """ send our user token to get our embed token, including api key """ url = config.MEDIA_AUTH_URL.format(code=video_id, pai=pai) try: data = comm.fetch_url(url, request_token=True) json_data = json.loads(data) if json_data.get('Fault'): raise AussieAddonsException( json_data.get('fault').get('faultstring')) media_auth_token = json_data.get('urlSigningToken') except requests.exceptions.HTTPError as e: utils.log('Error getting embed token. ' 'Response: {0}'.format(e.response.text)) cache.delete('AFLTOKEN') if e.response.status_code in [400, 401]: raise AussieAddonsException('Login token has expired, ' 'please try again.') elif e.response.status_code == 404: raise AussieAddonsException( 'Unknown error, please wait a few moments and try again.') else: raise e return media_auth_token
def get_new_token(session): """send user login info and retrieve token for session""" utils.log('Retrieving new user token') username = addon.getSetting('LIVE_USERNAME') password = addon.getSetting('LIVE_PASSWORD') session.headers = config.BIGPOND_HEADERS url = config.BIGPOND_URL.format(username, password) auth_resp = session.post(url, logging=False) # make sure not to log this!! auth_json = json.loads(auth_resp.text) artifact = auth_json['data'].get('artifactValue') session.headers = config.APIGEE_HEADERS url = config.APIGEE_URL.format(urllib.quote(artifact)) token_resp = session.get(url) try: token_resp.raise_for_status() except requests.HTTPError as e: raise e token = json.loads(token_resp.text) session.headers.update({'ovs-token': token.get('tokenId'), 'ovs-uuid': token.get('uuid')}) cookies = json.dumps(session.cookies.get_dict()) expire = datetime.now() + timedelta(hours=2) cache.set('BIGPONDTOKEN', json.dumps(token)) cache.set('BIGPONDCOOKIES', cookies) cache.set('BIGPONDEXPIRE', str(expire)) return token
def play(url): try: addon = xbmcaddon.Addon() p = classes.Program() p.parse_xbmc_url(url) # Some programs don't have protected streams. 'Public' streams are # set in program.url, otherwise we fetch it separately if p.get_url(): stream_info = {} stream_url = p.get_url() else: stream_info = comm.get_stream(p.id) stream_url = stream_info['url'] bandwidth = addon.getSetting('BANDWIDTH') if bandwidth == '0': stream_url = stream_url.replace('&b=0-2000', '&b=400-600') elif bandwidth == '1': stream_url = stream_url.replace('&b=0-2000', '&b=900-1100') elif bandwidth == '2': stream_url = stream_url.replace('&b=0-2000', '&b=1400-1600') listitem = xbmcgui.ListItem(label=p.get_list_title(), iconImage=p.thumbnail, thumbnailImage=p.thumbnail, path=stream_url) listitem.setInfo('video', p.get_kodi_list_item()) # Add subtitles if available if 'subtitles' in stream_info: sub_url = stream_info['subtitles'] profile = addon.getAddonInfo('profile') path = xbmc.translatePath(profile).decode('utf-8') if not os.path.isdir(path): os.makedirs(path) subfile = xbmc.translatePath( os.path.join(path, 'subtitles.eng.srt')) if os.path.isfile(subfile): os.remove(subfile) try: data = urllib2.urlopen(sub_url).read() f = open(subfile, 'w') f.write(data) f.close() if hasattr(listitem, 'setSubtitles'): # This function only supported from Kodi v14+ listitem.setSubtitles([subfile]) except Exception: utils.log('Subtitles not available for this program') if hasattr(listitem, 'addStreamInfo'): listitem.addStreamInfo('audio', p.get_kodi_audio_stream_info()) listitem.addStreamInfo('video', p.get_kodi_video_stream_info()) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listitem) except Exception: utils.handle_error("Unable to play video")
def get_embed_token(login_ticket, videoId): """ send our user token to get our embed token, including api key """ data = create_nrl_userid_xml(userToken) url = config.EMBED_TOKEN_URL.format(videoId) try: req = sess.post(url, data=data, headers=config.YINZCAM_AUTH_HEADERS, verify=False) xml = req.text[1:] try: tree = ET.fromstring(xml) except ET.ParseError as e: utils.log('Embed token response is: {0}'.format(xml)) cache.delete('NRLTOKEN') raise e if tree.find('ErrorCode') is not None: utils.log('Errorcode found: {0}'.format(xml)) raise AussieAddonsException() token = tree.find('Token').text except AussieAddonsException: cache.delete('NRLTOKEN') raise Exception('Login token has expired, please try again') return token
def make_category_list(url): utils.log("Making category list") try: params = utils.get_url(url) category = params.get('category') categories = comm.get_category(category) utils.log(categories) ok = True for c in categories.get('rows'): if c.get('layout', {}).get('itemType') == 'genre': genre = 'True' else: genre = 'False' url = "%s?%s" % (sys.argv[0], utils.make_url({ 'category': category, 'genre': genre, 'feed_url': c.get('feedUrl')})) thumbnail = c.get('thumbnail', '') listitem = xbmcgui.ListItem(label=c['name'], thumbnailImage=thumbnail) ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='episodes') except Exception: utils.handle_error('Unable to build categories list')
def get_secure_token(secure_url, video_id): """send our embed token back with a few other url encoded parameters""" res = sess.get(secure_url) try: parsed_json = json.loads(res.text) except ValueError: utils.log('Failed to load JSON. Data is: {0}'.format(res.text)) if '<html' in res.text: # smart DNS redirects raise AussieAddonsException( 'Failed to get authorization token. Please ensure any smart ' 'DNS service is disabled.') else: raise Exception('Failed to get authorization token.') if parsed_json.get('authorized') is False: raise AussieAddonsException('Failed to get authorization token: {0}' ''.format(parsed_json.get('message'))) auth_data = parsed_json.get('authorization_data') utils.log('auth_data: %s' % auth_data) video = auth_data.get(video_id) if video.get('authorized') is False: raise AussieAddonsException('Failed to obtain secure token: {0}.\n' 'Check your subscription is valid.'.format( video.get('message'))) try: streams = video.get('streams') ios_token = streams[0]['url']['data'] return base64.b64decode(ios_token) except Exception as e: raise AussieAddonsException('Failed to get stream URL: {0}'.format(e))
def get_programs_list(params): """Fetch the episode list for a given series""" series_url = params.get('url') program_list = [] json_data = api_query(series_url) for item in json_data.get('items'): if item.get('title') == 'Shelf Container': for sub_item in item['items']: if not sub_item.get('items'): continue for sub_item_2 in sub_item['items']: for episode in sub_item_2.get('items'): p = classes.Program() p.title = episode['cardData']['image'].get('name') p.thumb = episode['cardData']['image'].get('url') p.description = episode['cardData'].get('synopsis') p.url = episode['playerData'].get('videoUrl') try: # Try parsing the date date = episode['infoPanelData'].get('airDate') timestamp = time.mktime( time.strptime(date, '%d &b %Y')) p.date = datetime.date.fromtimestamp(timestamp) except: pass utils.log('added program') program_list.append(p) return program_list
def make_rounds(params): utils.log('Making rounds list...') try: season = comm.get_seasons(season=params.get('season')) rounds = reversed(season.get('rounds')) for r in rounds: name = r.get('name') round_id = r.get('roundId') season_id = r.get('seasonId') listitem = xbmcgui.ListItem(label=name) url = '{0}?name={1}&round_id={2}&season_id={3}'.format( sys.argv[0], name, round_id, season_id) # Add the item to the list ok = xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True, totalItems=len(season.get('rounds'))) # send notification we're finished, successfully or unsuccessfully xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) except Exception: utils.handle_error('Unable to make round list')
def get_bc_url(video): config_data = json.loads(fetch_url(config.CONFIG_URL, request_token=True)) video.policy_key = [ x for x in config_data['general'] if x.get('id') == 'brightCovePK_premium' ][0].get('value') video.account_id = [ x for x in config_data['general'] if x.get('id') == 'brightCoveAccountId' ][0].get('value') if not video.policy_key: raise Exception("Can't retrieve brightcove policy key for {0}".format( video.account_id)) data = fetch_url(config.BC_EDGE_URL.format(account_id=video.account_id, video_id=video.video_id), headers={'BCOV-POLICY': video.policy_key}) json_data = json.loads(data) src = None for source in json_data.get('sources'): if source.get('type') in [ 'application/vnd.apple.mpegurl', 'application/x-mpegURL' ]: src = source.get('src') if src.startswith('https'): break if not src: utils.log(json.dumps(json_data.get('sources'))) raise Exception('Unable to locate video source.') return src
def get_stream_url(params, media_auth_token): bc_url = config.BC_URL.format(params.get('account_id'), params.get('video_id')) data = json.loads( fetch_url(bc_url, {'BCOV-POLICY': params.get('policy_key')})) src = None sources = data.get('sources') if len(sources) == 1: src = sources[0].get('src') else: for source in sources: tmp = source.get('src') if source.get('type') in [ 'application/vnd.apple.mpegurl', 'application/x-mpegURL' ]: if tmp.startswith('https'): src = tmp break if not src: utils.log(data.get('sources')) raise Exception('Unable to locate video source.') if not media_auth_token: return str(src) else: src = sign_url(src, media_auth_token) return str(src)
def get_secure_token(secure_url, videoId): """send our embed token back with a few other url encoded parameters""" res = session.get(secure_url) data = res.text try: parsed_json = json.loads(data) for stream in parsed_json['authorization_data'][videoId]['streams']: if stream.get('delivery_type') == 'dash': dash_url = stream['url'].get('data') wv_lic = stream.get('widevine_server_path') except KeyError: utils.log('Parsed json data: {0}'.format(parsed_json)) try: auth_msg = parsed_json['authorization_data'][videoId]['message'] if auth_msg == 'unauthorizedlocation': country = parsed_json['user_info']['country'] raise AussieAddonsException('Unauthorised location for ' 'streaming. ' 'Detected location is: {0}. ' 'Please check VPN/smart DNS ' 'settings and try again' ''.format(country)) except Exception as e: raise e return {'dash_url': base64.b64decode(dash_url), 'wv_lic': wv_lic}
def make_index_list(): print "hasdhsadhsa" utils.log('g2') try: utils.log('hello') index = comm.get_index()['contentStructure'].get('menu') ok = True for i in index: url = "%s?%s" % (sys.argv[0], utils.make_url({ 'category': i})) listitem = xbmcgui.ListItem(i) # Add the program item to the list ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True) listitem = xbmcgui.ListItem(label='Settings') ok = xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url="{0}?action=settings".format(sys.argv[0]), listitem=listitem, isFolder=False) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='episodes') except Exception: utils.handle_error('Unable to build index')
def make_rounds(params): utils.log('Making rounds list...') try: season = comm.get_seasons(season=params.get('season')) rounds = reversed(season.get('rounds')) for r in rounds: name = r.get('name') round_id = r.get('roundId') season_id = r.get('seasonId') listitem = xbmcgui.ListItem(label=name) url = '{0}?name={1}&round_id={2}&season_id={3}'.format( sys.argv[0], name, round_id, season_id) # Add the item to the list ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True, totalItems=len( season.get('rounds'))) # send notification we're finished, successfully or unsuccessfully xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) except Exception: utils.handle_error('Unable to make round list')
def get_stream_url(video, media_auth_token): if not video.type == 'B': headers = {'authorization': 'basic {0}'.format(get_authorization())} data = fetch_url(config.STREAM_API_URL.format(video_id=video.video_id), headers=headers) hls_url = json.loads(data).get('hls') return str(hls_url.replace('[[FILTER]]', 'nrl-vidset-ms')) else: bc_url = config.BC_URL.format(video.account_id, video.video_id) data = json.loads( fetch_url(bc_url, headers={'BCOV-POLICY': video.policy_key})) src = None sources = data.get('sources') if len(sources) == 1: src = sources[0].get('src') else: for source in sources: ext_ver = source.get('ext_x_version') src = source.get('src') if ext_ver == '4' and src: if src.startswith('https'): break if not src: utils.log(data.get('sources')) raise Exception('Unable to locate video source.') if not media_auth_token: return str(src) else: src = sign_url(src, media_auth_token) return src
def router(paramstring): """ Router function that calls other functions depending on the provided paramstring :param paramstring: """ params = dict(parse_qsl(paramstring)) utils.log('Running addon with params: {0}'.format(params)) if paramstring: if paramstring != 'content_type=video': if params['action'] == 'listcategories': if params['category'] == 'My Library': menu.list_library(params) elif params['category'] == 'Settings': addon.openSettings() else: menu.make_content_list(params) elif params['action'] == 'librarylist': if params['category'] == 'movies': menu.make_content_list(params) elif params['category'] == 'tv shows': menu.list_series(params) elif params['action'] == 'listseries': menu.make_content_list(params) elif params['action'] == 'listplayable': play.play_video(params) elif params['action'] == 'cleartoken': telstra_auth.clear_token() elif params['action'] == 'reinstall_widevine_cdm': drmhelper.get_widevinecdm() elif params['action'] == 'reinstall_ssd_wv': drmhelper.get_ssd_wv() else: menu.list_categories()
def make_entries_list(url): utils.log('Making entries list') try: params = utils.get_url(url) programs = comm.get_entries(params['entries_url']) ok = True for p in sorted(programs): listitem = xbmcgui.ListItem(label=p.get_list_title(), iconImage=p.get_thumbnail(), thumbnailImage=p.get_thumbnail()) listitem.setInfo('video', p.get_kodi_list_item()) listitem.setProperty('IsPlayable', 'true') if hasattr(listitem, 'addStreamInfo'): listitem.addStreamInfo('audio', p.get_kodi_audio_stream_info()) listitem.addStreamInfo('video', p.get_kodi_video_stream_info()) # Build the URL for the program, including the list_info url = "%s?play=true&%s" % (sys.argv[0], p.make_xbmc_url()) # Add the program item to the list ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=False, totalItems=len(programs)) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='episodes') except Exception: utils.handle_error('Unable to fetch program list')
def get_secure_token(secure_url, videoId): """ send our embed token back with a few other url encoded parameters """ res = sess.get(secure_url) data = res.text try: parsed_json = json.loads(data) token = (parsed_json['authorization_data'][videoId]['streams'][0] ['url']['data']) except KeyError: utils.log('Parsed json data: {0}'.format(parsed_json)) try: auth_msg = parsed_json['authorization_data'][videoId]['message'] if auth_msg == 'unauthorized location': country = parsed_json['user_info']['country'] raise AussieAddonsException('Unauthorised location for ' 'streaming. ' 'Detected location is: {0}. ' 'Please check VPN/smart DNS ' 'settings ' ' and try again'.format(country)) except Exception as e: raise e return base64.b64decode(token)
def get_collection_from_feed(params): keyword = params.get('collection_id') utils.log('Getting collection from feed ({0})'.format(params.get('title'))) feed = get_cached_feed( config.API_BASE_URL.format(path='/v2/collection/{0}'.format(keyword))) collection = parse.parse_programme_from_feed(feed, params) return collection
def make_entries_list(url): utils.log('Making entries list') try: params = utils.get_url(url) programs = comm.get_entries(params['feed_url']) ok = True for p in sorted(programs): listitem = xbmcgui.ListItem(label=p.get_list_title(), iconImage=p.get_thumbnail(), thumbnailImage=p.get_thumbnail()) if type(p) is classes.Program: listitem.setInfo('video', p.get_kodi_list_item()) listitem.setProperty('IsPlayable', 'true') #if hasattr(listitem, 'addStreamInfo'): listitem.addStreamInfo('audio', p.get_kodi_audio_stream_info()) listitem.addStreamInfo('video', p.get_kodi_video_stream_info()) # Build the URL for the program, including the list_info url = "%s?play=true&%s" % (sys.argv[0], p.make_xbmc_url()) else: url = "%s?%s" % (sys.argv[0], utils.make_url({ 'feed_url': p.feed_url})) # Add the program item to the list isFolder = type(p) is classes.Series ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=isFolder, totalItems=len(programs)) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='episodes') except Exception: utils.handle_error('Unable to fetch program list')
def get_secure_token(secure_url, video_id): """send our embed token back with a few other url encoded parameters""" res = sess.get(secure_url) try: parsed_json = json.loads(res.text) except ValueError: utils.log('Failed to load JSON. Data is: {0}'.format(res.text)) if '<html' in res.text: # smart DNS redirects raise AussieAddonsException( 'Failed to get authorization token. Please ensure any smart ' 'DNS service is disabled.') else: raise Exception('Failed to get authorization token.') if parsed_json.get('authorized') is False: raise AussieAddonsException( 'Failed to get authorization token: {0}' ''.format(parsed_json.get('message'))) auth_data = parsed_json.get('authorization_data') utils.log('auth_data: %s' % auth_data) video = auth_data.get(video_id) if video.get('authorized') is False: raise AussieAddonsException( 'Failed to obtain secure token: {0}.\n' 'Check your subscription is valid.'.format(video.get('message'))) try: streams = video.get('streams') stream_url = base64.b64decode(streams[0]['url']['data']) widevine_url = streams[0].get('widevine_server_path') return {'stream_url': stream_url, 'widevine_url': widevine_url} except Exception as e: raise AussieAddonsException( 'Failed to get stream URL: {0}'.format(e))
def get_stream_url(hn, path): with session.Session() as sess: video_url = config.API_BASE_URL.format(path='/v2{0}'.format(path)) utils.log("Fetching stream URL: {0}".format(video_url)) video_data = sess.get(video_url).text video_json = json.loads(video_data) if video_json.get('playable') is False: return { 'msg': video_json.get('playableMessage'), 'availability': video_json.get('availability') } sess.headers = {'User-Agent': config.USER_AGENT} for playlist in video_json['_embedded']['playlist']: if playlist.get('type') not in ['program', 'livestream']: continue if 'hls' in playlist.get('streams'): hls_streams = playlist['streams'].get('hls') stream_url_base = hls_streams.get( '720', hls_streams.get('sd', hls_streams.get('sd-low'))) if stream_url_base: captions_url = playlist.get('captions', {}).get('src-vtt') break akamai_auth = get_auth(hn, sess) request = sess.get(stream_url_base, params={'hdnea': akamai_auth}) cookies = cookies_to_string(request.cookies) stream_url = '{0}|User-Agent={1}&Cookie={2}'.format( request.url, quote_plus(config.USER_AGENT), quote_plus(cookies)) return {'stream_url': stream_url, 'captions_url': captions_url}
def make_category_list(url): utils.log("Making category list") try: params = utils.get_url(url) categories = comm.get_category(params['category']) ok = True if 'children' in categories: for c in categories.get('children', []): if 'children' in c: url = "%s?%s" % (sys.argv[0], utils.make_url({ 'category': params['category'], 'section': c['name']})) elif 'url' in c: url = "%s?%s" % (sys.argv[0], utils.make_url({ 'entries_url': c['url']})) else: continue # If no thumbnail, make it an empty string as None makes # XBMC v12 with a TypeError must be unicode or str thumbnail = c.get('thumbnail', '') listitem = xbmcgui.ListItem(label=c['name'], thumbnailImage=thumbnail) ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True) else: # 'Coming soon' has no children jsonurl = categories['url'] programs = comm.get_entries(jsonurl) for p in sorted(programs): thumbnail = p.get_thumbnail() listitem = xbmcgui.ListItem(label=p.get_list_title(), iconImage=thumbnail, thumbnailImage=thumbnail) listitem.setInfo('video', p.get_kodi_list_item()) if hasattr(listitem, 'addStreamInfo'): listitem.addStreamInfo('audio', p.get_kodi_audio_stream_info()) listitem.addStreamInfo('video', p.get_kodi_video_stream_info()) # Build the URL for the program, including the list_info url = "%s?play=true&%s" % (sys.argv[0], p.make_xbmc_url()) # Add the program item to the list ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=False, totalItems=len(programs)) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='episodes') except Exception: utils.handle_error('Unable to build categories list')
def get_collections_from_feed(params): utils.log('Getting collections from feed ({0})'.format( params.get('category'))) feed = get_cached_feed( config.API_BASE_URL.format( path='/v2{0}'.format(params.get('category')))) collects = parse.parse_collections_from_feed(feed, params) return collects
def save_last_error_report(error): """Save a copy of our last error report""" try: rfile = os.path.join(utils.get_file_dir(), 'last_report_error.txt') with open(rfile, 'w') as f: f.write(error) except Exception: utils.log("Error writing error report file")
def parse_m3u8(data, m3u8_path, qual=-1, live=False): """ Parse the retrieved m3u8 stream list into a list of dictionaries then return the url for the highest quality stream. """ ver = 0 if '#EXT-X-VERSION:3' in data: ver = 3 data.remove('#EXT-X-VERSION:3') if '#EXT-X-VERSION:4' in data: ver = 4 data.remove('#EXT-X-VERSION:4') if '#EXT-X-INDEPENDENT-SEGMENTS' in data: data.remove('#EXT-X-INDEPENDENT-SEGMENTS') count = 1 m3u_list = [] while count < len(data): if ver == 3 or ver == 0: line = data[count] line = line.strip('#EXT-X-STREAM-INF:') line = line.strip('PROGRAM-ID=1,') if 'CODECS' in line: line = line[:line.find('CODECS')] if line.endswith(','): line = line[:-1] line = line.strip() line = line.split(',') linelist = [i.split('=') for i in line] if live: url = urlparse.urljoin(m3u8_path, data[count + 1]) else: url = data[count + 1] linelist.append(['URL', url]) m3u_list.append(dict((i[0], i[1]) for i in linelist)) count += 2 if ver == 4: line = data[count] line = line.strip('#EXT-X-STREAM-INF:') line = line.strip('PROGRAM-ID=1,') values = line.split(',') for value in values: if value.startswith('BANDWIDTH'): bw = value elif value.startswith('RESOLUTION'): res = value url = urlparse.urljoin(m3u8_path, data[count + 1]) m3u_list.append(dict([bw.split('='), res.split('='), ['URL', url]])) count += 3 sorted_m3u_list = sorted(m3u_list, key=lambda k: int(k['BANDWIDTH'])) utils.log('Available streams are: {0}'.format(sorted_m3u_list)) try: stream = sorted_m3u_list[qual]['URL'] except IndexError: # less streams than we expected - go with highest stream = sorted_m3u_list[-1]['URL'] return stream
def parse_m3u8_streams(data, live, secure_token_url): """ Parse the retrieved m3u8 stream list into a list of dictionaries then return the url for the highest quality stream. Different handling is required of live m3u8 files as they seem to only contain the destination filename and not the domain/path. """ if live: qual = int(addon.getSetting('LIVEQUALITY')) if qual == config.MAX_LIVEQUAL: qual = -1 # fix for values too high from previous API config if qual > config.MAX_LIVEQUAL: addon.setSetting('LIVEQUALITY', str(config.MAX_LIVEQUAL)) qual = -1 else: qual = int(addon.getSetting('REPLAYQUALITY')) if qual == config.MAX_REPLAYQUAL: qual = -1 # fix for values too high from previous API config if qual > config.MAX_REPLAYQUAL: addon.setSetting('REPLAYQUALITY', str(config.MAX_REPLAYQUAL)) qual = -1 if '#EXT-X-VERSION:3' in data: data.remove('#EXT-X-VERSION:3') count = 1 m3u_list = [] prepend_live = secure_token_url[:secure_token_url.find('index-root')] while count < len(data): line = data[count] line = line.strip('#EXT-X-STREAM-INF:') line = line.strip('PROGRAM-ID=1,') line = line[:line.find('CODECS')] if line.endswith(','): line = line[:-1] line = line.strip() line = line.split(',') linelist = [i.split('=') for i in line] if not live: linelist.append(['URL', data[count + 1]]) else: linelist.append(['URL', prepend_live + data[count + 1]]) m3u_list.append(dict((i[0], i[1]) for i in linelist)) count += 2 sorted_m3u_list = sorted(m3u_list, key=lambda k: int(k['BANDWIDTH'])) try: stream = sorted_m3u_list[qual]['URL'] except IndexError as e: utils.log('Quality setting: {0}'.format(qual)) utils.log('Sorted m3u8 list: {0}'.format(sorted_m3u_list)) raise e return stream
def create_seasons_list(seasons, thumb): listing = [] for entry in seasons.get('feeds'): try: s = create_season(entry, thumb) listing.append(s) except Exception: utils.log('Error parsing season entry') return listing
def getData(self, url, headers={}, name=None, noCache=False, expiry=None, data=None): now = datetime.datetime.now() if not headers or not headers.get('User-Agent'): headers['User-Agent'] = USER_AGENT if noCache or not name: data = CacheObj.fetch_url(url=url, headers=headers) if not name: return data if not data: rawData = self.win.getProperty('%s|%s' % (name, url)) if rawData: try: cachedData = eval(rawData) if not isinstance(expiry, datetime.timedelta): expiry = CacheObj.maxTTL if cachedData[0] + CacheObj.minTTL < now: bgFetch = Thread(target=self.getData, kwargs=dict(url=url, headers=headers, name=name, noCache=True)) bgFetch.start() if cachedData[0] + expiry > now: return cachedData[1] except Exception as e: utils.log('Error with eval of cached data: {0}'.format(e)) data = CacheObj.fetch_url(url=url, headers=headers) cachedData = (now, data) self.win.setProperty('%s|%s' % (name, url), repr(cachedData)) rawURLs = self.win.getProperty('%s|urls' % name) urls = eval(rawURLs) if rawURLs else deque() if url in urls: urls.remove(url) urls.append(url) if len(urls) > CacheObj.maxEntries: oldUrl = urls.popleft() self.win.clearProperty('%s|%s' % (name, oldUrl)) self.win.setProperty('%s|urls' % name, repr(urls)) return cachedData[1]
def get_category(category): """Fetch a given top level category from the index. This is usually programs and movies. """ utils.log("Fetching category: %s" % category) index = get_index() index2 = index.get('contentStructure') index3 = index2.get('screens') return index.get('contentStructure').get('screens').get(category)
def fetch_session_id(url, data): """send http POST and return the json response data""" data = urllib.urlencode(data) sess.headers = config.HEADERS comm.update_token(sess) res = sess.post(url, data) try: res.raise_for_status() except requests.exceptions.HTTPError as e: utils.log(e.response.text) raise Exception(e) return res.text
def fetch_url(url, headers={}): """ Use custom session to grab URL and return the text """ with session.Session(force_tlsv1=True) as sess: res = sess.get(url, headers=headers) try: data = json.loads(res.text) return data except ValueError as e: utils.log('Error parsing JSON, response is {0}'.format(res.text)) raise e
def get_stream_url(hn, url): utils.log("Fetching stream URL: {0}".format(url)) with session.Session() as sess: sess.headers = {'User-Agent': config.USER_AGENT} akamai_auth = get_auth(hn, sess) akamai_url = "{0}?hdnea={1}".format(url, akamai_auth) request = sess.get(akamai_url) cookies = cookies_to_string(request.cookies) stream_url = '{0}|User-Agent={1}&Cookie={2}'.format( akamai_url, urllib.quote(config.USER_AGENT), urllib.quote(cookies)) return stream_url
def fetch_auth_token(): """Perform a HTTP POST to secure server to fetch a token""" utils.log('Fetching new auth token') try: sess = session.Session() request = sess.post(config.token_url) request.raise_for_status() data = json.loads(request.text) token = data['sessiontoken']['response']['token'] return token except Exception as e: raise Exception('Failed to fetch SBS streaming token: %s' % e)
def fetch_related_list(urls, listobj): utils.log('Downloading episode listings (%d) ...' % len(urls)) threads = [] listobj.extend([None for x in range(len(urls))]) for index, url in enumerate(urls): thread = threading.Thread(target=fetch_related, args=(url, listobj, index)) thread.daemon = True thread.start() threads.append(thread) for thread in threads: thread.join()
def parse_m3u8_streams(data, live, secure_token_url): """Parse m3u8 stream Parse the retrieved m3u8 stream list into a list of dictionaries then return the url for the highest quality stream. Different handling is required of live m3u8 files as they seem to only contain the destination filename and not the domain/path. """ if live: qual = int(xbmcaddon.Addon().getSetting('LIVE_QUALITY')) if qual == config.MAX_LIVE_QUAL: qual = -1 else: qual = int(xbmcaddon.Addon().getSetting('REPLAYQUALITY')) if qual == config.MAX_REPLAY_QUAL: qual = -1 m3u_list = [] base_url = secure_token_url[:secure_token_url.rfind('/') + 1] base_domain = secure_token_url[:secure_token_url.find('/', 8) + 1] m3u8_lines = iter(data) for line in m3u8_lines: stream_inf = '#EXT-X-STREAM-INF:' if line.startswith(stream_inf): line = line[len(stream_inf):] else: continue csv_list = re.split(',(?=(?:(?:[^"]*"){2})*[^"]*$)', line) linelist = [i.split('=') for i in csv_list] uri = next(m3u8_lines) if uri.startswith('/'): linelist.append(['URL', base_domain + uri]) elif uri.find('://') == -1: linelist.append(['URL', base_url + uri]) else: linelist.append(['URL', uri]) m3u_list.append(dict((i[0], i[1]) for i in linelist)) sorted_m3u_list = sorted(m3u_list, key=lambda k: int(k['BANDWIDTH'])) try: stream = sorted_m3u_list[qual]['URL'] except IndexError as e: utils.log('Quality setting: {0}'.format(qual)) utils.log('Sorted m3u8 list: {0}'.format(sorted_m3u_list)) raise e return stream
def get_entries(url): resp = fetch_cache_url(url) json_data = json.loads(resp) programs_list = [] for entry in json_data.get('itemListElement'): try: if entry.get('type') == 'TVSeries': p = create_series(entry) else: p = create_program(entry) programs_list.append(p) except Exception: utils.log('Error parsing entry') return programs_list
def get_categories(url): # TODO(andy): Switch to use this function resp = fetch_cache_url(url) json_data = json.loads(resp) series_list = [] for entry in json_data['entries']: try: series = classes.Series() series.title = entry.get('name') series.id = entry.get('dealcodes') series.thumbnail = entry.get('thumbnail') series.num_episodes = int(entry.get('totalMpegOnly', 0)) if series.num_episodes > 0: series_list.append(series) except Exception: utils.log('Error parsing entry: %s' % entry) return series_list
def router(paramstring): """ Router function that calls other functions depending on the provided paramstring :param paramstring: """ params = dict(parse_qsl(paramstring)) utils.log('Running with params: {0}'.format(params)) if params: if params['action'] == 'listcategories': if params['category'] == 'Live Matches': menu.list_matches(params, live=True) elif params['category'] == 'Settings': addon.openSettings() else: menu.list_videos(params) elif params['action'] in ['listvideos', 'listmatches']: play.play_video(params) elif params['action'] == 'clearticket': ooyalahelper.clear_ticket() else: menu.list_categories()
def get_embed_token(user_token, video_id): """send our user token to get our embed token, including api key""" try: comm.update_token(sess) embed_token_url = config.EMBED_TOKEN_URL.format(user_token, video_id) utils.log("Fetching embed token: {0}".format(embed_token_url)) try: res = sess.get(embed_token_url) except requests.exceptions.SSLError: cache.delete('AFLTOKEN') raise AussieAddonsException( 'Your version of Kodi is too old to support live streaming. ' 'Please upgrade to the latest version.') except requests.exceptions.HTTPError as e: if subscription_type == 0: # paid afl.com.au/linked cache.delete('AFLTOKEN') raise AussieAddonsException( 'Paid or linked subscription not found for supplied username ' 'and password. Please check the subscription type in settings ' 'is correct or your subscription has been linked to your ' 'Telstra ID in the Android/iOS app') elif subscription_type in [1, 3]: # free/mobile sub cache.delete('AFLTOKEN') utils.log(e.response.text) if e.response.status_code == 400: raise AussieAddonsException( 'Stored login token has expired, please try to play this ' 'item again. If this error persists please submit an ' 'issue on our github (github.com/aussieaddons/plugin.' 'video.afl-video') else: raise e else: # in-app purchase/manual raise AussieAddonsException( 'mis-uuid token is invalid, please check the token in ' 'the settings is correct and try again') data = json.loads(res.text) return urllib.quote(data.get('token'))
def fetch_bc_url(url, headers={}): """ Use fetch_url and catch Brightcove API errors """ try: data = fetch_url(url=url, headers=headers) return data except requests.exceptions.HTTPError as e: utils.log(e.response.text) if e.response.status_code == 403: try: error_data = json.loads(e.response.text) if error_data[0].get('error_subcode') == 'CLIENT_GEO': raise AussieAddonsException( 'Content is geoblocked, your detected country is: {0}' ''.format(error_data[0].get('client_geo'))) else: raise e except IndexError: raise e except ValueError: raise e else: raise e
def make_categories_list(): utils.log('Showing category list') try: categories_list = comm.get_categories() categories_list.sort() categories_list.insert(0, classes.Category(title='Live TV')) categories_list.append(classes.Category(title='Settings')) for c in categories_list: url = '{0}?action=list_categories&{1}'.format(sys.argv[0], c.make_kodi_url()) listitem = xbmcgui.ListItem(label=c.title, iconImage=c.get_thumb(), thumbnailImage=c.get_thumb()) ok = xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True) xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=ok) xbmcplugin.setContent(handle=int(sys.argv[1]), content='tvshows') except Exception: utils.handle_error("Unable to show category listing")
def get_series_from_feed(series_url, ep_count): utils.log('Fetching series from feed') feed = get_feed(series_url) return parse.parse_programs_from_feed(feed, ep_count)
def get_programme_from_feed(keyword): keyword = validate_category(keyword) utils.log('Getting programme from feed (%s)' % keyword) feed = get_feed(keyword) shows = parse.parse_programme_from_feed(feed, keyword) return shows
import hmac import json import parse import requests import threading import time import urllib from aussieaddonscommon import exceptions from aussieaddonscommon import session from aussieaddonscommon import utils try: import StorageServer except ImportError: utils.log('script.common.plugin.cache not found!') import storageserverdummy as StorageServer cache = StorageServer.StorageServer(utils.get_addon_id(), 1) def fetch_url(url, headers=None): """Simple function that fetches a URL using requests.""" with session.Session() as sess: if headers: sess.headers.update(headers) request = sess.get(url) try: request.raise_for_status() except Exception as e: # Just re-raise for now
import base64 import classes import config import datetime import json import uuid from aussieaddonscommon import session from aussieaddonscommon import utils from bs4 import BeautifulSoup try: import StorageServer except ImportError: utils.log("script.common.plugin.cache not found!") import storageserverdummy as StorageServer cache = StorageServer.StorageServer(utils.get_addon_id(), 1) def fetch_url(url, headers=None): """Simple function that fetches a URL using requests.""" with session.Session(force_tlsv1=True) as sess: if headers: sess.headers.update(headers) request = sess.get(url) try: request.raise_for_status() except Exception as e: # Just re-raise for now
programs.make_programs_list(params_str) elif 'category' in params: if params['category'] == 'settings': xbmcaddon.Addon().openSettings() else: series.make_series_list(params_str) elif 'action' in params: if params['action'] == 'sendreport': utils.user_report() elif params['action'] == 'update_ia': try: import drmhelper addon = drmhelper.get_addon(drm=False) if not drmhelper.is_ia_current(addon, latest=True): if xbmcgui.Dialog().yesno( 'Upgrade?', ('Newer version of inputstream.adaptive ' 'available ({0}) - would you like to ' 'upgrade to this version?'.format( drmhelper.get_latest_ia_ver()))): drmhelper.get_ia_direct(update=True, drm=False) else: ver = addon.getAddonInfo('version') utils.dialog_message('Up to date: Inputstream.adaptive ' 'version {0} installed and enabled.' ''.format(ver)) except ImportError: utils.log("Failed to import drmhelper") utils.dialog_message('DRM Helper is needed for this function. ' 'For more information, please visit: ' 'http://aussieaddons.com/drm')
def get_user_token(): """Send user login info and retrieve token for session""" # in-app purchase/manual if subscription_type == 2: iap_token = addon.getSetting('IAP_TOKEN').lower() try: int(iap_token, 16) except ValueError: raise AussieAddonsException( 'mis-uuid token must be 32 characters in length, and only ' 'contain numbers 0-9 and letters a-f') if len(iap_token) != 32: raise AussieAddonsException( 'mis-uuid token must be 32 characters in length, and only ' 'contain numbers 0-9 and letters a-f') token = 'mis-uuid-{0}'.format(iap_token) utils.log('Using manual token: {0}******'.format(token[:-6])) return token stored_token = cache.get('AFLTOKEN') if stored_token: utils.log('Using token: {0}******'.format(stored_token[:-6])) return stored_token if addon.getSetting('LIVE_SUBSCRIPTION') == 'true': username = addon.getSetting('LIVE_USERNAME') password = addon.getSetting('LIVE_PASSWORD') if subscription_type == 1: # free subscription token = telstra_auth.get_token(username, password) elif subscription_type == 3: # mobile activated subscription token = telstra_auth.get_mobile_token() else: # paid afl.com.au login_data = {'userIdentifier': addon.getSetting('LIVE_USERNAME'), 'authToken': addon.getSetting('LIVE_PASSWORD'), 'userIdentifierType': 'EMAIL'} login_json = fetch_session_id(config.LOGIN_URL, login_data) data = json.loads(login_json) utils.log(data) if data.get('responseCode') != 0: raise AussieAddonsException('Invalid Telstra ID login/' 'password for paid afl.com.au ' '/ linked subscription.') session_id = data['data'].get('artifactValue') try: sess.headers.update({'Authorization': None}) encoded_session_id = urllib.quote(session_id) session_url = config.SESSION_URL.format(encoded_session_id) res = sess.get(session_url) data = json.loads(res.text) token = data.get('uuid') except requests.exceptions.HTTPError as e: utils.log(e.response.text) raise e cache.set('AFLTOKEN', token) utils.log('Using token: {0}******'.format(token[:-6])) return token else: raise AussieAddonsException('AFL Live Pass subscription is required ' 'for this content. Please open the ' 'add-on settings to enable and configure.')
def get_section(category, section): utils.log("Fetching section: %s" % section) category = get_category(category) for s in category['children']: if s['name'] == section: return s
def get_program(params): """Fetch the program information and stream URL for a given program ID""" utils.log('Fetching program information for: {0}'.format( params.get('title'))) program = classes.Program() program.parse_xbmc_url(sys.argv[2]) program_url = program.format_url(params.get('url')) data = fetch_url(program_url) dash_preferred = ADDON.getSetting('dash_enabled') == 'true' try: program_data = json.loads(data) except Exception: utils.log("Bad program data: %s" % program_data) raise Exception("Error decoding program information.") if 'text_tracks' in program_data.get('media'): if len(program_data['media'].get('text_tracks')) == 0: utils.log("No subtitles available for this program") else: utils.log("Subtitles are available for this program") program.subtitle = (program_data['media']['text_tracks'][0] .get('src')) for source in program_data['media'].get('sources'): # Get DASH URL if source.get('type') == 'application/dash+xml': if 'hbbtv' in source.get('src'): continue program.dash_url = source.get('src') program.dash_preferred = dash_preferred utils.log('Found DASH stream: {0}'.format(program.dash_url)) if 'key_systems' in source: if 'com.widevine.alpha' in source['key_systems']: program.drm_key = (source['key_systems'] ['com.widevine.alpha'] ['license_url']) utils.log('DASH stream using Widevine') # Get HLS URL elif source.get('type') in ['application/x-mpegURL', 'application/vnd.apple.mpegurl']: if 'key_systems' in source: continue if source.get('ext_x_version') != '5': program.hls_url = source.get('src') utils.log('Found HLS stream: {0}'.format(program.hls_url)) return program
def fetch_cache_token(): """Get the token from cache is possible""" utils.log('Fetching cached token if possible') return cache.cacheFunction(fetch_auth_token)