diff options
Diffstat (limited to 'pyload/plugins/internal')
| -rw-r--r-- | pyload/plugins/internal/AbstractExtractor.py | 100 | ||||
| -rw-r--r-- | pyload/plugins/internal/DeadCrypter.py | 19 | ||||
| -rw-r--r-- | pyload/plugins/internal/DeadHoster.py | 27 | ||||
| -rw-r--r-- | pyload/plugins/internal/MultiHoster.py | 192 | ||||
| -rw-r--r-- | pyload/plugins/internal/SimpleCrypter.py | 145 | ||||
| -rw-r--r-- | pyload/plugins/internal/SimpleHoster.py | 335 | ||||
| -rw-r--r-- | pyload/plugins/internal/UnRar.py | 220 | ||||
| -rw-r--r-- | pyload/plugins/internal/UnZip.py | 37 | ||||
| -rw-r--r-- | pyload/plugins/internal/UpdateManager.py | 299 | ||||
| -rw-r--r-- | pyload/plugins/internal/XFSPAccount.py | 96 | ||||
| -rw-r--r-- | pyload/plugins/internal/XFSPHoster.py | 361 | ||||
| -rw-r--r-- | pyload/plugins/internal/__init__.py | 0 | 
12 files changed, 1831 insertions, 0 deletions
| diff --git a/pyload/plugins/internal/AbstractExtractor.py b/pyload/plugins/internal/AbstractExtractor.py new file mode 100644 index 000000000..5a372fd71 --- /dev/null +++ b/pyload/plugins/internal/AbstractExtractor.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +class ArchiveError(Exception): +    pass + + +class CRCError(Exception): +    pass + + +class WrongPassword(Exception): +    pass + + +class AbtractExtractor: +    __name__ = "AbtractExtractor" +    __version__ = "0.1" + +    __description__ = """Abtract extractor plugin""" +    __authors__ = [("pyLoad Team", "admin@pyload.org")] + + +    @staticmethod +    def checkDeps(): +        """ Check if system statisfy dependencies +        :return: boolean +        """ +        return True + +    @staticmethod +    def getTargets(files_ids): +        """ Filter suited targets from list of filename id tuple list +        :param files_ids: List of filepathes +        :return: List of targets, id tuple list +        """ +        raise NotImplementedError + +    def __init__(self, m, file, out, fullpath, overwrite, excludefiles, renice): +        """Initialize extractor for specific file + +        :param m: ExtractArchive Addon plugin +        :param file: Absolute filepath +        :param out: Absolute path to destination directory +        :param fullpath: extract to fullpath +        :param overwrite: Overwrite existing archives +        :param renice: Renice value +        """ +        self.m = m +        self.file = file +        self.out = out +        self.fullpath = fullpath +        self.overwrite = overwrite +        self.excludefiles = excludefiles +        self.renice = renice +        self.files = []  #: Store extracted files here + +    def init(self): +        """ Initialize additional data structures """ +        pass + +    def checkArchive(self): +        """Check if password if needed. Raise ArchiveError if integrity is +        questionable. + +        :return: boolean +        :raises ArchiveError +        """ +        return False + +    def checkPassword(self, password): +        """ Check if the given password is/might be correct. +        If it can not be decided at this point return true. + +        :param password: +        :return: boolean +        """ +        return True + +    def extract(self, progress, password=None): +        """Extract the archive. Raise specific errors in case of failure. + +        :param progress: Progress function, call this to update status +        :param password password to use +        :raises WrongPassword +        :raises CRCError +        :raises ArchiveError +        :return: +        """ +        raise NotImplementedError + +    def getDeleteFiles(self): +        """Return list of files to delete, do *not* delete them here. + +        :return: List with paths of files to delete +        """ +        raise NotImplementedError + +    def getExtractedFiles(self): +        """Populate self.files at some point while extracting""" +        return self.files diff --git a/pyload/plugins/internal/DeadCrypter.py b/pyload/plugins/internal/DeadCrypter.py new file mode 100644 index 000000000..9a59677fd --- /dev/null +++ b/pyload/plugins/internal/DeadCrypter.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from pyload.plugins.base.Crypter import Crypter as _Crypter + + +class DeadCrypter(_Crypter): +    __name__ = "DeadCrypter" +    __type__ = "crypter" +    __version__ = "0.02" + +    __pattern__ = None + +    __description__ = """Crypter is no longer available""" +    __authors__ = [("stickell", "l.stickell@yahoo.it")] + + +    def setup(self): +        self.pyfile.error = "Crypter is no longer available" +        self.offline()  #@TODO: self.offline("Crypter is no longer available") diff --git a/pyload/plugins/internal/DeadHoster.py b/pyload/plugins/internal/DeadHoster.py new file mode 100644 index 000000000..349296e47 --- /dev/null +++ b/pyload/plugins/internal/DeadHoster.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugins.base.Hoster import Hoster as _Hoster + + +def create_getInfo(plugin): + +    def getInfo(urls): +        yield map(lambda url: ('#N/A: ' + url, 0, 1, url), urls) + +    return getInfo + + +class DeadHoster(_Hoster): +    __name__ = "DeadHoster" +    __type__ = "hoster" +    __version__ = "0.12" + +    __pattern__ = None + +    __description__ = """Hoster is no longer available""" +    __authors__ = [("zoidberg", "zoidberg@mujmail.cz")] + + +    def setup(self): +        self.pyfile.error = "Hoster is no longer available" +        self.offline()  #@TODO: self.offline("Hoster is no longer available") diff --git a/pyload/plugins/internal/MultiHoster.py b/pyload/plugins/internal/MultiHoster.py new file mode 100644 index 000000000..087edb6af --- /dev/null +++ b/pyload/plugins/internal/MultiHoster.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugins.base.Addon import Addon +from pyload.utils import remove_chars + + +class MultiHoster(Addon): +    __name__ = "MultiHoster" +    __type__ = "addon" +	__version__ = "0.20" + +    __description__ = """Generic MultiHoster plugin""" +    __authors__ = [("pyLoad Team", "admin@pyload.org")] + + +    replacements = [("2shared.com", "twoshared.com"), ("4shared.com", "fourshared.com"), ("cloudnator.com", "shragle.com"), +                    ("ifile.it", "filecloud.io"), ("easy-share.com", "crocko.com"), ("freakshare.net", "freakshare.com"), +                    ("hellshare.com", "hellshare.cz"), ("share-rapid.cz", "sharerapid.com"), ("sharerapid.cz", "sharerapid.com"), +                    ("ul.to", "uploaded.to"), ("uploaded.net", "uploaded.to"), ("1fichier.com", "onefichier.com")] +    ignored = [] +    interval = 24 * 60 * 60  #: reload hosters daily + + +    def setup(self): +        self.hosters = [] +        self.supported = [] +        self.new_supported = [] + +    def getConfig(self, option, default=''): +        """getConfig with default value - subclass may not implements all config options""" +        try: +            # Fixed loop due to getConf deprecation in 0.4.10 +            return super(MultiHoster, self).getConfig(option) +        except KeyError: +            return default + +    def getHosterCached(self): +        if not self.hosters: +            try: +                hosterSet = self.toHosterSet(self.getHoster()) - set(self.ignored) +            except Exception, e: +                self.logError(e) +                return [] + +            try: +                configMode = self.getConfig('hosterListMode', 'all') +                if configMode in ("listed", "unlisted"): +                    configSet = self.toHosterSet(self.getConfig('hosterList', '').replace('|', ',').replace(';', ',').split(',')) + +                    if configMode == "listed": +                        hosterSet &= configSet +                    else: +                        hosterSet -= configSet + +            except Exception, e: +                self.logError(e) + +            self.hosters = list(hosterSet) + +        return self.hosters + +    def toHosterSet(self, hosters): +        hosters = set((str(x).strip().lower() for x in hosters)) + +        for rep in self.replacements: +            if rep[0] in hosters: +                hosters.remove(rep[0]) +                hosters.add(rep[1]) + +        hosters.discard('') +        return hosters + +    def getHoster(self): +        """Load list of supported hoster + +        :return: List of domain names +        """ +        raise NotImplementedError + +    def coreReady(self): +        if self.cb: +            self.core.scheduler.removeJob(self.cb) + +        self.setConfig("activated", True)  #: config not in sync after plugin reload + +        cfg_interval = self.getConfig("interval", None)  #: reload interval in hours +        if cfg_interval is not None: +            self.interval = cfg_interval * 60 * 60 + +        if self.interval: +            self._periodical() +        else: +            self.periodical() + +    def initPeriodical(self): +        pass + +    def periodical(self): +        """reload hoster list periodically""" +        self.logInfo(_("Reloading supported hoster list")) + +        old_supported = self.supported +        self.supported, self.new_supported, self.hosters = [], [], [] + +        self.overridePlugins() + +        old_supported = [hoster for hoster in old_supported if hoster not in self.supported] +        if old_supported: +            self.logDebug("UNLOAD", ", ".join(old_supported)) +            for hoster in old_supported: +                self.unloadHoster(hoster) + +    def overridePlugins(self): +        pluginMap = {} +        for name in self.core.pluginManager.hosterPlugins.keys(): +            pluginMap[name.lower()] = name + +        accountList = [name.lower() for name, data in self.core.accountManager.accounts.items() if data] +        excludedList = [] + +        for hoster in self.getHosterCached(): +            name = remove_chars(hoster.lower(), "-.") + +            if name in accountList: +                excludedList.append(hoster) +            else: +                if name in pluginMap: +                    self.supported.append(pluginMap[name]) +                else: +                    self.new_supported.append(hoster) + +        if not self.supported and not self.new_supported: +            self.logError(_("No Hoster loaded")) +            return + +        module = self.core.pluginManager.getPlugin(self.__name__) +        klass = getattr(module, self.__name__) + +        # inject plugin plugin +        self.logDebug("Overwritten Hosters", ", ".join(sorted(self.supported))) +        for hoster in self.supported: +            dict = self.core.pluginManager.hosterPlugins[hoster] +            dict['new_module'] = module +            dict['new_name'] = self.__name__ + +        if excludedList: +            self.logInfo(_("The following hosters were not overwritten - account exists"), ", ".join(sorted(excludedList))) + +        if self.new_supported: +            self.logDebug("New Hosters", ", ".join(sorted(self.new_supported))) + +            # create new regexp +            regexp = r".*(%s).*" % "|".join([x.replace(".", "\\.") for x in self.new_supported]) +            if hasattr(klass, "__pattern__") and isinstance(klass.__pattern__, basestring) and '://' in klass.__pattern__: +                regexp = r"%s|%s" % (klass.__pattern__, regexp) + +            self.logDebug("Regexp", regexp) + +            dict = self.core.pluginManager.hosterPlugins[self.__name__] +            dict['pattern'] = regexp +            dict['re'] = re.compile(regexp) + +    def unloadHoster(self, hoster): +        dict = self.core.pluginManager.hosterPlugins[hoster] +        if "module" in dict: +            del dict['module'] + +        if "new_module" in dict: +            del dict['new_module'] +            del dict['new_name'] + +    def unload(self): +        """Remove override for all hosters. Scheduler job is removed by AddonManager""" +        for hoster in self.supported: +            self.unloadHoster(hoster) + +        # reset pattern +        klass = getattr(self.core.pluginManager.getPlugin(self.__name__), self.__name__) +        dict = self.core.pluginManager.hosterPlugins[self.__name__] +        dict['pattern'] = getattr(klass, "__pattern__", r'^unmatchable$') +        dict['re'] = re.compile(dict['pattern']) + +    def downloadFailed(self, pyfile): +        """remove plugin override if download fails but not if file is offline/temp.offline""" +        if pyfile.hasStatus("failed") and self.getConfig("unloadFailing", True): +            hdict = self.core.pluginManager.hosterPlugins[pyfile.pluginname] +            if "new_name" in hdict and hdict['new_name'] == self.__name__: +                self.logDebug("Unload MultiHoster", pyfile.pluginname, hdict) +                self.unloadHoster(pyfile.pluginname) +                pyfile.setStatus("queued") diff --git a/pyload/plugins/internal/SimpleCrypter.py b/pyload/plugins/internal/SimpleCrypter.py new file mode 100644 index 000000000..6c5c9593f --- /dev/null +++ b/pyload/plugins/internal/SimpleCrypter.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugins.Crypter import Crypter +from pyload.plugins.internal.SimpleHoster import PluginParseError, replace_patterns, set_cookies +from pyload.utils import fixup, html_unescape + + +class SimpleCrypter(Crypter): +    __name__ = "SimpleCrypter" +    __type__ = "crypter" +    __version__ = "0.13" + +    __pattern__ = None + +    __description__ = """Simple decrypter plugin""" +    __authors__ = [("stickell", "l.stickell@yahoo.it"), +                   ("zoidberg", "zoidberg@mujmail.cz"), +                   ("Walter Purcaro", "vuolter@gmail.com")] + + +    """ +    Following patterns should be defined by each crypter: + +      LINK_PATTERN: group(1) must be a download link or a regex to catch more links +        example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"' + +      TITLE_PATTERN: (optional) group(1) should be the folder name or the webpage title +        example: TITLE_PATTERN = r'<title>Files of: ([^<]+) folder</title>' + +      OFFLINE_PATTERN: (optional) Checks if the file is yet available online +        example: OFFLINE_PATTERN = r'File (deleted|not found)' + +      TEMP_OFFLINE_PATTERN: (optional) Checks if the file is temporarily offline +        example: TEMP_OFFLINE_PATTERN = r'Server maintainance' + + +    You can override the getLinks method if you need a more sophisticated way to extract the links. + + +    If the links are splitted on multiple pages you can define the PAGES_PATTERN regex: + +      PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links +        example: PAGES_PATTERN = r'Pages: (\d+)' + +    and its loadPage method: + +      def loadPage(self, page_n): +          return the html of the page number page_n +    """ + +    TITLE_REPLACEMENTS = [("&#?\w+;", fixup)] +    URL_REPLACEMENTS = [] + +    TEXT_ENCODING = False  #: Set to True or encoding name if encoding in http header is not correct +    COOKIES = True  #: or False or list of tuples [(domain, name, value)] + +    LOGIN_ACCOUNT = False +    LOGIN_PREMIUM = False + + +    def prepare(self): +        if self.LOGIN_ACCOUNT and not self.account: +            self.fail('Required account not found!') + +        if self.LOGIN_PREMIUM and not self.premium: +            self.fail('Required premium account not found!') + +        if isinstance(self.COOKIES, list): +            set_cookies(self.req.cj, self.COOKIES) + + +    def decrypt(self, pyfile): +        self.prepare() + +        pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS) + +        self.html = self.load(pyfile.url, decode=not self.TEXT_ENCODING) + +        self.checkOnline() + +        package_name, folder_name = self.getPackageNameAndFolder() + +        self.package_links = self.getLinks() + +        if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'): +            self.handleMultiPages() + +        self.logDebug("Package has %d links" % len(self.package_links)) + +        if self.package_links: +            self.packages = [(package_name, self.package_links, folder_name)] +        else: +            self.fail('Could not extract any links') + + +    def getLinks(self): +        """ +        Returns the links extracted from self.html +        You should override this only if it's impossible to extract links using only the LINK_PATTERN. +        """ +        return re.findall(self.LINK_PATTERN, self.html) + + +    def checkOnline(self): +        if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html): +            self.offline() +        elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html): +            self.tempOffline() + + +    def getPackageNameAndFolder(self): +        if hasattr(self, 'TITLE_PATTERN'): +            try: +                m = re.search(self.TITLE_PATTERN, self.html) +                name = replace_patterns(m.group(1).strip(), self.TITLE_REPLACEMENTS) +                folder = html_unescape(name) +            except: +                pass +            else: +                self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) +                return name, folder + +        name = self.pyfile.package().name +        folder = self.pyfile.package().folder +        self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) + +        return name, folder + + +    def handleMultiPages(self): +        try: +            m = re.search(self.PAGES_PATTERN, self.html) +            pages = int(m.group(1)) +        except: +            pages = 1 + +        for p in xrange(2, pages + 1): +            self.html = self.loadPage(p) +            self.package_links += self.getLinks() + + +    def parseError(self, msg): +        raise PluginParseError(msg) diff --git a/pyload/plugins/internal/SimpleHoster.py b/pyload/plugins/internal/SimpleHoster.py new file mode 100644 index 000000000..bc4cc3c88 --- /dev/null +++ b/pyload/plugins/internal/SimpleHoster.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- + +import re + +from time import time +from urlparse import urlparse + +from pyload.network.CookieJar import CookieJar +from pyload.network.RequestFactory import getURL +from pyload.plugins.base.Hoster import Hoster +from pyload.utils import fixup, html_unescape, parseFileSize + + +def replace_patterns(string, ruleslist): +    for r in ruleslist: +        rf, rt = r +        string = re.sub(rf, rt, string) +    return string + + +def set_cookies(cj, cookies): +    for cookie in cookies: +        if isinstance(cookie, tuple) and len(cookie) == 3: +            domain, name, value = cookie +            cj.setCookie(domain, name, value) + + +def parseHtmlTagAttrValue(attr_name, tag): +    m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I) +    return m.group(2) if m else None + + +def parseHtmlForm(attr_str, html, input_names=None): +    for form in re.finditer(r"(?P<tag><form[^>]*%s[^>]*>)(?P<content>.*?)</?(form|body|html)[^>]*>" % attr_str, +                            html, re.S | re.I): +        inputs = {} +        action = parseHtmlTagAttrValue("action", form.group('tag')) +        for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('content'), re.S | re.I): +            name = parseHtmlTagAttrValue("name", inputtag.group(1)) +            if name: +                value = parseHtmlTagAttrValue("value", inputtag.group(1)) +                if not value: +                    inputs[name] = inputtag.group(3) or '' +                else: +                    inputs[name] = value + +        if isinstance(input_names, dict): +            # check input attributes +            for key, val in input_names.items(): +                if key in inputs: +                    if isinstance(val, basestring) and inputs[key] == val: +                        continue +                    elif isinstance(val, tuple) and inputs[key] in val: +                        continue +                    elif hasattr(val, "search") and re.match(val, inputs[key]): +                        continue +                    break  # attibute value does not match +                else: +                    break  # attibute name does not match +            else: +                return action, inputs  # passed attribute check +        else: +            # no attribute check +            return action, inputs + +    return {}, None  # no matching form found + + +def parseFileInfo(self, url='', html=''): +    info = {"name": url, "size": 0, "status": 3} + +    if hasattr(self, "pyfile"): +        url = self.pyfile.url + +    if hasattr(self, "req") and self.req.http.code == '404': +        info['status'] = 1 +    else: +        if not html and hasattr(self, "html"): +            html = self.html +        if isinstance(self.TEXT_ENCODING, basestring): +            html = unicode(html, self.TEXT_ENCODING) +            if hasattr(self, "html"): +                self.html = html + +        if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, html): +            info['status'] = 1 +        elif hasattr(self, "FILE_OFFLINE_PATTERN") and re.search(self.FILE_OFFLINE_PATTERN, html):  #@TODO: Remove in 0.4.10 +            info['status'] = 1 +        elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, html): +            info['status'] = 6 +        else: +            online = False +            try: +                info.update(re.match(self.__pattern__, url).groupdict()) +            except: +                pass + +            for pattern in ("FILE_INFO_PATTERN", "FILE_NAME_PATTERN", "FILE_SIZE_PATTERN"): +                try: +                    info.update(re.search(getattr(self, pattern), html).groupdict()) +                    online = True +                except AttributeError: +                    continue + +            if online: +                # File online, return name and size +                info['status'] = 2 +                if 'N' in info: +                    info['name'] = replace_patterns(info['N'].strip(), self.FILE_NAME_REPLACEMENTS) +                if 'S' in info: +                    size = replace_patterns(info['S'] + info['U'] if 'U' in info else info['S'], +                                            self.FILE_SIZE_REPLACEMENTS) +                    info['size'] = parseFileSize(size) +                elif isinstance(info['size'], basestring): +                    unit = info['units'] if 'units' in info else None +                    info['size'] = parseFileSize(info['size'], unit) + +    if hasattr(self, "file_info"): +        self.file_info = info + +    return info['name'], info['size'], info['status'], url + + +def create_getInfo(plugin): + +    def getInfo(urls): +        for url in urls: +            cj = CookieJar(plugin.__name__) +            if isinstance(plugin.COOKIES, list): +                set_cookies(cj, plugin.COOKIES) +            file_info = parseFileInfo(plugin, url, getURL(replace_patterns(url, plugin.FILE_URL_REPLACEMENTS), +                                                          decode=not plugin.TEXT_ENCODING, cookies=cj)) +            yield file_info + +    return getInfo + + +def timestamp(): +    return int(time() * 1000) + + +class PluginParseError(Exception): + +    def __init__(self, msg): +        Exception.__init__(self) +        self.value = 'Parse error (%s) - plugin may be out of date' % msg + +    def __str__(self): +        return repr(self.value) + + +class SimpleHoster(Hoster): +    __name__ = "SimpleHoster" +    __type__ = "hoster" +    __version__ = "0.38" + +    __pattern__ = None + +    __description__ = """Simple hoster plugin""" +    __authors__ = [("zoidberg", "zoidberg@mujmail.cz"), +                   ("stickell", "l.stickell@yahoo.it"), +                   ("Walter Purcaro", "vuolter@gmail.com")] + + +    """ +    Following patterns should be defined by each hoster: + +      FILE_INFO_PATTERN: Name and Size of the file +        example: FILE_INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)' +      or +        FILE_NAME_PATTERN: Name that will be set for the file +          example: FILE_NAME_PATTERN = r'(?P<N>file_name)' +        FILE_SIZE_PATTERN: Size that will be checked for the file +          example: FILE_SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)' + +      OFFLINE_PATTERN: Checks if the file is yet available online +        example: OFFLINE_PATTERN = r'File (deleted|not found)' + +      TEMP_OFFLINE_PATTERN: Checks if the file is temporarily offline +        example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)' + +      PREMIUM_ONLY_PATTERN: (optional) Checks if the file can be downloaded only with a premium account +        example: PREMIUM_ONLY_PATTERN = r'Premium account required' + + +    Instead overriding handleFree and handlePremium methods now you can define patterns for direct download: + +      LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download +        example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"' + +      LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download +        example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"' +    """ + +    FILE_NAME_REPLACEMENTS = [("&#?\w+;", fixup)] +    FILE_SIZE_REPLACEMENTS = [] +    FILE_URL_REPLACEMENTS = [] + +    TEXT_ENCODING = False  #: Set to True or encoding name if encoding in http header is not correct +    COOKIES = True  #: or False or list of tuples [(domain, name, value)] +    FORCE_CHECK_TRAFFIC = False  #: Set to True to force checking traffic left for premium account + + +    def init(self): +        self.file_info = {} + + +    def setup(self): +        self.resumeDownload = self.multiDL = self.premium + + +    def prepare(self): +        if isinstance(self.COOKIES, list): +            set_cookies(self.req.cj, self.COOKIES) +        self.req.setOption("timeout", 120) + + +    def process(self, pyfile): +        self.prepare() + +        pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS) + +        # Due to a 0.4.9 core bug self.load would keep previous cookies even if overridden by cookies parameter. +        # Workaround using getURL. Can be reverted in 0.4.10 as the cookies bug has been fixed. +        self.html = getURL(pyfile.url, decode=not self.TEXT_ENCODING, cookies=self.COOKIES) +        premium_only = hasattr(self, 'PREMIUM_ONLY_PATTERN') and re.search(self.PREMIUM_ONLY_PATTERN, self.html) +        if not premium_only:  # Usually premium only pages doesn't show the file information +            self.getFileInfo() + +        if self.premium and (not self.FORCE_CHECK_TRAFFIC or self.checkTrafficLeft()): +            self.handlePremium() +        elif premium_only: +            self.fail("This link require a premium account") +        else: +            # This line is required due to the getURL workaround. Can be removed in 0.4.10 +            self.html = self.load(pyfile.url, decode=not self.TEXT_ENCODING) +            self.handleFree() + + +    def getFileInfo(self): +        self.logDebug("URL", self.pyfile.url) + +        name, size, status = parseFileInfo(self)[:3] + +        if status == 1: +            self.offline() +        elif status == 6: +            self.tempOffline() +        elif status != 2: +            self.logDebug(self.file_info) +            self.parseError('File info') + +        if name: +            self.pyfile.name = name +        else: +            self.pyfile.name = html_unescape(urlparse(self.pyfile.url).path.split("/")[-1]) + +        if size: +            self.pyfile.size = size +        else: +            self.logError(_("File size not parsed")) + +        self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size)) +        return self.file_info + + +    def handleFree(self): +        if not hasattr(self, 'LINK_FREE_PATTERN'): +            self.fail("Free download not implemented") + +        try: +            m = re.search(self.LINK_FREE_PATTERN, self.html) +            if m is None: +                self.parseError("Free download link not found") + +            link = m.group(1) +        except Exception, e: +            self.logError(str(e)) +        else: +            self.download(link, ref=True, cookies=True, disposition=True) + + +    def handlePremium(self): +        if not hasattr(self, 'LINK_PREMIUM_PATTERN'): +            self.fail("Premium download not implemented") + +        try: +            m = re.search(self.LINK_PREMIUM_PATTERN, self.html) +            if m is None: +                self.parseError("Premium download link not found") + +            link = m.group(1) +        except Exception, e: +            self.logError(str(e)) +        else: +            self.download(link, ref=True, cookies=True, disposition=True) + + +    def parseError(self, msg): +        raise PluginParseError(msg) + + +    def longWait(self, wait_time=None, max_tries=3): +        if wait_time and isinstance(wait_time, (int, long, float)): +            time_str = "%dh %dm" % divmod(wait_time / 60, 60) +        else: +            wait_time = 900 +            time_str = "(unknown time)" +            max_tries = 100 + +        self.logInfo(_("Download limit reached, reconnect or wait %s") % time_str) + +        self.setWait(wait_time, True) +        self.wait() +        self.retry(max_tries=max_tries, reason="Download limit reached") + + +    def parseHtmlForm(self, attr_str='', input_names=None): +        return parseHtmlForm(attr_str, self.html, input_names) + + +    def checkTrafficLeft(self): +        traffic = self.account.getAccountInfo(self.user, True)['trafficleft'] +        if traffic == -1: +            return True +        size = self.pyfile.size / 1024 +        self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size, self.user, traffic)) +        return size <= traffic + + +    #@TODO: Remove in 0.4.10 +    def wait(self, seconds=False, reconnect=False): +        if seconds: +            self.setWait(seconds, reconnect) +        super(SimpleHoster, self).wait() diff --git a/pyload/plugins/internal/UnRar.py b/pyload/plugins/internal/UnRar.py new file mode 100644 index 000000000..df7557d0d --- /dev/null +++ b/pyload/plugins/internal/UnRar.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- + +import os +import re + +from glob import glob +from os.path import basename, join +from string import digits +from subprocess import Popen, PIPE + +from pyload.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError +from pyload.utils import safe_join, decode + + +def renice(pid, value): +    if os.name != "nt" and value: +        try: +            Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1) +        except: +            print "Renice failed" + + +class UnRar(AbtractExtractor): +    __name__ = "UnRar" +    __version__ = "0.18" + +    __description__ = """Rar extractor plugin""" +    __authors__ = [("RaNaN", "RaNaN@pyload.org")] + + +    CMD = "unrar" + +    # there are some more uncovered rar formats +    re_version = re.compile(r"(UNRAR 5[\.\d]+(.*?)freeware)") +    re_splitfile = re.compile(r"(.*)\.part(\d+)\.rar$", re.I) +    re_partfiles = re.compile(r".*\.(rar|r[0-9]+)", re.I) +    re_filelist = re.compile(r"(.+)\s+(\d+)\s+(\d+)\s+") +    re_filelist5 = re.compile(r"(.+)\s+(\d+)\s+\d\d-\d\d-\d\d\s+\d\d:\d\d\s+(.+)") +    re_wrongpwd = re.compile("(Corrupt file or wrong password|password incorrect)", re.I) + + +    @staticmethod +    def checkDeps(): +        if os.name == "nt": +            UnRar.CMD = join(pypath, "UnRAR.exe") +            p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) +            p.communicate() +        else: +            try: +                p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) +                p.communicate() +            except OSError: + +                # fallback to rar +                UnRar.CMD = "rar" +                p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE) +                p.communicate() + +        return True + + +    @staticmethod +    def getTargets(files_ids): +        result = [] + +        for file, id in files_ids: +            if not file.endswith(".rar"): +                continue + +            match = UnRar.re_splitfile.findall(file) +            if match: +                # only add first parts +                if int(match[0][1]) == 1: +                    result.append((file, id)) +            else: +                result.append((file, id)) + +        return result + + +    def init(self): +        self.passwordProtected = False +        self.headerProtected = False  #: list files will not work without password +        self.smallestFile = None  #: small file to test passwords +        self.password = ""  #: save the correct password + + +    def checkArchive(self): +        p = self.call_unrar("l", "-v", self.file) +        out, err = p.communicate() +        if self.re_wrongpwd.search(err): +            self.passwordProtected = True +            self.headerProtected = True +            return True + +        # output only used to check if passworded files are present +        if self.re_version.search(out): +            for attr, size, name in self.re_filelist5.findall(out): +                if attr.startswith("*"): +                    self.passwordProtected = True +                    return True +        else: +            for name, size, packed in self.re_filelist.findall(out): +                if name.startswith("*"): +                    self.passwordProtected = True +                    return True + +        self.listContent() +        if not self.files: +            raise ArchiveError("Empty Archive") + +        return False + + +    def checkPassword(self, password): +        # at this point we can only verify header protected files +        if self.headerProtected: +            p = self.call_unrar("l", "-v", self.file, password=password) +            out, err = p.communicate() +            if self.re_wrongpwd.search(err): +                return False + +        return True + + +    def extract(self, progress, password=None): +        command = "x" if self.fullpath else "e" + +        p = self.call_unrar(command, self.file, self.out, password=password) +        renice(p.pid, self.renice) + +        progress(0) +        progressstring = "" +        while True: +            c = p.stdout.read(1) +            # quit loop on eof +            if not c: +                break +            # reading a percentage sign -> set progress and restart +            if c == '%': +                progress(int(progressstring)) +                progressstring = "" +            # not reading a digit -> therefore restart +            elif c not in digits: +                progressstring = "" +            # add digit to progressstring +            else: +                progressstring = progressstring + c +        progress(100) + +        # retrieve stderr +        err = p.stderr.read() + +        if "CRC failed" in err and not password and not self.passwordProtected: +            raise CRCError +        elif "CRC failed" in err: +            raise WrongPassword +        if err.strip():  #: raise error if anything is on stderr +            raise ArchiveError(err.strip()) +        if p.returncode: +            raise ArchiveError("Process terminated") + +        if not self.files: +            self.password = password +            self.listContent() + + +    def getDeleteFiles(self): +        if ".part" in basename(self.file): +            return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.IGNORECASE)) +        # get files which matches .r* and filter unsuited files out +        parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.IGNORECASE)) +        return filter(lambda x: self.re_partfiles.match(x), parts) + + +    def listContent(self): +        command = "vb" if self.fullpath else "lb" +        p = self.call_unrar(command, "-v", self.file, password=self.password) +        out, err = p.communicate() + +        if "Cannot open" in err: +            raise ArchiveError("Cannot open file") + +        if err.strip():  #: only log error at this point +            self.m.logError(err.strip()) + +        result = set() + +        for f in decode(out).splitlines(): +            f = f.strip() +            result.add(safe_join(self.out, f)) + +        self.files = result + + +    def call_unrar(self, command, *xargs, **kwargs): +        args = [] +        # overwrite flag +        args.append("-o+") if self.overwrite else args.append("-o-") + +        if self.excludefiles: +            for word in self.excludefiles.split(';'): +                args.append("-x%s" % word) + +        # assume yes on all queries +        args.append("-y") + +        # set a password +        if "password" in kwargs and kwargs['password']: +            args.append("-p%s" % kwargs['password']) +        else: +            args.append("-p-") + +        # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue +        call = [self.CMD, command] + args + list(xargs) +        self.m.logDebug(" ".join(call)) + +        p = Popen(call, stdout=PIPE, stderr=PIPE) + +        return p diff --git a/pyload/plugins/internal/UnZip.py b/pyload/plugins/internal/UnZip.py new file mode 100644 index 000000000..0fe50198f --- /dev/null +++ b/pyload/plugins/internal/UnZip.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +import sys +import zipfile + +from pyload.plugins.internal.AbstractExtractor import AbtractExtractor + + +class UnZip(AbtractExtractor): +    __name__ = "UnZip" +    __version__ = "0.1" + +    __description__ = """Zip extractor plugin""" +    __authors__ = [("RaNaN", "RaNaN@pyload.org")] + + +    @staticmethod +    def checkDeps(): +        return sys.version_info[:2] >= (2, 6) + +    @staticmethod +    def getTargets(files_ids): +        result = [] + +        for file, id in files_ids: +            if file.endswith(".zip"): +                result.append((file, id)) + +        return result + +    def extract(self, progress, password=None): +        z = zipfile.ZipFile(self.file) +        self.files = z.namelist() +        z.extractall(self.out) + +    def getDeleteFiles(self): +        return [self.file] diff --git a/pyload/plugins/internal/UpdateManager.py b/pyload/plugins/internal/UpdateManager.py new file mode 100644 index 000000000..f64a1e573 --- /dev/null +++ b/pyload/plugins/internal/UpdateManager.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- + +import re +import sys + +from operator import itemgetter +from os import path, remove, stat + +from pyload.network.RequestFactory import getURL +from pyload.plugins.base.Addon import Expose, Addon, threaded +from pyload.utils import safe_join + + +class UpdateManager(Addon): +    __name__ = "UpdateManager" +    __type__ = "addon" +    __version__ = "0.36" + +    __config__ = [("activated", "bool", "Activated", True), +                  ("mode", "pyLoad + plugins;plugins only", "Check updates for", "pyLoad + plugins"), +                  ("interval", "int", "Check interval in hours", 8), +                  ("reloadplugins", "bool", "Monitor plugins for code changes (debug mode only)", True), +                  ("nodebugupdate", "bool", "Don't check for updates in debug mode", True)] + +    __description__ = """Check for updates""" +    __authors__ = [("Walter Purcaro", "vuolter@gmail.com")] + + +    event_list = ["pluginConfigChanged"] + +    SERVER_URL = "http://updatemanager.pyload.org" +    MIRROR_URL = ""  #: empty actually + +    MIN_INTERVAL = 3 * 60 * 60  #: 3h minimum check interval (value is in seconds) + + +    def pluginConfigChanged(self, plugin, name, value): +        if name == "interval": +            interval = value * 60 * 60 +            if self.MIN_INTERVAL <= interval != self.interval: +                self.core.scheduler.removeJob(self.cb) +                self.interval = interval +                self.initPeriodical() +            else: +                self.logDebug("Invalid interval value, kept current") +        elif name == "reloadplugins": +            if self.cb2: +                self.core.scheduler.removeJob(self.cb2) +            if value is True and self.core.debug: +                self.periodical2() + + +    def coreReady(self): +        self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval")) +        x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins")) +        self.core.scheduler.addJob(10, x, threaded=False) + + +    def unload(self): +        self.pluginConfigChanged(self.__name__, "reloadplugins", False) + + +    def setup(self): +        self.cb2 = None +        self.interval = self.MIN_INTERVAL +        self.updating = False +        self.info = {'pyload': False, 'version': None, 'plugins': False} +        self.mtimes = {}  #: store modification time for each plugin + + +    def periodical2(self): +        if not self.updating: +            self.autoreloadPlugins() +        self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False) + + +    @Expose +    def autoreloadPlugins(self): +        """ reload and reindex all modified plugins """ +        modules = filter( +            lambda m: m and (m.__name__.startswith("pyload.plugins.") or +                             m.__name__.startswith("userplugins.")) and +                             m.__name__.count(".") >= 2, sys.modules.itervalues() +        ) + +        reloads = [] + +        for m in modules: +            root, type, name = m.__name__.rsplit(".", 2) +            id = (type, name) +            if type in self.core.pluginManager.plugins: +                f = m.__file__.replace(".pyc", ".py") +                if not path.isfile(f): +                    continue + +                mtime = stat(f).st_mtime + +                if id not in self.mtimes: +                    self.mtimes[id] = mtime +                elif self.mtimes[id] < mtime: +                    reloads.append(id) +                    self.mtimes[id] = mtime + +        return True if self.core.pluginManager.reloadPlugins(reloads) else False + + +    def periodical(self): +        if not self.info['pyload'] and not (self.getConfig("nodebugupdate") and self.core.debug): +            self.updateThread() + + +    def server_request(self): +        request = lambda x: getURL(x, get={'v': self.core.api.getServerVersion()}).splitlines() +        try: +            return request(self.SERVER_URL) +        except: +            try: +                return request(self.MIRROR_URL) +            except: +                pass +        self.logWarning(_("Unable to contact server to get updates")) + + +    @threaded +    def updateThread(self): +        self.updating = True +        status = self.update(onlyplugin=self.getConfig("mode") == "plugins only") +        if status == 2: +            self.core.api.restart() +        else: +            self.updating = False + + +    @Expose +    def updatePlugins(self): +        """ simple wrapper for calling plugin update quickly """ +        return self.update(onlyplugin=True) + + +    @Expose +    def update(self, onlyplugin=False): +        """ check for updates """ +        data = self.server_request() +        if not data: +            exitcode = 0 +        elif data[0] == "None": +            self.logInfo(_("No new pyLoad version available")) +            updates = data[1:] +            exitcode = self._updatePlugins(updates) +        elif onlyplugin: +            exitcode = 0 +        else: +            newversion = data[0] +            self.logInfo(_("***  New pyLoad Version %s available  ***") % newversion) +            self.logInfo(_("***  Get it here: https://github.com/pyload/pyload/releases  ***")) +            exitcode = 3 +            self.info['pyload'] = True +            self.info['version'] = newversion +        return exitcode  #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available + + +    def _updatePlugins(self, updates): +        """ check for plugin updates """ + +        if self.info['plugins']: +            return False  #: plugins were already updated + +        updated = [] + +        vre = re.compile(r'__version__.*=.*("|\')([0-9.]+)') +        url = updates[0] +        schema = updates[1].split('|') +        if "BLACKLIST" in updates: +            blacklist = updates[updates.index('BLACKLIST') + 1:] +            updates = updates[2:updates.index('BLACKLIST')] +        else: +            blacklist = None +            updates = updates[2:] + +        upgradable = sorted(map(lambda x: dict(zip(schema, x.split('|'))), updates), key=itemgetter("type", "name")) +        for plugin in upgradable: +            filename = plugin['name'] +            prefix = plugin['type'] +            version = plugin['version'] + +            if filename.endswith(".pyc"): +                name = filename[:filename.find("_")] +            else: +                name = filename.replace(".py", "") + +            #@TODO: obsolete after 0.4.10 +            if prefix.endswith("s"): +                type = prefix[:-1] +            else: +                type = prefix + +            plugins = getattr(self.core.pluginManager, "%sPlugins" % type) + +            oldver = float(plugins[name]['v']) if name in plugins else None +            newver = float(version) + +            if not oldver: +                msg = "New [%(type)s] %(name)s (v%(newver)s)" +            elif newver > oldver: +                msg = "New version of [%(type)s] %(name)s (v%(oldver)s -> v%(newver)s)" +            else: +                continue + +            self.logInfo(_(msg) % { +                'type': type, +                'name': name, +                'oldver': oldver, +                'newver': newver, +            }) + +            try: +                content = getURL(url % plugin) +                m = vre.search(content) +                if m and m.group(2) == version: +                    f = open(safe_join("userplugins", prefix, filename), "wb") +                    f.write(content) +                    f.close() +                    updated.append((prefix, name)) +                else: +                    raise Exception, _("Version mismatch") +            except Exception, e: +                self.logError(_("Error updating plugin: %s") % filename, e) + +        if blacklist: +            blacklisted = map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist) + +            # Always protect base and internal plugins from removing +            for i, n, t in blacklisted.enumerate(): +                if t in ("base", "internal"): +                    del blacklisted[i] + +            blacklisted = sorted(blacklisted) +            removed = self.removePlugins(blacklisted) +            for t, n in removed: +                self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % { +                    'type': t, +                    'name': n, +                }) + +        if updated: +            reloaded = self.core.pluginManager.reloadPlugins(updated) +            if reloaded: +                self.logInfo(_("Plugins updated and reloaded")) +                exitcode = 1 +            else: +                self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***")) +                self.info['plugins'] = True +                exitcode = 2 +        else: +            self.logInfo(_("No plugin updates available")) +            exitcode = 0 + +        return exitcode  #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required + + +    @Expose +    def removePlugins(self, type_plugins): +        """ delete plugins from disk """ + +        if not type_plugins: +            return + +        self.logDebug("Requested deletion of plugins: %s" % type_plugins) + +        removed = [] + +        for type, name in type_plugins: +            err = False +            file = name + ".py" + +            for root in ("userplugins", path.join(pypath, "pyload", "plugins")): + +                filename = safe_join(root, type, file) +                try: +                    remove(filename) +                except Exception, e: +                    self.logDebug("Error deleting: %s" % path.basename(filename), e) +                    err = True + +                filename += "c" +                if path.isfile(filename): +                    try: +                        if type == "addon": +                            self.manager.deactivateAddon(name) +                        remove(filename) +                    except Exception, e: +                        self.logDebug("Error deleting: %s" % path.basename(filename), e) +                        err = True + +            if not err: +                id = (type, name) +                removed.append(id) + +        return removed  #: return a list of the plugins successfully removed diff --git a/pyload/plugins/internal/XFSPAccount.py b/pyload/plugins/internal/XFSPAccount.py new file mode 100644 index 000000000..edf6ad3cc --- /dev/null +++ b/pyload/plugins/internal/XFSPAccount.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +import re + +from urlparse import urljoin +from time import mktime, strptime + +from pyload.plugins.base.Account import Account +from pyload.plugins.internal.SimpleHoster import parseHtmlForm, set_cookies +from pyload.utils import parseFileSize + + +class XFSPAccount(Account): +    __name__ = "XFSPAccount" +    __type__ = "account" +    __version__ = "0.09" + +    __description__ = """XFileSharingPro base account plugin""" +    __authors__ = [("zoidberg", "zoidberg@mujmail.cz"), +                   ("Walter Purcaro", "vuolter@gmail.com")] + + +    HOSTER_URL = None + +    COOKIES = None  #: or list of tuples [(domain, name, value)] + +    VALID_UNTIL_PATTERN = r'>Premium.[Aa]ccount expire:.*?<b>(.+?)</b>' +    TRAFFIC_LEFT_PATTERN = r'>Traffic available today:.*?<b>(?P<S>.+?)</b>' +    LOGIN_FAIL_PATTERN = r'>(Incorrect Login or Password|Error<)' +    # PREMIUM_PATTERN = r'>Renew premium<' + + +    def loadAccountInfo(self, user, req): +        html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True) + +        validuntil = None +        trafficleft = None +        premium = None + +        if hasattr(self, "PREMIUM_PATTERN"): +            premium = True if re.search(self.PREMIUM_PATTERN, html) else False + +        m = re.search(self.VALID_UNTIL_PATTERN, html) +        if m: +            expiredate = m.group(1) +            self.logDebug("Expire date: " + expiredate) + +            try: +                validuntil = mktime(strptime(expiredate, "%d %B %Y")) +            except Exception, e: +                self.logError(e) +            else: +                if validuntil > mktime(gmtime()): +                    premium = True +                    trafficleft = -1 +                else: +                    if premium is False:  #: registered account type (not premium) +                        validuntil = -1 +                    premium = False + +        try: +            traffic = re.search(self.TRAFFIC_LEFT_PATTERN, html).groupdict() +            trafficsize = traffic['S'] + traffic['U'] if 'U' in traffic else traffic['S'] +            if "Unlimited" in trafficsize: +                trafficleft = -1 +                if premium is None: +                    premium = True +            else: +                trafficleft = parseFileSize(trafficsize) +        except: +            pass + +        if premium is None: +            premium = False + +        return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium} + + +    def login(self, user, data, req): +        set_cookies(req.cj, self.COOKIES) + +        url = urljoin(self.HOSTER_URL, "login.html") +        html = req.load(url, decode=True) + +        action, inputs = parseHtmlForm('name="FL"', html) +        if not inputs: +            inputs = {'op': "login", +                      'redirect': self.HOSTER_URL} + +        inputs.update({'login': user, +                       'password': data['password']}) + +        html = req.load(self.HOSTER_URL, post=inputs, decode=True) + +        if re.search(self.LOGIN_FAIL_PATTERN, html): +            self.wrongPassword() diff --git a/pyload/plugins/internal/XFSPHoster.py b/pyload/plugins/internal/XFSPHoster.py new file mode 100644 index 000000000..ae8065732 --- /dev/null +++ b/pyload/plugins/internal/XFSPHoster.py @@ -0,0 +1,361 @@ +# -*- coding: utf-8 -*- + +import re + +from pycurl import FOLLOWLOCATION, LOW_SPEED_TIME +from random import random +from urllib import unquote +from urlparse import urlparse + +from pyload.network.RequestFactory import getURL +from pyload.plugins.internal.CaptchaService import ReCaptcha, SolveMedia +from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, PluginParseError, replace_patterns +from pyload.utils import html_unescape + + +class XFSPHoster(SimpleHoster): +    """ +    Common base for XFileSharingPro hosters like EasybytezCom, CramitIn, FiledinoCom... +    Some hosters may work straight away when added to __pattern__ +    However, most of them will NOT work because they are either down or running a customized version +    """ +    __name__ = "XFSPHoster" +    __type__ = "hoster" +    __version__ = "0.37" + +    __pattern__ = r'^unmatchable$' + +    __description__ = """XFileSharingPro base hoster plugin""" +    __authors__ = [("zoidberg", "zoidberg@mujmail.cz"), +                   ("stickell", "l.stickell@yahoo.it"), +                   ("Walter Purcaro", "vuolter@gmail.com")] + + +    HOSTER_NAME = None + +    FILE_URL_REPLACEMENTS = [(r'/embed-(\w{12}).*', r'/\1')]  #: support embedded files + +    COOKIES = [(HOSTER_NAME, "lang", "english")] + +    FILE_INFO_PATTERN = r'<tr><td align=right><b>Filename:</b></td><td nowrap>(?P<N>[^<]+)</td></tr>\s*.*?<small>\((?P<S>[^<]+)\)</small>' +    FILE_NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>[^"]+)"' +    FILE_SIZE_PATTERN = r'You have requested .*\((?P<S>[\d\.\,]+) ?(?P<U>\w+)?\)</font>' + +    OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)' +    TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)' + +    WAIT_PATTERN = r'<span id="countdown_str">.*?>(\d+)</span>' + +    OVR_LINK_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)' +    LINK_PATTERN = None  #: final download url pattern + +    CAPTCHA_URL_PATTERN = r'(http://[^"\']+?/captchas?/[^"\']+)' +    CAPTCHA_DIV_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>' +    RECAPTCHA_PATTERN = None +    SOLVEMEDIA_PATTERN = None + +    ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.+?)</' + + +    def setup(self): +        self.chunkLimit = 1 + +        if self.__name__ == "XFSPHoster": +            self.multiDL = True +            self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern'] +            self.HOSTER_NAME = re.match(self.__pattern__, self.pyfile.url).group(1).lower() +            self.COOKIES = [(self.HOSTER_NAME, "lang", "english")] +        else: +            self.resumeDownload = self.multiDL = self.premium + + +    def prepare(self): +        """ Initialize important variables """ +        if not self.HOSTER_NAME: +            self.fail("Missing HOSTER_NAME") + +        if not self.LINK_PATTERN: +            pattr = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<]+)' +            self.LINK_PATTERN = pattr % self.HOSTER_NAME + +        if isinstance(self.COOKIES, list): +            set_cookies(self.req.cj, self.COOKIES) + +        self.captcha = None +        self.errmsg = None +        self.passwords = self.getPassword().splitlines() + + +    def process(self, pyfile): +        self.prepare() + +        pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS) + +        if not re.match(self.__pattern__, pyfile.url): +            if self.premium: +                self.handleOverriden() +            else: +                self.fail("Only premium users can download from other hosters with %s" % self.HOSTER_NAME) +        else: +            try: +                # Due to a 0.4.9 core bug self.load would use cookies even if +                # cookies=False. Workaround using getURL to avoid cookies. +                # Can be reverted in 0.4.10 as the cookies bug has been fixed. +                self.html = getURL(pyfile.url, decode=True, cookies=self.COOKIES) +                self.file_info = self.getFileInfo() +            except PluginParseError: +                self.file_info = None + +            self.location = self.getDirectDownloadLink() + +            if not self.file_info: +                pyfile.name = html_unescape(unquote(urlparse( +                    self.location if self.location else pyfile.url).path.split("/")[-1])) + +            if self.location: +                self.startDownload(self.location) +            elif self.premium: +                self.handlePremium() +            else: +                self.handleFree() + + +    def getDirectDownloadLink(self): +        """ Get download link for premium users with direct download enabled """ +        self.req.http.lastURL = self.pyfile.url + +        self.req.http.c.setopt(FOLLOWLOCATION, 0) +        self.html = self.load(self.pyfile.url, decode=True) +        self.header = self.req.http.header +        self.req.http.c.setopt(FOLLOWLOCATION, 1) + +        location = None +        m = re.search(r"Location\s*:\s*(.*)", self.header, re.I) +        if m and re.match(self.LINK_PATTERN, m.group(1)): +            location = m.group(1).strip() + +        return location + + +    def handleFree(self): +        url = self.getDownloadLink() +        self.logDebug("Download URL: %s" % url) +        self.startDownload(url) + + +    def getDownloadLink(self): +        for i in xrange(5): +            self.logDebug("Getting download link: #%d" % i) +            data = self.getPostParameters() + +            self.req.http.c.setopt(FOLLOWLOCATION, 0) +            self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True) +            self.header = self.req.http.header +            self.req.http.c.setopt(FOLLOWLOCATION, 1) + +            m = re.search(r"Location\s*:\s*(.*)", self.header, re.I) +            if m: +                break + +            m = re.search(self.LINK_PATTERN, self.html, re.S) +            if m: +                break + +        else: +            if self.errmsg and 'captcha' in self.errmsg: +                self.fail("No valid captcha code entered") +            else: +                self.fail("Download link not found") + +        return m.group(1) + + +    def handlePremium(self): +        self.html = self.load(self.pyfile.url, post=self.getPostParameters()) +        m = re.search(self.LINK_PATTERN, self.html) +        if m is None: +            self.parseError('LINK_PATTERN not found') +        self.startDownload(m.group(1)) + + +    def handleOverriden(self): +        #only tested with easybytez.com +        self.html = self.load("http://www.%s/" % self.HOSTER_NAME) +        action, inputs = self.parseHtmlForm('') +        upload_id = "%012d" % int(random() * 10 ** 12) +        action += upload_id + "&js_on=1&utype=prem&upload_type=url" +        inputs['tos'] = '1' +        inputs['url_mass'] = self.pyfile.url +        inputs['up1oad_type'] = 'url' + +        self.logDebug(self.HOSTER_NAME, action, inputs) +        #wait for file to upload to easybytez.com +        self.req.http.c.setopt(LOW_SPEED_TIME, 600) +        self.html = self.load(action, post=inputs) + +        action, inputs = self.parseHtmlForm('F1') +        if not inputs: +            self.parseError('TEXTAREA not found') +        self.logDebug(self.HOSTER_NAME, inputs) +        if inputs['st'] == 'OK': +            self.html = self.load(action, post=inputs) +        elif inputs['st'] == 'Can not leech file': +            self.retry(max_tries=20, wait_time=3 * 60, reason=inputs['st']) +        else: +            self.fail(inputs['st']) + +        #get easybytez.com link for uploaded file +        m = re.search(self.OVR_LINK_PATTERN, self.html) +        if m is None: +            self.parseError('OVR_LINK_PATTERN not found') +        self.pyfile.url = m.group(1) +        header = self.load(self.pyfile.url, just_header=True) +        if 'location' in header:  # Direct link +            self.startDownload(self.pyfile.url) +        else: +            self.retry() + + +    def startDownload(self, link): +        link = link.strip() +        if self.captcha: +            self.correctCaptcha() +        self.logDebug("DIRECT LINK: %s" % link) +        self.download(link, disposition=True) + + +    def checkErrors(self): +        m = re.search(self.ERROR_PATTERN, self.html) +        if m: +            self.errmsg = m.group(1) +            self.logWarning(re.sub(r"<.*?>", " ", self.errmsg)) + +            if 'wait' in self.errmsg: +                wait_time = sum([int(v) * {"hour": 3600, "minute": 60, "second": 1}[u] for v, u in +                                 re.findall(r'(\d+)\s*(hour|minute|second)', self.errmsg)]) +                self.wait(wait_time, True) +            elif 'captcha' in self.errmsg: +                self.invalidCaptcha() +            elif 'premium' in self.errmsg and 'require' in self.errmsg: +                self.fail("File can be downloaded by premium users only") +            elif 'limit' in self.errmsg: +                self.wait(1 * 60 * 60, True) +                self.retry(25) +            elif 'countdown' in self.errmsg or 'Expired' in self.errmsg: +                self.retry() +            elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg: +                self.tempOffline() +            elif 'download files up to' in self.errmsg: +                self.fail("File too large for free download") +            else: +                self.fail(self.errmsg) + +        else: +            self.errmsg = None + +        return self.errmsg + + +    def getPostParameters(self): +        for _ in xrange(3): +            if not self.errmsg: +                self.checkErrors() + +            if hasattr(self, "FORM_PATTERN"): +                action, inputs = self.parseHtmlForm(self.FORM_PATTERN) +            else: +                action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")}) + +            if not inputs: +                action, inputs = self.parseHtmlForm('F1') +                if not inputs: +                    if self.errmsg: +                        self.retry() +                    else: +                        self.parseError("Form not found") + +            self.logDebug(self.HOSTER_NAME, inputs) + +            if 'op' in inputs and inputs['op'] in ("download2", "download3"): +                if "password" in inputs: +                    if self.passwords: +                        inputs['password'] = self.passwords.pop(0) +                    else: +                        self.fail("No or invalid passport") + +                if not self.premium: +                    m = re.search(self.WAIT_PATTERN, self.html) +                    if m: +                        wait_time = int(m.group(1)) + 1 +                        self.setWait(wait_time, False) +                    else: +                        wait_time = 0 + +                    self.captcha = self.handleCaptcha(inputs) + +                    if wait_time: +                        self.wait() + +                self.errmsg = None +                return inputs + +            else: +                inputs['referer'] = self.pyfile.url + +                if self.premium: +                    inputs['method_premium'] = "Premium Download" +                    if 'method_free' in inputs: +                        del inputs['method_free'] +                else: +                    inputs['method_free'] = "Free Download" +                    if 'method_premium' in inputs: +                        del inputs['method_premium'] + +                self.html = self.load(self.pyfile.url, post=inputs, ref=True) +                self.errmsg = None + +        else: +            self.parseError('FORM: %s' % (inputs['op'] if 'op' in inputs else 'UNKNOWN')) + + +    def handleCaptcha(self, inputs): +        m = re.search(self.CAPTCHA_URL_PATTERN, self.html) +        if m: +            captcha_url = m.group(1) +            inputs['code'] = self.decryptCaptcha(captcha_url) +            return 1 + +        m = re.search(self.CAPTCHA_DIV_PATTERN, self.html, re.DOTALL) +        if m: +            captcha_div = m.group(1) +            self.logDebug(captcha_div) +            numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div)) +            inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))]) +            self.logDebug("CAPTCHA", inputs['code'], numerals) +            return 2 + +        recaptcha = ReCaptcha(self) +        try: +            captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1) +        except: +            captcha_key = recaptcha.detect_key() + +        if captcha_key: +            self.logDebug("RECAPTCHA KEY: %s" % captcha_key) +            inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(captcha_key) +            return 3 + +        solvemedia = SolveMedia(self) +        try: +            captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1) +        except: +            captcha_key = solvemedia.detect_key() + +        if captcha_key: +            inputs['adcopy_challenge'], inputs['adcopy_response'] = solvemedia.challenge(captcha_key) +            return 4 + +        return 0 + + +getInfo = create_getInfo(XFSPHoster) diff --git a/pyload/plugins/internal/__init__.py b/pyload/plugins/internal/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/plugins/internal/__init__.py | 
