def __auth_ip(self, media_id): header = i18n('flashx_auth_header') line1 = i18n('auth_required') line2 = i18n('visit_link') line3 = i18n('click_pair') % 'http://flashx.tv/pair' with common.kodi.CountdownDialog(header, line1, line2, line3, countdown=120) as cd: return cd.start(self.__check_auth, [media_id])
def __auth_ip(self, media_id): header = i18n('stream_auth_header') line1 = i18n('auth_required') line2 = i18n('visit_link') line3 = i18n('click_pair') % ('http://api.streamin.to/pair') with common.kodi.CountdownDialog(header, line1, line2, line3) as cd: return cd.start(self.__check_auth, [media_id])
def __auth_ip(self, media_id): header = i18n('vevio_auth_header') line1 = i18n('auth_required') line2 = i18n('visit_link') line3 = i18n('click_pair') % 'https://vev.io/pair' with common.kodi.CountdownDialog(header, line1, line2, line3) as cd: return cd.start(self.__check_auth, [media_id])
def __auth_ip(self, media_id): js_data = self.__get_json(INFO_URL) pair_url = js_data.get('result', {}).get('auth_url', '') if pair_url: pair_url = pair_url.replace('\/', '/') header = i18n('ol_auth_header') line1 = i18n('auth_required') line2 = i18n('visit_link') line3 = i18n('click_pair').decode('utf-8') % (pair_url) with common.kodi.CountdownDialog(header, line1, line2, line3) as cd: return cd.start(self.__check_auth, [media_id])
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() # xml.append('<setting id="%s_autopick" type="bool" label="%s" default="false"/>' % ( # cls.__name__, i18n('auto_primary_link'))) xml.append( '<setting id="%s_auth" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_ad)"/>' % ( cls.__name__, i18n('auth_my_account'))) xml.append( '<setting id="%s_reset" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_ad)"/>' % ( cls.__name__, i18n('reset_my_auth'))) xml.append('<setting id="%s_token" visible="false" type="text" default=""/>' % cls.__name__) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_auto_update" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('auto_update'))) xml.append( '<setting id="%s_url" type="text" label=" %s" default="" visible="eq(-1,true)"/>' % (cls.__name__, i18n('update_url'))) xml.append( '<setting id="%s_key" type="text" label=" %s" default="" option="hidden" visible="eq(-2,true)"/>' % (cls.__name__, i18n('decrypt_key'))) xml.append( '<setting id="%s_etag" type="text" default="" visible="false"/>' % (cls.__name__)) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_use_https" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('use_https'))) xml.append( '<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append( '<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('customer_id'))) xml.append( '<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('pin'))) return xml
def authorize_resolver(self): if exists(self.cookie_file): remove(self.cookie_file) if not self.get_setting('username') or not self.get_setting('password'): username = common.kodi.get_keyboard(i18n('username')) password = common.kodi.get_keyboard(i18n('password'), hide_input=True) if username and password: self.set_setting('username', username) self.set_setting('password', password) login_query = '?{0}'.format(urlencode({'username': username, 'password': password})) else: raise ResolverError('Linksnappy Error: {0}'.format('Did not provide both username and password')) else: login_query = '?{0}'.format( urlencode({'username': self.get_setting('username'), 'password': self.get_setting('password')}) ) response = self.net.http_GET(url=''.join([authenticate, login_query]), headers=self.headers).content res = json.loads(response) if 'OK' in res.get('status'): self.net.save_cookies(self.cookie_file) self.__update_timestamp() common.kodi.notify(msg=i18n('ls_authorized')) return True elif 'ERROR' in res.get('status'): self.set_setting('username', '') self.set_setting('password', '') remove(self.cookie_file) raise ResolverError('Linksnappy Error: {0}'.format(res.get('error')))
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_client_id" type="text" label="%s" default="%s"/>' % (cls.__name__, i18n('client_id'), 'am6l6dn0x3bxrdgc557p1qeg1ma3bto')) return xml
def get_media_url(self, host, media_id): web_url = self.get_url(host, media_id) html = self.net.http_GET(web_url, headers=self.headers).content if html: encoded = re.search( '''srces\.push\(\s*{type:"video/mp4",src:\w+\('([^']+)',(\d+)''', html) if encoded: source = self.decode(encoded.group(1), int(encoded.group(2))) if source: source = "http:%s" % source if source.startswith( "//") else source source = source.split("/") if not source[-1].isdigit(): source[-1] = re.sub('[^\d]', '', source[-1]) source = "/".join(source) self.headers.update({'Referer': web_url}) return source + helpers.append_headers(self.headers) try: if not self.__file_exists(media_id): raise ResolverError('File Not Available') video_url = self.__check_auth(media_id) if not video_url: video_url = self.__auth_ip(media_id) except ResolverError: raise if video_url: return video_url + helpers.append_headers(self.headers) else: raise ResolverError(i18n('no_ip_authorization'))
def _auto_update(self, py_source, py_path, key=''): try: if self.get_setting('auto_update') == 'true' and py_source: headers = self.net.http_HEAD(py_source).get_headers(as_dict=True) common.logger.log(headers) old_etag = self.get_setting('etag') new_etag = headers.get('Etag', '') old_len = common.file_length(py_path, key) new_len = int(headers.get('Content-Length', 0)) py_name = os.path.basename(py_path) if old_etag != new_etag or old_len != new_len: common.logger.log('Updating %s: |%s|%s|%s|%s|' % (py_name, old_etag, new_etag, old_len, new_len)) self.set_setting('etag', new_etag) new_py = self.net.http_GET(py_source).content if new_py: if key: new_py = common.decrypt_py(new_py, key) if new_py and 'import' in new_py: with open(py_path, 'w') as f: f.write(new_py.encode('utf-8')) common.kodi.notify('%s %s' % (self.name, common.i18n('resolver_updated'))) else: common.logger.log('Reusing existing %s: |%s|%s|%s|%s|' % (py_name, old_etag, new_etag, old_len, new_len)) common.log_file_hash(py_path) except Exception as e: common.logger.log_warning('Exception during %s Auto-Update code retrieve: %s' % (self.name, e))
def pick_source(sources, auto_pick=None): if auto_pick is None: auto_pick = common.get_setting('auto_pick') == 'true' if len(sources) == 1: return sources[0][1] elif len(sources) > 1: if auto_pick: return sources[0][1] else: result = xbmcgui.Dialog().select(common.i18n('choose_the_link'), [str(source[0]) if source[0] else 'Unknown' for source in sources]) if result == -1: raise ResolverError(common.i18n('no_link_selected')) else: return sources[result][1] else: raise ResolverError(common.i18n('no_video_link'))
def authorize_resolver(self): url = 'https://uptobox.com/api/streaming' js_data = json.loads(self.net.http_GET(url, headers=self.headers).content) if js_data.get('message') == 'Success': js_data = js_data.get('data') heading = i18n('uptobox_auth_header') line1 = i18n('auth_required') line2 = i18n('upto_link').format(js_data.get('base_url')) line3 = i18n('upto_pair').format(js_data.get('pin')) with common.kodi.CountdownDialog(heading, line1, line2, line3, True, js_data.get('expired_in'), 10) as cd: js_result = cd.start(self.__check_auth, [js_data.get('check_url')]) # cancelled if js_result is None: return return self.__get_token(js_result) raise ResolverError('Error during authorisation.')
def get_media_url(self, host, media_id): result = self.__check_auth(media_id) if not result: result = self.__auth_ip(media_id) if result: return helpers.pick_source(result.items()) + helpers.append_headers(self.headers) raise ResolverError(i18n('no_ip_authorization'))
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append( '<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append( '<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append( '<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append( '<setting id="%s_premium" enable="eq(-3,true)" type="bool" label="Premium Account" default="false"/>' % (cls.__name__)) xml.append( '<setting id="%s_session_id" visible="false" type="text" default=""/>' % (cls.__name__)) return xml
def get_media_url(self, host, media_id): result = self.__check_auth(media_id) if not result: result = self.__auth_ip(media_id) if result: return helpers.get_media_url(result, patterns=['''src:\s*["'](?P<url>[^"']+).+?res:\s*(?P<label>\d+)'''], result_blacklist=["trailer"], generic_patterns=False).replace(' ', '%20') raise ResolverError(i18n('no_ip_authorization'))
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_torrents" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('torrents'))) xml.append( '<setting id="%s_cached_only" enable="eq(-1,true)" type="bool" label="%s" default="false" />' % (cls.__name__, i18n('cached_only'))) xml.append( '<setting id="%s_auth" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_pm)"/>' % (cls.__name__, i18n('auth_my_account'))) xml.append( '<setting id="%s_reset" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_pm)"/>' % (cls.__name__, i18n('reset_my_auth'))) xml.append( '<setting id="%s_token" visible="false" type="text" default=""/>' % cls.__name__) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append( '<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append( '<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append( '<setting id="%s_ts_offset" type="int" visible="false" enable="false"/>' % (cls.__name__)) xml.append( '<setting id="%s_token" type="text" visible="false" enable="false"/>' % (cls.__name__)) xml.append( '<setting id="%s_key" type="text" visible="false" enable="false"/>' % (cls.__name__)) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append( '<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append( '<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append( '<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append( '<setting id="%s_auth" type="action" label="%s" enable="!eq(-1,)+!eq(-2,)+!eq(-3,false)" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_ad)"/>' % (cls.__name__, i18n('auth_my_account'))) xml.append( '<setting id="%s_reset" type="action" label="%s" enable="!eq(-2,)+!eq(-3,)+!eq(-4,false)" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_ad)"/>' % (cls.__name__, i18n('reset_my_auth'))) xml.append( '<setting id="%s_token" visible="false" type="text" default=""/>' % (cls.__name__)) return xml
def get_response(img): try: img = xbmcgui.ControlImage(450, 0, 400, 130, img) wdlg = xbmcgui.WindowDialog() wdlg.addControl(img) wdlg.show() common.kodi.sleep(3000) solution = common.kodi.get_keyboard(common.i18n('letters_image')) if not solution: raise Exception('captcha_error') finally: wdlg.close()
def get_settings_xml(cls): ''' This method should return XML which describes the settings you would like for your plugin. You should make sure that the ``id`` starts with your plugins class name (which can be found using :attr:`cls.__name__`) followed by an underscore. Override this method if you want your plugin to have more settings than just 'priority'. If you do and still want the defaults settings you should call this method from the base class first. Returns: A list containing XML elements that will be valid in settings.xml ''' xml = [ '<setting id="%s_priority" type="number" label="%s" default="100"/>' % (cls.__name__, common.i18n('priority')), '<setting id="%s_enabled" ' 'type="bool" label="%s" default="true"/>' % (cls.__name__, common.i18n('enabled')) ] return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="{0}_username" enable="eq(-1,true)" type="text" label="{1}" visible="false" default=""/>'.format( cls.__name__, i18n('username') ) ) xml.append( '<setting id="{0}_password" enable="eq(-2,true)" type="text" label="{1}" visible="false" default=""/>'.format( cls.__name__, i18n('password') ) ) xml.append( '<setting id="{0}_auth" type="action" label="{1}" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_ls)" option="close"/>'.format( cls.__name__, i18n('auth_my_account') ) ) xml.append( '<setting id="{0}_reset" type="action" label="{1}" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_ls)"/>'.format( cls.__name__, i18n('reset_my_auth') ) ) xml.append( '<setting id="{0}_cached_files_only" type="bool" label="{1}" default="false" />'.format( cls.__name__, i18n('cached_files_only') ) ) xml.append( '<setting id="{0}_torrents" type="bool" label="{1}" default="true"/>'.format( cls.__name__, i18n('torrents') ) ) xml.append( '<setting id="{0}_cached_only" enable="eq(-1,true)" type="bool" label="{1}" default="true" />'.format( cls.__name__, i18n('cached_only') ) ) xml.append( '<setting id="{0}_expiration_timestamp" label="Linksnappy expiration timestamp" visible="false" type="text" default=""/>'.format( cls.__name__ ) ) return xml
def get_media_url(self, host, media_id): url = self.get_url(host, media_id) result = json.loads(self.net.http_GET(url, headers=self.headers).content) if result.get('message') == 'Success': js_data = result.get('data') if 'streamLinks' in js_data.keys(): js_result = result else: heading = i18n('uptobox_auth_header') line1 = i18n('auth_required') line2 = i18n('upto_link').format(js_data.get('base_url')) line3 = i18n('upto_pair').format(js_data.get('pin')) with common.kodi.CountdownDialog(heading, line1, line2, line3, True, js_data.get('expired_in'), 10) as cd: js_result = cd.start(self.__check_auth, [js_data.get('check_url')]) if js_result.get('data').get('token'): self.set_setting('token', js_result.get('data').get('token')) self.set_setting('premium', 'true') if js_result: js_result = js_result.get('data').get('streamLinks') sources = [(key, list(js_result.get(key).values())[0]) for key in list(js_result.keys())] return helpers.pick_source(helpers.sort_sources_list(sources)) + helpers.append_headers(self.headers) raise ResolverError('The requested video was not found or may have been removed.')
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append( '<setting id="%s_autopick" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('auto_primary_link'))) xml.append( '<setting id="%s_auth" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_rd)"/>' % (cls.__name__, i18n('auth_my_account'))) xml.append( '<setting id="%s_reset" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_rd)"/>' % (cls.__name__, i18n('reset_my_auth'))) xml.append( '<setting id="%s_token" visible="false" type="text" default=""/>' % (cls.__name__)) xml.append( '<setting id="%s_refresh" visible="false" type="text" default=""/>' % (cls.__name__)) xml.append( '<setting id="%s_client_id" visible="false" type="text" default=""/>' % (cls.__name__)) xml.append( '<setting id="%s_client_secret" visible="false" type="text" default=""/>' % (cls.__name__)) return xml
def get_media_url(self, host, media_id): web_url = self.get_url(host, media_id) headers = {'Referer': web_url} headers.update(self.headers) html = self.net.http_GET(web_url, headers=headers).content sources = helpers.scrape_sources(html) if sources: auth = self.__check_auth(media_id) if not auth: auth = self.__auth_ip(media_id) if auth: return helpers.pick_source(sources) + helpers.append_headers(headers) else: raise ResolverError(i18n('no_ip_authorization')) else: raise ResolverError('Unable to locate links')
def get_media_url(self, host, media_id): url = helpers.get_media_url(self.get_url(host, media_id), result_blacklist=['intro_black']).replace(' ', '%20') net = common.Net() headers = {} if '|' in url: qs_header_split = url.split('|') url = qs_header_split[0] headers = urlparse.parse_qs(qs_header_split[1]) headers = dict((k, v[0]) for k, v in headers.iteritems()) response = net.http_HEAD(url, headers=headers) if(response.get_url()): return response.get_url() + helpers.append_headers(headers) else: raise ResolverError(common.i18n('no_video_link'))
def get_media_url(self, host, media_id): web_url = self.get_url(host, media_id) headers = {'Referer': web_url} headers.update(self.headers) html = self.net.http_GET(web_url, headers=headers).content sources = helpers.scrape_sources(html, patterns=["""file:\s*["'](?P<url>[^"']+)"""]) if sources: auth = self.__check_auth(media_id) if not auth: auth = self.__auth_ip(media_id) if auth: return helpers.pick_source(sources) + helpers.append_headers(headers) else: raise ResolverError(i18n('no_ip_authorization')) else: raise ResolverError('Unable to locate links')
def get_media_url(self, host, media_id): try: if not self.__file_exists(media_id): raise ResolverError('File Not Available') video_url = self.__check_auth(media_id) if not video_url: video_url = self.__auth_ip(media_id) except ResolverError: raise if video_url: headers = {'User-Agent': common.RAND_UA} video_url = video_url + helpers.append_headers(headers) return video_url else: raise ResolverError(i18n('no_ol_auth'))
def get_media_url(self, host, media_id): try: self._auto_update(self.get_setting('url'), OL_PATH, self.get_setting('key')) reload(ol_gmu) return ol_gmu.get_media_url(self.get_url(host, media_id)) # @UndefinedVariable except Exception as e: common.log_utils.log_debug('Exception during openload resolve parse: %s' % (e)) try: video_url = self.__check_auth(media_id) if not video_url: video_url = self.__auth_ip(media_id) except ResolverError: raise if video_url: return video_url else: raise ResolverError(i18n('no_ol_auth'))
def get_media_url(self, host, media_id): url = helpers.get_media_url(self.get_url(host, media_id), result_blacklist=['intro_black' ]).replace(' ', '%20') net = common.Net() headers = {} if '|' in url: qs_header_split = url.split('|') url = qs_header_split[0] headers = urlparse.parse_qs(qs_header_split[1]) headers = dict((k, v[0]) for k, v in headers.iteritems()) response = net.http_HEAD(url, headers=headers) if (response.get_url()): return response.get_url() + helpers.append_headers(headers) else: raise ResolverError(common.i18n('no_video_link'))
def get_media_url(self, host, media_id): try: self._auto_update(self.get_setting('url'), OL_PATH, self.get_setting('key')) reload(ol_gmu) return ol_gmu.get_media_url(self.get_url(host, media_id)) # @UndefinedVariable except Exception as e: logger.log_debug('Exception during openload resolve parse: %s' % (e)) try: if not self.__file_exists(media_id): raise ResolverError('File Not Available') video_url = self.__check_auth(media_id) if not video_url: video_url = self.__auth_ip(media_id) except ResolverError: raise if video_url: return video_url else: raise ResolverError(i18n('no_ol_auth'))
def _auto_update(self, py_source, py_path, key=''): try: if self.get_setting('auto_update') == 'true' and py_source: headers = self.net.http_HEAD(py_source).get_headers( as_dict=True) common.logger.log(headers) old_etag = self.get_setting('etag') new_etag = headers.get('Etag', '') old_len = common.file_length(py_path, key) new_len = int(headers.get('Content-Length', 0)) py_name = os.path.basename(py_path) if old_etag != new_etag or old_len != new_len: common.logger.log( 'Updating %s: |%s|%s|%s|%s|' % (py_name, old_etag, new_etag, old_len, new_len)) self.set_setting('etag', new_etag) new_py = self.net.http_GET(py_source).content if new_py: if key: new_py = common.decrypt_py(new_py, key) if new_py and 'import' in new_py: with open(py_path, 'w') as f: f.write(new_py.encode('utf-8')) common.kodi.notify( '%s %s' % (self.name, common.i18n('resolver_updated'))) else: common.logger.log( 'Reusing existing %s: |%s|%s|%s|%s|' % (py_name, old_etag, new_etag, old_len, new_len)) common.log_file_hash(py_path) except Exception as e: common.logger.log_warning( 'Exception during %s Auto-Update code retrieve: %s' % (self.name, e))
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append('<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append('<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append('<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append('<setting id="%s_auth" type="action" label="%s" enable="!eq(-1,)+!eq(-2,)+!eq(-3,false)" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_ad)"/>' % (cls.__name__, i18n('auth_my_account'))) xml.append('<setting id="%s_reset" type="action" label="%s" enable="!eq(-2,)+!eq(-3,)+!eq(-4,false)" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_ad)"/>' % (cls.__name__, i18n('reset_my_auth'))) xml.append('<setting id="%s_token" visible="false" type="text" default=""/>' % (cls.__name__)) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append('<setting id="%s_torrents" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('torrents'))) xml.append('<setting id="%s_cached_only" enable="eq(-1,true)" type="bool" label="%s" default="false" />' % (cls.__name__, i18n('cached_only'))) xml.append('<setting id="%s_autopick" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('auto_primary_link'))) xml.append('<setting id="%s_auth" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_rd)"/>' % (cls.__name__, i18n('auth_my_account'))) xml.append('<setting id="%s_reset" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_rd)"/>' % (cls.__name__, i18n('reset_my_auth'))) xml.append('<setting id="%s_token" visible="false" type="text" default=""/>' % cls.__name__) xml.append('<setting id="%s_refresh" visible="false" type="text" default=""/>' % cls.__name__) xml.append('<setting id="%s_client_id" visible="false" type="text" default=""/>' % cls.__name__) xml.append('<setting id="%s_client_secret" visible="false" type="text" default=""/>' % cls.__name__) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append('<setting id="%s_client_id" type="text" label="%s" default="%s"/>' % (cls.__name__, i18n('client_id'), 'am6l6dn0x3bxrdgc557p1qeg1ma3bto')) return xml
def __direct_dl(self, media_id, torrent=False): try: if torrent: response = self.net.http_GET(torrents_files.format(media_id), headers=self.headers).content else: response = self.net.http_GET(linkgen.format(quote('{"link":"%s"}' % media_id)), headers=self.headers).content result = json.loads(response) if torrent: if result.get('status') == 'OK': _videos = [] def _search_tree(d): for k, v in d.items(): if isinstance(v, dict) and v.get('isVideo') != 'y': _search_tree(v) else: if isinstance(v, dict): _videos.append(v) _search_tree(result) try: link = max(_videos, key=lambda x: int(x.get('size'))).get('downloadLink', None) stream = self.net.http_GET(link, headers=self.headers).get_url() return stream except Exception: raise ResolverError('Failed to locate largest video file') else: raise ResolverError('Unexpected Response Received') else: stream = result.get('links')[0] if stream['status'] != 'OK': raise ResolverError('Link Not Found: {0}'.format(stream.get('error'))) elif stream['type'] != 'video': raise ResolverError( 'Generated link "{0}" does not contain a playable file'.format(stream.get('generated')) ) elif any(item in media_id for item in self.get_hosts()[1]): transfer_info = self.__check_dl_status(stream.get('hash')) if transfer_info.get('percent') != 100: line1 = stream.get('filename') line2 = stream.get('filehost') with common.kodi.ProgressDialog('URLResolver Linksnappy transfer', line1, line2) as pd: while self.__check_dl_status(stream.get('hash')).get('percent') != 100: common.kodi.sleep(2000) transfer_info = self.__check_dl_status(stream.get('hash')) try: logger.log_debug( 'Transfer with id "{0}" is still in progress, caching... active connections {1}, download speed {2}'.format( stream.get('hash'), transfer_info.get('connections'), transfer_info.get('downloadSpeed') ) ) except ValueError: pass try: line1 = stream.get('filename') line2 = stream.get('filehost') try: line3 = ''.join( [i18n('download_rate'), ' ', transfer_info.get('downloadSpeed')] ) pd.update(int(transfer_info.get('percent')), line1=line1, line2=line2, line3=line3) except ValueError: pd.update(int(transfer_info.get('percent')), line1=line1, line2=line2) except ValueError: pass if pd.is_canceled(): raise ResolverError('Transfer ID "{0}" canceled by user'.format(stream.get('hash'))) else: logger.log_debug('Transfer with id "{0}" completed'.format(stream.get('hash'))) pd.update(percent=100) return stream.get('generated') else: stream.get('generated') return stream.get('generated') except Exception as e: # _, __, tb = sys.exc_info() # # print traceback.print_tb(tb) logger.log_debug('Linksnappy, error at __direct_dl function: {0}'.format(e)) return None
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append('<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append('<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append('<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append('<setting id="%s_ts_offset" type="number" visible="false" enable="false" default="0"/>' % (cls.__name__)) xml.append('<setting id="%s_token" type="text" visible="false" enable="false"/>' % (cls.__name__)) xml.append('<setting id="%s_key" type="text" visible="false" enable="false"/>' % (cls.__name__)) return xml
def __init__(self, *args, **kwargs): bg_image = os.path.join(common.addon_path, 'resources', 'images', 'DialogBack2.png') check_image = os.path.join(common.addon_path, 'resources', 'images', 'checked.png') button_fo = os.path.join(common.kodi.get_path(), 'resources', 'skins', 'Default', 'media', 'button-fo.png') button_nofo = os.path.join(common.kodi.get_path(), 'resources', 'skins', 'Default', 'media', 'button-nofo.png') self.cancelled = False self.chk = [0] * 9 self.chkbutton = [0] * 9 self.chkstate = [False] * 9 imgX, imgY, imgw, imgh = 436, 210, 408, 300 ph, pw = imgh / 3, imgw / 3 x_gap = 70 y_gap = 70 button_gap = 40 button_h = 40 button_y = imgY + imgh + button_gap middle = imgX + (imgw / 2) win_x = imgX - x_gap win_y = imgY - y_gap win_h = imgh + 2 * y_gap + button_h + button_gap win_w = imgw + 2 * x_gap ctrlBackgound = xbmcgui.ControlImage(win_x, win_y, win_w, win_h, bg_image) self.addControl(ctrlBackgound) self.msg = '[COLOR red]%s[/COLOR]' % (kwargs.get('msg')) self.strActionInfo = xbmcgui.ControlLabel(imgX, imgY - 30, imgw, 20, self.msg, 'font13') self.addControl(self.strActionInfo) img = xbmcgui.ControlImage(imgX, imgY, imgw, imgh, kwargs.get('captcha')) self.addControl(img) self.iteration = kwargs.get('iteration') self.strActionInfo = xbmcgui.ControlLabel(imgX, imgY + imgh, imgw, 20, common.i18n('captcha_round') % (str(self.iteration)), 'font40') self.addControl(self.strActionInfo) self.cancelbutton = xbmcgui.ControlButton(middle - 110, button_y, 100, button_h, common.i18n('cancel'), focusTexture=button_fo, noFocusTexture=button_nofo, alignment=2) self.okbutton = xbmcgui.ControlButton(middle + 10, button_y, 100, button_h, common.i18n('ok'), focusTexture=button_fo, noFocusTexture=button_nofo, alignment=2) self.addControl(self.okbutton) self.addControl(self.cancelbutton) for i in xrange(9): row = i / 3 col = i % 3 x_pos = imgX + (pw * col) y_pos = imgY + (ph * row) self.chk[i] = xbmcgui.ControlImage(x_pos, y_pos, pw, ph, check_image) self.addControl(self.chk[i]) self.chk[i].setVisible(False) self.chkbutton[i] = xbmcgui.ControlButton(x_pos, y_pos, pw, ph, str(i + 1), font='font1', focusTexture=button_fo, noFocusTexture=button_nofo) self.addControl(self.chkbutton[i]) for i in xrange(9): row_start = (i / 3) * 3 right = row_start + (i + 1) % 3 left = row_start + (i - 1) % 3 up = (i - 3) % 9 down = (i + 3) % 9 self.chkbutton[i].controlRight(self.chkbutton[right]) self.chkbutton[i].controlLeft(self.chkbutton[left]) if i <= 2: self.chkbutton[i].controlUp(self.okbutton) else: self.chkbutton[i].controlUp(self.chkbutton[up]) if i >= 6: self.chkbutton[i].controlDown(self.okbutton) else: self.chkbutton[i].controlDown(self.chkbutton[down]) self.okbutton.controlLeft(self.cancelbutton) self.okbutton.controlRight(self.cancelbutton) self.cancelbutton.controlLeft(self.okbutton) self.cancelbutton.controlRight(self.okbutton) self.okbutton.controlDown(self.chkbutton[2]) self.okbutton.controlUp(self.chkbutton[8]) self.cancelbutton.controlDown(self.chkbutton[0]) self.cancelbutton.controlUp(self.chkbutton[6]) self.setFocus(self.okbutton)
def __initiate_transfer(self, media_id, interval=5): torrent_id = self.__create_transfer(media_id) transfer_info = self.__list_transfer(torrent_id) if transfer_info: line1 = transfer_info.get('name') line2 = ''.join([ i18n('download_rate'), ' ', str(transfer_info.get('downloadRate')) ]) line3 = ''.join( [i18n('peer_number'), ' ', str(transfer_info.get('getPeers'))]) with common.kodi.ProgressDialog('URLResolver Linksnappy transfer', line1, line2, line3) as pd: while transfer_info.get('status') != 'FINISHED': common.kodi.sleep(2000) transfer_info = self.__list_transfer(torrent_id) try: line1 = transfer_info.get('name') line2 = ''.join([ i18n('download_rate'), ' ', str(transfer_info.get('downloadRate')) ]) line3 = ''.join([ i18n('peer_number'), ' ', str(transfer_info.get('getPeers')), ', ETA: ', str(transfer_info.get('eta')), ' ', i18n('seconds') ]) logger.log_debug(line2) pd.update(int(transfer_info.get('percentDone')), line1=line1, line2=line2, line3=line3) except ValueError: pass if pd.is_canceled(): self.__delete_transfer(torrent_id) # self.__delete_folder() raise ResolverError( 'Transfer ID "{0}" canceled by user'.format( torrent_id)) else: logger.log_debug( 'Linksnappy.com: Transfer with id "{0}" completed!'. format(torrent_id)) common.kodi.sleep( 1000 * interval) # allow api time to generate the stream_link return torrent_id
def __init__(self, *args, **kwargs): bg_image = os.path.join(common.addon_path, 'resources', 'images', 'DialogBack2.png') check_image = os.path.join(common.addon_path, 'resources', 'images', 'checked.png') button_fo = os.path.join(common.kodi.get_path(), 'resources', 'skins', 'Default', 'media', 'button-fo.png') button_nofo = os.path.join(common.kodi.get_path(), 'resources', 'skins', 'Default', 'media', 'button-nofo.png') self.cancelled = False self.chk = [0] * 9 self.chkbutton = [0] * 9 self.chkstate = [False] * 9 imgX, imgY, imgw, imgh = 436, 210, 408, 300 ph, pw = imgh / 3, imgw / 3 x_gap = 70 y_gap = 70 button_gap = 40 button_h = 40 button_y = imgY + imgh + button_gap middle = imgX + (imgw / 2) win_x = imgX - x_gap win_y = imgY - y_gap win_h = imgh + 2 * y_gap + button_h + button_gap win_w = imgw + 2 * x_gap ctrlBackgound = xbmcgui.ControlImage(win_x, win_y, win_w, win_h, bg_image) self.addControl(ctrlBackgound) self.msg = '[COLOR red]%s[/COLOR]' % (kwargs.get('msg')) self.strActionInfo = xbmcgui.ControlLabel(imgX, imgY - 30, imgw, 20, self.msg, 'font13') self.addControl(self.strActionInfo) img = xbmcgui.ControlImage(imgX, imgY, imgw, imgh, kwargs.get('captcha')) self.addControl(img) self.iteration = kwargs.get('iteration') self.strActionInfo = xbmcgui.ControlLabel( imgX, imgY + imgh, imgw, 20, common.i18n('captcha_round') % (str(self.iteration)), 'font40') self.addControl(self.strActionInfo) self.cancelbutton = xbmcgui.ControlButton(middle - 110, button_y, 100, button_h, common.i18n('cancel'), focusTexture=button_fo, noFocusTexture=button_nofo, alignment=2) self.okbutton = xbmcgui.ControlButton(middle + 10, button_y, 100, button_h, common.i18n('ok'), focusTexture=button_fo, noFocusTexture=button_nofo, alignment=2) self.addControl(self.okbutton) self.addControl(self.cancelbutton) for i in xrange(9): row = i / 3 col = i % 3 x_pos = imgX + (pw * col) y_pos = imgY + (ph * row) self.chk[i] = xbmcgui.ControlImage(x_pos, y_pos, pw, ph, check_image) self.addControl(self.chk[i]) self.chk[i].setVisible(False) self.chkbutton[i] = xbmcgui.ControlButton( x_pos, y_pos, pw, ph, str(i + 1), font='font1', focusTexture=button_fo, noFocusTexture=button_nofo) self.addControl(self.chkbutton[i]) for i in xrange(9): row_start = (i / 3) * 3 right = row_start + (i + 1) % 3 left = row_start + (i - 1) % 3 up = (i - 3) % 9 down = (i + 3) % 9 self.chkbutton[i].controlRight(self.chkbutton[right]) self.chkbutton[i].controlLeft(self.chkbutton[left]) if i <= 2: self.chkbutton[i].controlUp(self.okbutton) else: self.chkbutton[i].controlUp(self.chkbutton[up]) if i >= 6: self.chkbutton[i].controlDown(self.okbutton) else: self.chkbutton[i].controlDown(self.chkbutton[down]) self.okbutton.controlLeft(self.cancelbutton) self.okbutton.controlRight(self.cancelbutton) self.cancelbutton.controlLeft(self.okbutton) self.cancelbutton.controlRight(self.okbutton) self.okbutton.controlDown(self.chkbutton[2]) self.okbutton.controlUp(self.chkbutton[8]) self.cancelbutton.controlDown(self.chkbutton[0]) self.cancelbutton.controlUp(self.chkbutton[6]) self.setFocus(self.okbutton)
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append('<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append('<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('username'))) xml.append('<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('password'))) xml.append('<setting id="%s_premium" enable="eq(-3,true)" type="bool" label="Premium Account" default="false"/>' % (cls.__name__)) xml.append('<setting id="%s_session_id" visible="false" type="text" default=""/>' % (cls.__name__)) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append('<setting id="{0}_premium" enable="false" label="{1}" type="bool" default="false"/>'.format(cls.__name__, i18n('ub_authorized'))) xml.append('<setting id="{0}_auth" type="action" label="{1}" action="RunPlugin(plugin://script.module.urlresolver/?mode=auth_ub)"/>'.format(cls.__name__, i18n('auth_my_account'))) xml.append('<setting id="{0}_reset" type="action" label="{1}" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_ub)"/>'.format(cls.__name__, i18n('reset_my_auth'))) xml.append('<setting id="{0}_token" visible="false" type="text" default=""/>'.format(cls.__name__)) return xml
def get_settings_xml(cls, include_login=True): """ This method should return XML which describes the settings you would like for your plugin. You should make sure that the ``id`` starts with your plugins class name (which can be found using :attr:`cls.__name__`) followed by an underscore. Override this method if you want your plugin to have more settings than just 'priority'. If you do and still want the defaults settings you should call this method from the base class first. Returns: A list containing XML elements that will be valid in settings.xml """ xml = [ '<setting id="%s_priority" type="number" label="%s" default="100"/>' % (cls.__name__, common.i18n('priority')), '<setting id="%s_enabled" ''type="bool" label="%s" default="true"/>' % (cls.__name__, common.i18n('enabled')) ] if include_login: xml.append('<setting id="%s_login" ''type="bool" label="%s" default="true" visible="false"/>' % (cls.__name__, common.i18n('login'))) return xml
def __direct_dl(self, media_id, torrent=False): try: if torrent: response = self.net().http_GET(torrents_files.format(media_id)).content else: response = self.net().http_GET(linkgen.format(urllib_parse.quote_plus('{"link":"%s"}' % media_id))).content result = json.loads(response) if torrent: if result.get('status') == 'OK': _videos = [] def _search_tree(d): for k, v in list(d.items()): if isinstance(v, dict) and v.get('isVideo') != 'y': _search_tree(v) else: if isinstance(v, dict): _videos.append(v) return _videos try: link = max(_search_tree(result), key=lambda x: int(x.get('size')))['downloadLink'] except Exception: raise ResolverError('(Linksnappy) Failed to locate largest video file') try: stream = self.net().http_HEAD(link).get_url() except Exception: try: logger.log_debug('(Linksnappy) SSL verification failed, attempting to generate link without validation') stream = self.net(ssl_verify=False).http_HEAD(link).get_url() self.verify = False except Exception: raise ResolverError('(Linksnappy) Failed to produce playable link') return stream else: raise ResolverError('(Linksnappy) Unexpected Response Received') else: stream = result.get('links')[0] if stream['status'] != 'OK': raise ResolverError('(Linksnappy) Link Not Found: {0}'.format(stream.get('error'))) elif stream['type'] != 'video': raise ResolverError( '(Linksnappy) Generated link "{0}" does not contain a playable file'.format(stream.get('generated')) ) elif any(item in media_id for item in self.get_hosts()[1]): transfer_info = self.__check_dl_status(stream.get('hash')) if transfer_info.get('percent') != 100: line1 = stream.get('filename') line2 = stream.get('filehost') with common.kodi.ProgressDialog('URLResolver Linksnappy transfer', line1, line2) as pd: while self.__check_dl_status(stream.get('hash')).get('percent') != 100: transfer_info = self.__check_dl_status(stream.get('hash')) try: logger.log_debug( '(Linksnappy) Transfer with id "{0}" is still in progress, caching... active connections {1}, download speed {2}'.format( stream.get('hash'), transfer_info.get('connections'), transfer_info.get('downloadSpeed') ) ) except ValueError: pass try: line1 = stream.get('filename') line2 = stream.get('filehost') try: line3 = ''.join( [i18n('download_rate'), ' ', transfer_info.get('downloadSpeed')] ) pd.update(int(transfer_info.get('percent')), line1=line1, line2=line2, line3=line3) except ValueError: pd.update(int(transfer_info.get('percent')), line1=line1, line2=line2) except ValueError: pass common.kodi.sleep(1000) if pd.is_canceled(): raise ResolverError('(Linksnappy) Transfer ID "{0}" canceled by user'.format(stream.get('hash'))) else: logger.log_debug('(Linksnappy) Transfer with id "{0}" completed'.format(stream.get('hash'))) pd.update(percent=100) return stream.get('generated') else: stream.get('generated') return stream.get('generated') except Exception as e: logger.log_debug('(Linksnappy) Error at __direct_dl function: {0}'.format(e)) return None
def _update_settings_xml(): """ This function writes a new ``resources/settings.xml`` file which contains all settings for this addon and its plugins. """ try: xbmcvfs.mkdirs(common.settings_path) except OSError: pass new_xml = [ '<?xml version="1.0" encoding="utf-8" standalone="yes"?>', '<settings>', '\t<category label="URLResolver">', '\t\t<setting default="true" id="allow_universal" label="%s" type="bool"/>' % (common.i18n('enable_universal')), '\t\t<setting default="true" id="allow_popups" label="%s" type="bool"/>' % (common.i18n('enable_popups')), '\t\t<setting default="true" id="auto_pick" label="%s" type="bool"/>' % (common.i18n('auto_pick')), '\t\t<setting default="true" id="use_cache" label="%s" type="bool"/>' % (common.i18n('use_function_cache')), '\t\t<setting id="reset_cache" type="action" label="%s" action="RunPlugin(plugin://script.module.urlresolver/?mode=reset_cache)"/>' % (common.i18n('reset_function_cache')), '\t\t<setting id="personal_nid" label="Your NID" type="text" visible="false" default=""/>', '\t\t<setting id="last_ua_create" label="last_ua_create" type="number" visible="false" default="0"/>', '\t\t<setting id="current_ua" label="current_ua" type="text" visible="false" default=""/>', '\t\t<setting id="addon_debug" label="addon_debug" type="bool" visible="false" default="false"/>', '\t</category>', '\t<category label="%s">' % (common.i18n('universal_resolvers'))] resolvers = relevant_resolvers(include_universal=True, include_disabled=True) resolvers = sorted(resolvers, key=lambda x: x.name.upper()) for resolver in resolvers: if resolver.isUniversal(): new_xml.append('\t\t<setting label="%s" type="lsep"/>' % resolver.name) new_xml += ['\t\t' + line for line in resolver.get_settings_xml()] new_xml.append('\t</category>') new_xml.append('\t<category label="%s 1">' % (common.i18n('resolvers'))) i = 0 cat_count = 2 for resolver in resolvers: if not resolver.isUniversal(): if i > MAX_SETTINGS: new_xml.append('\t</category>') new_xml.append('\t<category label="%s %s">' % (common.i18n('resolvers'), cat_count)) cat_count += 1 i = 0 new_xml.append('\t\t<setting label="%s" type="lsep"/>' % resolver.name) res_xml = resolver.get_settings_xml() new_xml += ['\t\t' + line for line in res_xml] i += len(res_xml) + 1 new_xml.append('\t</category>') new_xml.append('</settings>') try: if six.PY3: with open(common.settings_file, 'r', encoding='utf-8') as f: old_xml = f.read() else: with open(common.settings_file, 'r') as f: old_xml = f.read() except: old_xml = u'' new_xml = six.ensure_text('\n'.join(new_xml)) if old_xml != new_xml: common.logger.log_debug('Updating Settings XML') try: if six.PY3: with open(common.settings_file, 'w', encoding='utf-8') as f: f.write(new_xml) else: with open(common.settings_file, 'w') as f: f.write(new_xml.encode('utf8')) except: raise else: common.logger.log_debug('No Settings Update Needed')
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append('<setting id="%s_torrents" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('torrents'))) xml.append('<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append('<setting id="%s_password" enable="eq(-1,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('api_key'))) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml() xml.append('<setting id="%s_auto_update" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('auto_update'))) xml.append('<setting id="%s_url" type="text" label=" %s" default="" visible="eq(-1,true)"/>' % (cls.__name__, i18n('update_url'))) xml.append('<setting id="%s_key" type="text" label=" %s" default="" option="hidden" visible="eq(-2,true)"/>' % (cls.__name__, i18n('decrypt_key'))) xml.append('<setting id="%s_etag" type="text" default="" visible="false"/>' % (cls.__name__)) return xml
def get_settings_xml(cls): xml = super(cls, cls).get_settings_xml(include_login=False) xml.append('<setting id="%s_use_https" type="bool" label="%s" default="true"/>' % (cls.__name__, i18n('use_https'))) xml.append('<setting id="%s_login" type="bool" label="%s" default="false"/>' % (cls.__name__, i18n('login'))) xml.append('<setting id="%s_username" enable="eq(-1,true)" type="text" label="%s" default=""/>' % (cls.__name__, i18n('customer_id'))) xml.append('<setting id="%s_password" enable="eq(-2,true)" type="text" label="%s" option="hidden" default=""/>' % (cls.__name__, i18n('pin'))) return xml
def __initiate_transfer(self, media_id, interval=5): torrent_id = self.__create_transfer(media_id) transfer_info = self.__list_transfer(torrent_id) if transfer_info: line1 = transfer_info.get('name') line2 = ''.join([i18n('download_rate'), ' ', str(transfer_info.get('downloadRate'))]) line3 = ''.join( [ i18n('peer_number'), ' ', str(transfer_info.get('getPeers')) ] ) with common.kodi.ProgressDialog('URLResolver Linksnappy transfer', line1, line2, line3) as pd: while transfer_info.get('status') != 'FINISHED': transfer_info = self.__list_transfer(torrent_id) seconds = transfer_info.get('eta') if seconds >= 3600: eta = datetime.fromtimestamp(seconds).strftime( '%H {0} %M {1} %S {2}'.format(i18n('hours'), i18n('minutes'), i18n('seconds')) ) elif seconds >= 60: eta = datetime.fromtimestamp(seconds).strftime( '%M {0} %S {1}'.format(i18n('minutes'), i18n('seconds')) ) else: eta = ''.join([str(seconds), ' ', i18n('seconds')]) try: line1 = transfer_info.get('name') line2 = ''.join( [ i18n('download_rate'), ' ', str(transfer_info.get('downloadRate')), ', ', str(transfer_info.get('percentDone')), '%' ] ) line3 = ''.join( [ i18n('peer_number'), ' ', str(transfer_info.get('getPeers')), ', ETA: ', eta ] ) logger.log_debug(line2) pd.update(int(transfer_info.get('percentDone')), line1=line1, line2=line2, line3=line3) common.kodi.sleep(1000) except ValueError: pass if pd.is_canceled(): self.__delete_transfer(torrent_id) # self.__delete_folder() raise ResolverError('(Linksnappy) Transfer with ID "{0}" canceled by user'.format(torrent_id)) else: logger.log_debug('(Linksnappy) Transfer with ID "{0}" completed!'.format(torrent_id)) common.kodi.sleep(1000 * interval) # allow api time to generate the stream_link return torrent_id