# urlresolver XBMC Addon # Copyright (C) 2011, 2016 t0mm0, tknorris # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ This module defines the interfaces that you can implement when writing your URL resolving plugin. """ import os import re import abc from urlresolver import common abstractstaticmethod = abc.abstractmethod class abstractclassmethod(classmethod): __isabstractmethod__ = True def __init__(self, callable): callable.__isabstractmethod__ = True super(abstractclassmethod, self).__init__(callable) class ResolverError(Exception): pass class UrlResolver(object): __metaclass__ = abc.ABCMeta ''' Your plugin needs to implement the abstract methods in this interface if it wants to be able to resolve URLs domains: (array) List of domains handled by this plugin. (Use ["*"] for universal resolvers.) ''' domains = ['localdomain'] @abc.abstractmethod def get_media_url(self, host, media_id): """ The method that does the actual resolving. You must implement this method. Args: host (str): the host the link is on media_id (str): the media_id the can be returned by get_host_and_id Returns: If the media_id could be resolved, a string containing the direct URL to the media file, if not, raises ResolverError. """ raise NotImplementedError @abc.abstractmethod def get_url(self, host, media_id): """ The method that converts a host and media_id into a valid url Args: host (str): the host the link is on media_id (str): the media_id the can be returned by get_host_and_id Returns: a valid url on the host this resolver resolves """ raise NotImplementedError def get_host_and_id(self, url): """ The method that converts a host and media_id into a valid url Args: url (str): a valid url on the host this resolver resolves Returns: host (str): the host the link is on media_id (str): the media_id the can be returned by get_host_and_id """ r = re.search(self.pattern, url, re.I) if r: return r.groups() else: return False def valid_url(self, url, host): """ Determine whether this plugin is capable of resolving this URL. You must implement this method. Returns: True if this plugin thinks it can handle the web_url or host otherwise False. """ if isinstance(host, basestring): host = host.lower() if url: return re.search(self.pattern, url, re.I) is not None else: return any(host in domain.lower() for domain in self.domains) @classmethod def isUniversal(cls): """ You need to override this to return True, if you are implementing a universal resolver like real-debrid etc., which handles multiple hosts """ return False @classmethod def isPopup(cls): """ You need to override this to return True, if you are implementing a resolver like openload etc., which handles pairing or captcha hosts """ return False def login(self): """ This method should perform the login to the file host site. This will normally involve posting credentials (stored in your plugin's settings) to a web page which will set cookies. """ return True @classmethod 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 = [ '' % (cls.__name__, common.i18n('priority')), '' % (cls.__name__, common.i18n('enabled')) ] if include_login: xml.append('' % (cls.__name__, common.i18n('login'))) return xml @classmethod def set_setting(cls, key, value): common.set_setting('%s_%s' % (cls.__name__, key), str(value)) @classmethod def get_setting(cls, key): return common.get_setting('%s_%s' % (cls.__name__, key)) @classmethod def _get_priority(cls): try: return int(cls.get_setting('priority')) except: return 100 @classmethod def _is_enabled(cls): # default behaviour is enabled is True if resolver is enabled, or has login set to "true", or doesn't have the setting return cls.get_setting('enabled') == 'true' and cls.get_setting('login') in ['', 'true'] def _get_host(self, host): if '.' not in host: for domain in self.domains: if host in domain: return domain return host def _default_get_url(self, host, media_id, template=None): if template is None: template = 'http://{host}/embed-{media_id}.html' host = self._get_host(host) return template.format(host=host, media_id=media_id) @common.cache.cache_method(cache_limit=1) 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))