def login_wrapper(*args, **kwargs): with PersistentDict("creds") as db: username = db.get("username") password = db.get("password") # token is 5 days old ? with PersistentDict("headers") as db: headers = db.get("headers") if headers: return func(*args, **kwargs) elif username and password: login(username, password) return func(*args, **kwargs) else: Script.notify( 'Login Error', 'You need to login with Jio Username and password to use this add-on' ) executebuiltin( "RunPlugin(plugin://plugin.video.jiotv/resources/lib/main/login/)" ) return False
def resolveKey(self): global headers key = self.path.split('/')[-1] effective_url = "https://tv.media.jio.com/streams_live/{0}/{1}".format( self.channel_name, key) if not headers: headers = utils.getHeaders() resp = ChannelRequestHandler.make_requests( effective_url, headers, max_age=urlquick.MAX_AGE) if resp.status_code == 411 or resp.status_code == 451: Script.notify( "Error", "JioTV can not be accessed from outside India") self.proxy.send_error(resp.status_code) elif resp.status_code >= 400: self.proxy.send_error(resp.status_code) else: self.proxy.send_response(resp.status_code, 'OK') self.proxy.send_header('Content-Type', 'application/octet-stream') self.proxy.send_header('Connection', 'keep-alive') self.proxy.send_header('Content-Length', len(resp.content)) self.proxy.end_headers() self.proxy.wfile.write(resp.content)
def import_needed_module(): # Import needed module according to the # base URL and query string (Fix for Kodi favorite item and search) modules_to_import = [get_module_in_url(sys.argv[0])] if 'codequick/search' in sys.argv[0]: modules_to_import.append(get_module_in_query(sys.argv[2])) for module_to_import in modules_to_import: if module_to_import == '': # No additionnal module to load continue # Need to load additional module try: Script.log( '[cq_utils.import_needed_module] Import module {} on the fly'. format(module_to_import), lvl=Script.INFO) importlib.import_module(module_to_import) except Exception: Script.log( '[cq_utils.import_needed_module] Failed to import module {} on the fly' .format(module_to_import), lvl=Script.WARNING)
def get_item_label(item_id): """Get (translated) label of 'item_id' Args: item_id (str) Returns: str: (translated) label of 'item_id' """ label = item_id if item_id in LABELS: label = LABELS[item_id] if isinstance(label, int): label = Script.localize(label) return label
def item_post_treatment(item, is_playable=False, is_downloadable=False): """Add needed context menus to 'item' Args: is_playable (bool): If 'item' is playable is_downloadable (bool): If 'item' is downloadable Returns: str: (translated) label of 'item_id' """ # Add `Download` context menu to 'item' if 'item' is downloadable if is_downloadable: item.context.script(item.path, Script.localize(30503), download_mode=True, **item.params) # Add `Add to add-on favourites` context menu to 'item' item.context.script(fav.add_item_to_favourites, Script.localize(30800), is_playable=is_playable) return
def migrate_from_pickled_fav(): """ This function moves existing pickled favourites in the new json file used for the favourites. The new format (json) appeared in 0.2.17~beta04 All user with version >= 0.2.17 will use favourites in the JSON format Maybe we can remove the migration check on version 0.2.20? """ # Move all pickled existing favs in json file fav_pickle_fp = os.path.join(Script.get_info('profile'), "favourites.pickle") if xbmcvfs.exists(fav_pickle_fp): Script.log('Start favourites migration from pickle file to json file') new_fav_dict = {} with storage.PersistentDict("favourites.pickle") as db: new_fav_dict = dict(db) # Fix old fav for item_hash, item_dict in new_fav_dict.items(): if 'params' in item_dict and isinstance(item_dict['params'], listing.Params): new_fav_dict[item_hash]['params'] = dict( new_fav_dict[item_hash]['params']) try: del new_fav_dict[item_hash]['params']['item_dict'][ 'params'] except Exception: pass if 'properties' in item_dict: if isinstance(item_dict['properties'], listing.Property): new_fav_dict[item_hash]['properties'] = dict( new_fav_dict[item_hash]['properties']) fav_json_fp = os.path.join(Script.get_info('profile'), "favourites.json") with open(fav_json_fp, 'w') as f: json.dump(new_fav_dict, f, indent=4) xbmcvfs.delete(fav_pickle_fp)
def grab_programmes(country_id, day_delta): """Retrieve programmes of channels of country_id at day today + day_detla. Args: country_id (str) day_delta (int) Returns: list: Programmes. """ if country_id not in xmltv_infos: return [] try: # Download, if needed, xmltv file xmltv_fp = download_xmltv_file(country_id, day_delta=day_delta) # Grab programmes in xmltv file programmes = read_programmes(xmltv_fp, only_current_programmes=False) programmes_post_treated = [] for programme in programmes: programmes_post_treated.append(programme_post_treatment_iptvmanager(programme)) return programmes_post_treated except Exception as e: Script.log('xmltv module failed with error: {}'.format(e), lvl=Script.ERROR) return []
def get_video_url(plugin, item_id, next_url, download_mode=False, video_label=None): resp = urlquick.get(next_url, headers={'User-Agent': web_utils.get_random_ua}, max_age=-1) json_parser = json.loads(resp.text) if json_parser["detail"]["informations"]['consumptionPlatform'] == 'HAPI': Script.notify("INFO", plugin.localize(LABELS['drm_notification']), Script.NOTIFY_INFO) return False stream_url = '' for stream_datas in json_parser["detail"]["informations"]["videoURLs"]: if stream_datas["encryption"] == 'clear': stream_url = stream_datas["videoURL"] if download_mode: return download.download_video(stream_url, video_label) return stream_url
def buildPlay(self, playbackUrl, licenceUrl=None, playbackProto="mpd", label="", drm=False): is_helper = inputstreamhelper.Helper("mpd", drm=drm) if is_helper.check_inputstream(): stream_headers = HotstarAPI._getPlayHeaders( playbackUrl=playbackUrl) subtitleUrl = re.sub( "(.*)(master[\w+\_\-]*?\.[\w+]{3})([\w\/\?=~\*\-]*)", "\g<1>subtitle/lang_en/subtitle.vtt\g<3>", playbackUrl) + "|User-Agent=Hotstar%3Bin.startv.hotstar%2F3.3.0+%28Android%2F8.1.0%29" Script.log(subtitleUrl, lvl=Script.DEBUG) if licenceUrl and "/hms/wv/" in licenceUrl: drm = "com.widevine.alpha" return Listitem().from_dict(**{ "label": label, "callback": playbackUrl, "properties": { "IsPlayable": True, "inputstream": is_helper.inputstream_addon, "inputstream.adaptive.manifest_type": playbackProto, "inputstream.adaptive.license_type": drm, "inputstream.adaptive.stream_headers": urlencode(stream_headers), "inputstream.adaptive.license_key": licenceUrl and licenceUrl + '|%s&Content-Type=application/octet-stream|R{SSM}|' % urlencode(stream_headers) }, "subtitles": [subtitleUrl] }) return False
def getM3U8(self): m3u8 = self.path.split('/')[-1] effective_url = "{4}{0}/{1}{2}/{3}".format( self.hlsrx, self.channel_name, '_HLS' if self.ishls else '', m3u8, SERVER) resp = ChannelRequestHandler.make_requests(effective_url) if resp.status_code == 411 or resp.status_code == 451: Script.notify( "Error", "JioTV can not be accessed from outside India") self.proxy.send_error(resp.status_code) else: if self.ishls: resp_text = self.refine(resp.text, quality=m3u8[:1]) else: rq = int(re.search('(.*?_)(\d+)\.m3u8', m3u8).group(2)) quality = rq >= self.maxq and self.maxq resp_text = self.refine(resp.text, quality) self.proxy.send_response(resp.status_code, 'OK') resp.headers.pop("Content-Length") for k, v in resp.headers.items(): self.proxy.send_header(k, v) self.proxy.send_header('Content-Length', len(resp_text)) self.proxy.end_headers() self.proxy.wfile.write(resp_text)
def _findPlayback(playBackSets, encryption="widevine"): for each in playBackSets: Script.log("Checking combination %s for encryption %s" % (each.get("tagsCombination"), encryption), lvl=Script.DEBUG) if re.match( ".*?encryption:%s.*?(ladder:tv)?.*?package:dash.*" % encryption, each.get("tagsCombination")): Script.log( "Found Stream! URL : %s LicenceURL: %s Encryption: %s" % (each.get("playbackUrl"), each.get("licenceUrl"), encryption), lvl=Script.DEBUG) return (each.get("playbackUrl"), each.get("licenceUrl"), "mpd") elif re.match( ".*?encryption:plain.*?(ladder:tv)?.*?package:dash.*", each.get("tagsCombination")): Script.log( "Found Stream! URL : %s LicenceURL: %s Encryption: %s" % (each.get("playbackUrl"), each.get("licenceUrl"), "plain"), lvl=Script.DEBUG) return (each.get("playbackUrl"), each.get("licenceUrl"), "mpd") elif re.match(".*?encryption:plain.*?(ladder:tv)?.*?package:hls.*", each.get("tagsCombination")): Script.log( "Found Stream! URL : %s LicenceURL: %s Encryption: %s" % (each.get("playbackUrl"), each.get("licenceUrl"), "plain"), lvl=Script.DEBUG) return (each.get("playbackUrl"), each.get("licenceUrl"), "hls") playbackUrl = playBackSets[0].get("playbackUrl") licenceUrl = playBackSets[0].get("licenceUrl") Script.log("No stream found for desired config. Using %s" % playbackUrl, lvl=Script.INFO) return (playbackUrl, licenceUrl, "hls" if ".m3u8" in playbackUrl else "mpd")
def delete_favourites(plugin): """ Callback function of 'Delete favourites' setting button """ Script.log('Delete favourites db') xbmcvfs.delete( os.path.join(Script.get_info('profile'), 'favourites.pickle')) Script.notify(Script.localize(30374), '')
def add_fav_context(item, item_dict, **kwargs): """ Add the 'Add to add-on favourites' context menu to item """ if kwargs.get('is_playable', False): item_dict['params']['is_folder'] = False item_dict['params']['is_playable'] = True else: item_dict['params']['is_folder'] = True item_dict['params']['is_playable'] = False item.context.script(add_item_to_favourites, Script.localize(LABELS['Add to add-on favourites']), item_dict=item_dict, **kwargs)
def _handleError(self, e, url, **kwargs): if e.__class__.__name__ == "ValueError": Script.log("Can not parse response of request url %s" % url, lvl=Script.DEBUG) Script.notify("Internal Error", "") elif e.__class__.__name__ == "HTTPError": if e.code == 419: executebuiltin( "RunPlugin(plugin://plugin.video.jiotvplus/resources/lib/main/login/)" ) else: raise urlquick.HTTPError(e.filename, e.code, e.msg, e.hdrs) else: Script.log("Got unexpected response for request url %s" % url, lvl=Script.DEBUG) Script.notify( "API Error", "Raise issue if you are continuously facing this error")
def get_item_label(item_id, item_infos={}): """Get (translated) label of 'item_id' Args: item_id (str) item_infos (dict): Information from the skeleton 'menu' dict Returns: str: (translated) label of 'item_id' """ if 'label' in item_infos: label = item_infos['label'] else: label = item_id if isinstance(label, int): label = Script.localize(label) return label
def grab_json_collections(plugin, json_url, page=0, collection_position=None, **kwargs): plugin.add_sort_methods(xbmcplugin.SORT_METHOD_UNSORTED) r = urlquick.get(json_url, params={'platform': 'apps', 'page': str(page)}) j = json.loads(r.text) cnt = -1 items = [] if 'collections' in j: collections = j['collections'] else: collections = [j] for collection in collections: cnt = cnt + 1 next_page_item = None if 'cursor' in collection: if 'next' in collection['cursor']: next_ = collection['cursor']['next'] if next_: next_page_item = Listitem.next_page( json_url, page=next_, collection_position=cnt) # If we are not in page 0, directly print items if collection_position is not None and cnt == collection_position: return list_generic_items(plugin, collection['items'], next_page_item) else: item = Listitem() if set_item_callback_based_on_type(item, collection['type'], collection, next_page_item): items.append(item) if 'item' in j and 'program_path' in j['item']: item = Listitem() item.label = Script.localize(30701) # All videos item.set_callback(grab_json_collections, URL_API_MOBILE('/generic/taxonomy/%s/contents' % j['item']['program_path']), page=0, collection_position=0) item_post_treatment(item) items.append(item) return items
def download_video(video_url): """Callback function of the 'Download' context menu Args: video_url (str): URL of the video to download """ # print('URL Video to download ' + video_url) # Now that we have video URL we can try to download this one YDStreamUtils = __import__('YDStreamUtils') YDStreamExtractor = __import__('YDStreamExtractor') vid = YDStreamExtractor.getVideoInfo( video_url, quality=get_quality_YTDL(download_mode=True), resolve_redirects=True) path = Script.setting.get_string('dl_folder') download_ok = False with YDStreamUtils.DownloadProgress() as prog: try: YDStreamExtractor.setOutputCallback(prog) result = YDStreamExtractor.handleDownload( vid, bg=Script.setting.get_boolean('dl_background'), path=path) if result: if result.status == 'canceled': error_message = result.message Script.log('Download failed: %s' % error_message) else: full_path_to_file = result.filepath Script.log('Download success: %s' % full_path_to_file) download_ok = True finally: YDStreamExtractor.setOutputCallback(None) if path != '' and \ Script.setting.get_boolean('dl_item_filename') and \ download_ok: try: filename = os.path.basename(full_path_to_file) _, file_extension = os.path.splitext(full_path_to_file) current_filepath = os.path.join(path, filename) video_name = get_selected_item_label() final_filepath = os.path.join(path, video_name + file_extension) xbmcvfs.rename(current_filepath, final_filepath) except Exception: Script.log('Failed to rename video file') return False
def doLogin(self): with PersistentDict("userdata.pickle") as db: try: username = Settings.get_string( "username", "plugin.video.jiotv") or db.get("username") or Dialog( ).input("Username (MobileNo / Email)") password = Settings.get_string( "password", "plugin.video.jiotv") or db.get( "password") or Dialog().input("Password") except RuntimeError: username = Dialog().input("Username (MobileNo / Email)") password = Dialog().input("Password") if username and password: body = { "identifier": username if '@' in username else "+91" + username, "password": password, "rememberUser": "******", "upgradeAuth": "Y", "returnSessionDetails": "T", "deviceInfo": { "consumptionDeviceName": "Jio", "info": { "type": "android", "platform": { "name": "vbox86p", "version": "8.0.0" }, "androidId": "6fcadeb7b4b10d77" } } } resp = urlquick.post( "https://api.jio.com/v3/dip/user/unpw/verify", json=body, headers={ "x-api-key": "l7xx75e822925f184370b2e25170c5d5820a" }, verify=False, raise_for_status=False).json() if resp.get("ssoToken"): db["data"] = resp Script.notify("Login Successful", "You have been logged in successfully") else: Script.notify( "Login Failed", "Double check you username and password and try again") else: Script.notify("Login Required", "Please login with you Jio credentials")
def clear_cache(plugin): # Callback function of clear cache setting button # Clear urlquick cache urlquick.cache_cleanup(-1) Script.notify(plugin.localize(30371), '') # Remove all tv guides dirs, files = xbmcvfs.listdir(Script.get_info('profile')) for fn in files: if '.xml' in fn and fn != 'settings.xml': Script.log('Remove xmltv file: {}'.format(fn)) xbmcvfs.delete(os.path.join(Script.get_info('profile'), fn))
def item_post_treatment(item, **kwargs): """ Optional keyworded arguments: - is_playable (bool) (default: False) - is_downloadable (bool) (default: False) """ # Add `Download` context menu to the item # if is_downloadable is given and True if kwargs.get('is_downloadable', False): item.context.script(item.path, Script.localize(LABELS['Download']), download_mode=True, **item.params) # Add `Add to favourites` context menu to the item add_fav_context(item, item2dict(item), **kwargs) return
def save_tv_integration_settings(j): """Save tv integration settings dict in json file Args: j (dict): Tv integration dict to save """ try: with open(TV_INTEGRATION_SETTINGS_FP, 'w') as f: json.dump(j, f, indent=4) except Exception as e: Script.notify(Script.localize(30270), Script.localize(30276)) Script.log('Failed to save TV integration settings (%s)', e, lvl=Script.ERROR)
def import_ovpn(*args, **kwargs): path = xbmcgui.Dialog().browse( 1, Script.localize(LABELS['Import OpenVPN configuration file']), 'files', mask='.ovpn|.conf', enableMultiple=False) if path and os.path.exists(path) and os.path.isfile(path): Script.log('OpenVPN: Import: [%s]' % path) keyboard = xbmc.Keyboard( default='', heading=Script.localize( LABELS['Choose a name for OpenVPN configuration']), hidden=False) keyboard.doModal() if keyboard.isConfirmed() and len(keyboard.getText()) > 0: name = keyboard.getText() ovpnfiles = {} with storage.PersistentDict('vpn') as db: ovpnfiles = db['ovpnfiles'] db.flush() if name in ovpnfiles and not xbmcgui.Dialog().yesno( 'OpenVPN', Script.localize(LABELS[ 'This OpenVPN configuration name already exists. Overwrite?'] )): xbmcgui.Dialog().ok( 'OpenVPN', Script.localize(LABELS['Import cancelled'])) else: ovpnfiles[name] = path with storage.PersistentDict('vpn') as db: db['ovpnfiles'] = ovpnfiles db.flush() else: xbmcgui.Dialog().ok( 'OpenVPN', Script.localize(LABELS[ 'Import failed. You must specify a name for the OpenVPN configuration'] ))
def get_quality_YTDL(download_mode=False): """Get YoutTubeDL quality setting Args: download_mode (bool) Returns: int: YoutTubeDL quality """ # If not download mode get the 'quality' setting if not download_mode: quality = Script.setting.get_string('quality') if quality == 'BEST': return 3 if quality == 'DEFAULT': return 3 if quality == 'DIALOG': youtubeDL_qualiy = ['SD', '720p', '1080p', 'Highest Available'] selected_item = xbmcgui.Dialog().select(Script.localize(30709), youtubeDL_qualiy) return selected_item return 3 # Else we need to use the 'dl_quality' setting dl_quality = Script.setting.get_string('dl_quality') if dl_quality == 'SD': return 0 if dl_quality == '720p': return 1 if dl_quality == '1080p': return 2 if dl_quality == 'Highest available': return 3 return 3
def geoip(): """Get country code based on IP address Returns: str: Country code (e.g. FR) """ # better service - https://geoftv-a.akamaihd.net/ws/edgescape.json try: resp = urlquick.get('https://geoftv-a.akamaihd.net/ws/edgescape.json', max_age=-1) data = json.loads(resp.text) if 'reponse' in data: return data['reponse']['geo_info']['country_code'] except Exception: pass Script.notify(Script.get_info('name'), Script.localize(30724), icon=Script.NOTIFY_WARNING) Script.log('Failed to get country code based on IP address', lvl=Script.WARNING) return None
def delete_ovpn(*args, **kwargs): ovpnfiles = {} with storage.PersistentDict('vpn') as db: try: ovpnfiles = db['ovpnfiles'] except KeyError: db['ovpnfiles'] = ovpnfiles db.flush() if len(ovpnfiles) == 0: return None else: response = vpnlib.is_running(ip, port) Script.log('OpenVPN: Response from is_running: [%s] [%s] [%s]' % (response[0], response[1], response[2])) if response[0]: # Le VPN est connecté Script.log('OpenVPN: VPN still connected, we disconnect it') disconnect_openvpn() configs = [] ovpnfileslist = [] for name, configfilepath in list(ovpnfiles.items()): configs.append(name) ovpnfileslist.append(configfilepath) idx = xbmcgui.Dialog().select( Script.localize(LABELS['Select OpenVPN configuration to delete']), configs) if idx >= 0: Script.log('Select: [%s]' % ovpnfileslist[idx]) new_ovpnfiles = {} for name, configfilepath in list(ovpnfiles.items()): if configfilepath != ovpnfileslist[idx]: new_ovpnfiles[name] = configfilepath with storage.PersistentDict('vpn') as db: db['ovpnfiles'] = new_ovpnfiles db.flush() else: return ''
def _findPlayback(playBackSets, encryption="widevine"): for each in playBackSets: Script.log("Checking combination: %s" % each.get("tagsCombination"), lvl=Script.DEBUG) if re.search( "encryption:%s.*?ladder:tv.*?package:dash" % encryption, each.get("tagsCombination")): Script.log("Found Stream! URL : %s LicenceURL: %s" % (each.get("playbackUrl"), each.get("licenceUrl")), lvl=Script.DEBUG) return (each.get("playbackUrl"), each.get("licenceUrl"), "mpd") playbackUrl = playBackSets[0].get("playbackUrl") Script.log("No stream found for desired config. Using %s" % playbackUrl, lvl=Script.DEBUG) return (playbackUrl, None, "hls" if "master.m3u8" in playbackUrl else "mpd")
def import_ovpn(*args, **kwargs): path = xbmcgui.Dialog().browse( 1, Script.localize(30342), 'files', mask='.ovpn|.conf', enableMultiple=False) if path and os.path.exists(path) and os.path.isfile(path): Script.log('OpenVPN: Import: [%s]' % path) keyboard = xbmc.Keyboard( default='', heading=Script.localize(30348), hidden=False) keyboard.doModal() if keyboard.isConfirmed() and len(keyboard.getText()) > 0: name = keyboard.getText() ovpnfiles = {} with storage.PersistentDict('vpn') as db: ovpnfiles = db['ovpnfiles'] db.flush() if name in ovpnfiles and not xbmcgui.Dialog().yesno( 'OpenVPN', Script.localize(30349)): xbmcgui.Dialog().ok( 'OpenVPN', Script.localize(30350)) else: ovpnfiles[name] = path with storage.PersistentDict('vpn') as db: db['ovpnfiles'] = ovpnfiles db.flush() else: xbmcgui.Dialog().ok( 'OpenVPN', Script.localize(30351))
def get_item_media_path(item_media_path): """Get full path or URL of an item_media Args: item_media_path (str or list): Partial media path of the item (e.g. channels/fr/tf1.png) Returns: str: Full path or URL of the item_pedia """ full_path = '' # Local image in ressources/media folder if type(item_media_path) is list: full_path = os.path.join(Script.get_info("path"), "resources", "media", *(item_media_path)) # Remote image with complete URL elif 'http' in item_media_path: full_path = item_media_path # Image in our resource.images add-on else: full_path = 'resource://resource.images.catchuptvandmore/' + item_media_path return utils.ensure_native_str(full_path)
def get_item_media_path(item_media_path): """Get full path or URL of an item_media Args: item_media_path (str or list): Partial media path of the item (e.g. channels/fr/tf1.png) Returns: str: Full path or URL of the item_pedia """ full_path = '' # Local image in ressources/media folder if type(item_media_path) is list: full_path = os.path.join(Script.get_info("path"), "resources", "media", *(item_media_path)) # Remote image with complete URL elif 'http' in item_media_path: full_path = item_media_path # Remote image on our images repo else: full_path = 'https://github.com/Catch-up-TV-and-More/images/raw/master/' + item_media_path return utils.ensure_native_str(full_path)
def get_item_label(item_id, item_infos={}, append_selected_lang=True): """Get (translated) label of 'item_id' Args: item_id (str) item_infos (dict): Information from the skeleton 'menu' dict append_selected_lang (bool, optional): Append selected language by the user in the label Returns: str: (translated) label of 'item_id' """ if 'label' in item_infos: label = item_infos['label'] else: label = item_id if isinstance(label, int): label = Script.localize(label) if append_selected_lang and 'available_languages' in item_infos: label = '{} ({})'.format( label, utils.ensure_unicode( Script.setting['{}.language'.format(item_id)])) return label