diff options
Diffstat (limited to 'pyload/plugins/addons')
35 files changed, 3854 insertions, 0 deletions
| diff --git a/pyload/plugins/addons/AlldebridCom.py b/pyload/plugins/addons/AlldebridCom.py new file mode 100644 index 000000000..6818b8c43 --- /dev/null +++ b/pyload/plugins/addons/AlldebridCom.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# should be working + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class AlldebridCom(MultiHoster): +    __name__ = "AlldebridCom" +    __version__ = "0.13" +    __type__ = "hook" + +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("https", "bool", "Enable HTTPS", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to stanard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + +    __description__ = """Real-Debrid.com hook plugin""" +    __author_name__ = ("Andy, Voigt") +    __author_mail__ = ("spamsales@online.de") + +    def getHoster(self): +        https = "https" if self.getConfig("https") else "http" +        page = getURL(https + "://www.alldebrid.com/api.php?action=get_host").replace("\"","").strip() +         +        return [x.strip() for x in page.split(",") if x.strip()] diff --git a/pyload/plugins/addons/BypassCaptcha.py b/pyload/plugins/addons/BypassCaptcha.py new file mode 100644 index 000000000..24ad17dd8 --- /dev/null +++ b/pyload/plugins/addons/BypassCaptcha.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: RaNaN, Godofdream, zoidberg +""" + +from thread import start_new_thread +from pycurl import FORM_FILE, LOW_SPEED_TIME + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader + +from module.plugins.Hook import Hook + +PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32" + +class BypassCaptchaException(Exception): +    def __init__(self, err): +        self.err = err + +    def getCode(self): +        return self.err + +    def __str__(self): +        return "<BypassCaptchaException %s>" % self.err + +    def __repr__(self): +        return "<BypassCaptchaException %s>" % self.err + +class BypassCaptcha(Hook): +    __name__ = "BypassCaptcha" +    __version__ = "0.03" +    __description__ = """send captchas to BypassCaptcha.com""" +    __config__ = [("activated", "bool", "Activated", True), +                  ("force", "bool", "Force BC even if client is connected", False), +                  ("passkey", "password", "Passkey", "")] +    __author_name__ = ("RaNaN", "Godofdream", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "soilfcition@gmail.com", "zoidberg@mujmail.cz") + +    SUBMIT_URL = "http://bypasscaptcha.com/upload.php" +    RESPOND_URL = "http://bypasscaptcha.com/check_value.php" +    GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php" + +    def setup(self): +        self.info = {} + +    def getCredits(self): +        response = getURL(self.GETCREDITS_URL, +                      post = {"key": self.getConfig("passkey")} +                      ) +                                                                          +        data = dict([x.split(' ',1) for x in response.splitlines()]) +        return int(data['Left']) +         + +    def submit(self, captcha, captchaType="file", match=None): +        req = getRequest() + +        #raise timeout threshold +        req.c.setopt(LOW_SPEED_TIME, 80) + +        try: +            response = req.load(self.SUBMIT_URL,  +                            post={"vendor_key": PYLOAD_KEY, +                                  "key": self.getConfig("passkey"), +                                  "gen_task_id": "1", +                                  "file": (FORM_FILE, captcha)}, +                            multipart=True) +        finally: +            req.close() + +        data = dict([x.split(' ',1) for x in response.splitlines()]) +        if not data or "Value" not in data: +            raise BypassCaptchaException(response) +             +        result = data['Value'] +        ticket = data['TaskId'] +        self.logDebug("result %s : %s" % (ticket,result)) + +        return ticket, result + +    def respond(self, ticket, success): +        try: +            response = getURL(self.RESPOND_URL,  +                              post={"task_id": ticket, +                                    "key": self.getConfig("passkey"), +                                    "cv": 1 if success else 0} +                              ) +        except BadHeader, e: +            self.logError("Could not send response.", str(e)) + +    def newCaptchaTask(self, task): +        if "service" in task.data: +            return False +         +        if not task.isTextual(): +            return False + +        if not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False + +        if self.getCredits() > 0: +            task.handler.append(self) +            task.data['service'] = self.__name__ +            task.setWaiting(100) +            start_new_thread(self.processCaptcha, (task,)) + +        else: +            self.logInfo("Your %s account has not enough credits" % self.__name__) + +    def captchaCorrect(self, task): +        if task.data['service'] == self.__name__ and "ticket" in task.data: +            self.respond(task.data["ticket"], True) + +    def captchaInvalid(self, task): +        if task.data['service'] == self.__name__ and "ticket" in task.data: +            self.respond(task.data["ticket"], False) + +    def processCaptcha(self, task): +        c = task.captchaFile +        try: +            ticket, result = self.submit(c) +        except BypassCaptchaException, e: +            task.error = e.getCode() +            return + +        task.data["ticket"] = ticket +        task.setResult(result)
\ No newline at end of file diff --git a/pyload/plugins/addons/CaptchaBrotherhood.py b/pyload/plugins/addons/CaptchaBrotherhood.py new file mode 100644 index 000000000..bdf547827 --- /dev/null +++ b/pyload/plugins/addons/CaptchaBrotherhood.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay, RaNaN, zoidberg +""" +from __future__ import with_statement + +from thread import start_new_thread + +import pycurl +import StringIO +from urllib import urlencode +from time import sleep +import Image + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader +from module.plugins.Hook import Hook + +class CaptchaBrotherhoodException(Exception): +    def __init__(self, err): +        self.err = err + +    def getCode(self): +        return self.err + +    def __str__(self): +        return "<CaptchaBrotherhoodException %s>" % self.err + +    def __repr__(self): +        return "<CaptchaBrotherhoodException %s>" % self.err + +class CaptchaBrotherhood(Hook): +    __name__ = "CaptchaBrotherhood" +    __version__ = "0.04" +    __description__ = """send captchas to CaptchaBrotherhood.com""" +    __config__ = [("activated", "bool", "Activated", False), +                  ("username", "str", "Username", ""), +                  ("force", "bool", "Force CT even if client is connected", False), +                  ("passkey", "password", "Password", ""),] +    __author_name__ = ("RaNaN", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz") +     +    API_URL = "http://www.captchabrotherhood.com/" + +    def setup(self): +        self.info = {} + +    def getCredits(self): +        response = getURL(self.API_URL + "askCredits.aspx", +                          get = {"username": self.getConfig("username"), +                                 "password": self.getConfig("passkey")}) +        if not response.startswith("OK"): +            raise CaptchaBrotherhoodException(response) +        else: +            credits = int(response[3:]) +            self.logInfo(_("%d credits left") % credits) +            self.info["credits"] = credits +            return credits + +    def submit(self, captcha, captchaType="file", match=None):                +        try: +            img = Image.open(captcha) +            output = StringIO.StringIO() +            self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode) +            if img.format in ("GIF", "JPEG"): +                img.save(output, img.format) +            else:     +                if img.mode != "RGB": +                    img = img.convert("RGB") +                img.save(output, "JPEG") +            data = output.getvalue() +            output.close() +        except Exception, e: +            raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e) +         +        req = getRequest() + +        url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL,  +                   urlencode({"username": self.getConfig("username"), +                              "password": self.getConfig("passkey"), +                              "captchaSource": "pyLoad", +                              "timeout": "80"}) +                   ) + +        req.c.setopt(pycurl.URL, url) +        req.c.setopt(pycurl.POST, 1) +        req.c.setopt(pycurl.POSTFIELDS, data) +        req.c.setopt(pycurl.HTTPHEADER, [ "Content-Type: text/html" ])         + +        try: +            req.c.perform() +            response = req.getResponse() +        except Exception, e: +            raise CaptchaBrotherhoodException("Submit captcha image failed") +             +        req.close() + +        if not response.startswith("OK"): +            raise CaptchaBrotherhoodException(response[1]) +        +        ticket = response[3:] +         +        for i in range(15): +            sleep(5) +            response = self.get_api("askCaptchaResult", ticket) +            if response.startswith("OK-answered"): +                return ticket, response[12:]  + +        raise CaptchaBrotherhoodException("No solution received in time") + +    def get_api(self, api, ticket): +        response = getURL("%s%s.aspx" % (self.API_URL, api),  +                          get={"username": self.getConfig("username"), +                               "password": self.getConfig("passkey"), +                               "captchaID": ticket} +                          ) +        if not response.startswith("OK"): +            raise CaptchaBrotherhoodException("Unknown response: %s" % response) +         +        return response + +    def newCaptchaTask(self, task): +        if "service" in task.data: +            return False +             +        if not task.isTextual(): +            return False + +        if not self.getConfig("username") or not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False + +        if self.getCredits() > 10: +            task.handler.append(self) +            task.data['service'] = self.__name__ +            task.setWaiting(100) +            start_new_thread(self.processCaptcha, (task,)) +        else: +            self.logInfo("Your CaptchaBrotherhood Account has not enough credits") + +    def captchaInvalid(self, task): +        if task.data['service'] == self.__name__ and "ticket" in task.data: +            response = self.get_api("complainCaptcha", task.data['ticket']) + +    def processCaptcha(self, task): +        c = task.captchaFile +        try: +            ticket, result = self.submit(c) +        except CaptchaBrotherhoodException, e: +            task.error = e.getCode() +            return + +        task.data["ticket"] = ticket +        task.setResult(result)
\ No newline at end of file diff --git a/pyload/plugins/addons/CaptchaTrader.py b/pyload/plugins/addons/CaptchaTrader.py new file mode 100644 index 000000000..889fa38ef --- /dev/null +++ b/pyload/plugins/addons/CaptchaTrader.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay, RaNaN +""" + +try: +    from json import loads +except ImportError: +    from simplejson import loads + +from thread import start_new_thread +from pycurl import FORM_FILE, LOW_SPEED_TIME + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader + +from module.plugins.Addon import Addon + +PYLOAD_KEY = "9f65e7f381c3af2b076ea680ae96b0b7" + +class CaptchaTraderException(Exception): +    def __init__(self, err): +        self.err = err + +    def getCode(self): +        return self.err + +    def __str__(self): +        return "<CaptchaTraderException %s>" % self.err + +    def __repr__(self): +        return "<CaptchaTraderException %s>" % self.err + +class CaptchaTrader(Addon): +    __name__ = "CaptchaTrader" +    __version__ = "0.14" +    __description__ = """send captchas to captchatrader.com""" +    __config__ = [("activated", "bool", "Activated", True), +                  ("username", "str", "Username", ""), +                  ("force", "bool", "Force CT even if client is connected", False), +                  ("passkey", "password", "Password", ""),] +    __author_name__ = ("RaNaN") +    __author_mail__ = ("RaNaN@pyload.org") + +    SUBMIT_URL = "http://api.captchatrader.com/submit" +    RESPOND_URL = "http://api.captchatrader.com/respond" +    GETCREDITS_URL = "http://api.captchatrader.com/get_credits/username:%(user)s/password:%(password)s/" + +    def setup(self): +        self.info = {} + +    def getCredits(self): +        json = getURL(CaptchaTrader.GETCREDITS_URL % {"user": self.getConfig("username"), +                                                           "password": self.getConfig("passkey")}) +        response = loads(json) +        if response[0] < 0: +            raise CaptchaTraderException(response[1]) +        else: +            self.logInfo(_("%s credits left") % response[1]) +            self.info["credits"] = response[1] +            return response[1] + +    def submit(self, captcha, captchaType="file", match=None): +        if not PYLOAD_KEY: +            raise CaptchaTraderException("No API Key Specified!") + +        #if type(captcha) == str and captchaType == "file": +        #    raise CaptchaTraderException("Invalid Type") +        assert captchaType in ("file", "url-jpg", "url-jpeg", "url-png", "url-bmp") + +        req = getRequest() + +        #raise timeout threshold +        req.c.setopt(LOW_SPEED_TIME, 80) + +        try: +            json = req.load(CaptchaTrader.SUBMIT_URL, post={"api_key": PYLOAD_KEY, +                                                           "username": self.getConfig("username"), +                                                           "password": self.getConfig("passkey"), +                                                           "value": (FORM_FILE, captcha), +                                                           "type": captchaType}, multipart=True) +        finally: +            req.close() + +        response = loads(json) +        if response[0] < 0: +            raise CaptchaTraderException(response[1]) + +        ticket = response[0] +        result = response[1] +        self.logDebug("result %s : %s" % (ticket,result)) + +        return ticket, result + +    def respond(self, ticket, success): +        try: +            json = getURL(CaptchaTrader.RESPOND_URL, post={"is_correct": 1 if success else 0, +                                                            "username": self.getConfig("username"), +                                                            "password": self.getConfig("passkey"), +                                                            "ticket": ticket}) + +            response = loads(json) +            if response[0] < 0: +                raise CaptchaTraderException(response[1]) + +        except BadHeader, e: +            self.logError(_("Could not send response."), str(e)) + +    def newCaptchaTask(self, task): +        if not task.isTextual(): +            return False + +        if not self.getConfig("username") or not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False + +        if self.getCredits() > 10: +            task.handler.append(self) +            task.setWaiting(100) +            start_new_thread(self.processCaptcha, (task,)) + +        else: +            self.logInfo(_("Your CaptchaTrader Account has not enough credits")) + +    def captchaCorrect(self, task): +        if "ticket" in task.data: +            ticket = task.data["ticket"] +            self.respond(ticket, True) + +    def captchaInvalid(self, task): +        if "ticket" in task.data: +            ticket = task.data["ticket"] +            self.respond(ticket, False) + +    def processCaptcha(self, task): +        c = task.captchaFile +        try: +            ticket, result = self.submit(c) +        except CaptchaTraderException, e: +            task.error = e.getCode() +            return + +        task.data["ticket"] = ticket +        task.setResult(result) diff --git a/pyload/plugins/addons/Checksum.py b/pyload/plugins/addons/Checksum.py new file mode 100644 index 000000000..b290838bb --- /dev/null +++ b/pyload/plugins/addons/Checksum.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: zoidberg +""" +from __future__ import with_statement +import hashlib, zlib +from os import remove +from os.path import getsize, isfile, splitext +import re + +from module.utils import save_join, fs_encode +from module.plugins.Hook import Hook + +def computeChecksum(local_file, algorithm):  +    if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")): +        h = getattr(hashlib, algorithm)() +        chunk_size = 128 * h.block_size +         +        with open(local_file, 'rb') as f:  +            for chunk in iter(lambda: f.read(chunk_size), ''):  +                 h.update(chunk) +         +        return h.hexdigest() +          +    elif algorithm in ("adler32", "crc32"): +        hf = getattr(zlib, algorithm) +        last = 0 +         +        with open(local_file, 'rb') as f:  +            for chunk in iter(lambda: f.read(8192), ''):  +                last = hf(chunk, last) +         +        return "%x" % last +     +    else: +        return None       + +class Checksum(Hook): +    __name__ = "Checksum" +    __version__ = "0.07" +    __description__ = "Verify downloaded file size and checksum (enable in general preferences)" +    __config__ = [("activated", "bool", "Activated", True), +                  ("action", "fail;retry;nothing", "What to do if check fails?", "retry"), +                  ("max_tries", "int", "Number of retries", 2)] +    __author_name__ = ("zoidberg") +    __author_mail__ = ("zoidberg@mujmail.cz") +     +    methods = { 'sfv':'crc32', 'crc': 'crc32', 'hash': 'md5'} +    regexps = { 'sfv': r'^(?P<name>[^;].+)\s+(?P<hash>[0-9A-Fa-f]{8})$', +                'md5': r'^(?P<name>[0-9A-Fa-f]{32})  (?P<file>.+)$', +                'crc': r'filename=(?P<name>.+)\nsize=(?P<size>\d+)\ncrc32=(?P<hash>[0-9A-Fa-f]{8})$',   +                'default': r'^(?P<hash>[0-9A-Fa-f]+)\s+\*?(?P<name>.+)$' } +     +    def setup(self):     +        if not self.config['general']['checksum']: +            self.logInfo("Checksum validation is disabled in general configuration") + +        self.algorithms = sorted(getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse = True) +        self.algorithms.extend(["crc32", "adler32"])                     +        self.formats = self.algorithms + ['sfv', 'crc', 'hash']                                    +              +    def downloadFinished(self, pyfile): +        """  +        Compute checksum for the downloaded file and compare it with the hash provided by the hoster. +        pyfile.plugin.check_data should be a dictionary which can contain: +        a) if known, the exact filesize in bytes (e.g. "size": 123456789) +        b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307")     +        """                 +        if hasattr(pyfile.plugin, "check_data") and (isinstance(pyfile.plugin.check_data, dict)): +            data = pyfile.plugin.check_data.copy()         +        elif hasattr(pyfile.plugin, "api_data") and (isinstance(pyfile.plugin.api_data, dict)): +            data = pyfile.plugin.api_data.copy()   +        else: +            return  +         +        self.logDebug(data)        +         +        if not pyfile.plugin.lastDownload: +            self.checkFailed(pyfile, None, "No file downloaded")  +                +        local_file = fs_encode(pyfile.plugin.lastDownload) +        #download_folder = self.config['general']['download_folder'] +        #local_file = fs_encode(save_join(download_folder, pyfile.package().folder, pyfile.name)) +         +        if not isfile(local_file): +            self.checkFailed(pyfile, None, "File does not exist")   +         +        # validate file size +        if "size" in data: +            api_size = int(data['size']) +            file_size = getsize(local_file) +            if api_size != file_size: +                self.logWarning("File %s has incorrect size: %d B (%d expected)" % (pyfile.name, file_size, api_size)) +                self.checkFailed(pyfile, local_file, "Incorrect file size") +            del data['size'] +                 +        # validate checksum +        if data and self.config['general']['checksum']:                                                        +            if "checksum" in data: +                data['md5'] = data['checksum'] +             +            for key in self.algorithms: +                if key in data:  +                    checksum = computeChecksum(local_file, key.replace("-","").lower())                     +                    if checksum: +                        if checksum == data[key]: +                            self.logInfo('File integrity of "%s" verified by %s checksum (%s).' % (pyfile.name, key.upper(), checksum)) +                            return +                        else: +                            self.logWarning("%s checksum for file %s does not match (%s != %s)" % (key.upper(), pyfile.name, checksum, data[key]))     +                            self.checkFailed(pyfile, local_file, "Checksums do not match") +                    else: +                        self.logWarning("Unsupported hashing algorithm: %s" % key.upper())   +            else: +                self.logWarning("Unable to validate checksum for file %s" % (pyfile.name)) +     +    def checkFailed(self, pyfile, local_file, msg): +        action = self.getConfig("action") +        if action == "fail": +            pyfile.plugin.fail(reason = msg) +        elif action == "retry": +            if local_file: +                remove(local_file) +            pyfile.plugin.retry(reason = msg, max_tries = self.getConfig("max_tries")) + +            +    def packageFinished(self, pypack): +        download_folder = save_join(self.config['general']['download_folder'], pypack.folder, "") +         +        for link in pypack.getChildren().itervalues(): +            file_type = splitext(link["name"])[1][1:].lower() +            #self.logDebug(link, file_type) +             +            if file_type not in self.formats: +                continue +             +            hash_file = fs_encode(save_join(download_folder, link["name"]))    +            if not isfile(hash_file): +                self.logWarning("File not found: %s" % link["name"]) +                continue +                 +            with open(hash_file) as f: +                text = f.read() +                 +            for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text): +                data = m.groupdict()  +                self.logDebug(link["name"], data) +                                +                local_file = fs_encode(save_join(download_folder, data["name"])) +                algorithm = self.methods.get(file_type, file_type) +                checksum = computeChecksum(local_file, algorithm) +                if checksum == data["hash"]: +                    self.logInfo('File integrity of "%s" verified by %s checksum (%s).' % (data["name"], algorithm, checksum)) +                else: +                    self.logWarning("%s checksum for file %s does not match (%s != %s)" % (algorithm, data["name"], checksum, data["hash"]))
\ No newline at end of file diff --git a/pyload/plugins/addons/ClickAndLoad.py b/pyload/plugins/addons/ClickAndLoad.py new file mode 100644 index 000000000..6d6928557 --- /dev/null +++ b/pyload/plugins/addons/ClickAndLoad.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: RaNaN +    @interface-version: 0.2 +""" + +import socket +import thread + +from module.plugins.Addon import Addon + +class ClickAndLoad(Addon): +    __name__ = "ClickAndLoad" +    __version__ = "0.2" +    __description__ = """Gives abillity to use jd's click and load. depends on webinterface""" +    __config__ = [("activated", "bool", "Activated", "True"), +                  ("extern", "bool", "Allow external link adding", "False")] +    __author_name__ = ("RaNaN", "mkaay") +    __author_mail__ = ("RaNaN@pyload.de", "mkaay@mkaay.de") + +    def activate(self): +        self.port = int(self.core.config['webinterface']['port']) +        if self.core.config['webinterface']['activated']: +            try: +                if self.getConfig("extern"): +                    ip = "0.0.0.0" +                else: +                    ip = "127.0.0.1" + +                thread.start_new_thread(proxy, (self, ip, self.port, 9666)) +            except: +                self.log.error("ClickAndLoad port already in use.") + + +def proxy(self, *settings): +    thread.start_new_thread(server, (self,) + settings) +    lock = thread.allocate_lock() +    lock.acquire() +    lock.acquire() + + +def server(self, *settings): +    try: +        dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +        dock_socket.bind((settings[0], settings[2])) +        dock_socket.listen(5) +        while True: +            client_socket = dock_socket.accept()[0] +            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +            server_socket.connect(("127.0.0.1", settings[1])) +            thread.start_new_thread(forward, (client_socket, server_socket)) +            thread.start_new_thread(forward, (server_socket, client_socket)) +    except socket.error, e: +        if hasattr(e, "errno"): +            errno = e.errno +        else: +            errno = e.args[0] + +        if errno == 98: +            self.core.log.warning(_("Click'N'Load: Port 9666 already in use")) +            return +        thread.start_new_thread(server, (self,) + settings) +    except: +        thread.start_new_thread(server, (self,) + settings) + + +def forward(source, destination): +    string = ' ' +    while string: +        string = source.recv(1024) +        if string: +            destination.sendall(string) +        else: +            #source.shutdown(socket.SHUT_RD) +            destination.shutdown(socket.SHUT_WR) diff --git a/pyload/plugins/addons/DeathByCaptcha.py b/pyload/plugins/addons/DeathByCaptcha.py new file mode 100644 index 000000000..59ff40ded --- /dev/null +++ b/pyload/plugins/addons/DeathByCaptcha.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay, RaNaN, zoidberg +""" +from __future__ import with_statement + +from thread import start_new_thread +from pycurl import FORM_FILE, HTTPHEADER, RESPONSE_CODE +from time import sleep +from base64 import b64encode +import re + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader +from module.plugins.Hook import Hook +from module.common.json_layer import json_loads + +class DeathByCaptchaException(Exception): +    DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials', +                  'invalid-credentials': 'Access denied, check your credentials', +                  'banned': 'Access denied, account is suspended', +                  'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA', +                  'invalid-captcha': 'CAPTCHA is not a valid image', +                  'service-overload': 'CAPTCHA was rejected due to service overload, try again later', +                  'invalid-request': 'Invalid request', +                  'timed-out': 'No CAPTCHA solution received in time' }  +     +    def __init__(self, err): +        self.err = err + +    def getCode(self): +        return self.err +         +    def getDesc(self): +        if self.err in self.DBC_ERRORS.keys(): +            return self.DBC_ERRORS[self.err] +        else: +            return self.err + +    def __str__(self): +        return "<DeathByCaptchaException %s>" % self.err + +    def __repr__(self): +        return "<DeathByCaptchaException %s>" % self.err + +class DeathByCaptcha(Hook): +    __name__ = "DeathByCaptcha" +    __version__ = "0.03" +    __description__ = """send captchas to DeathByCaptcha.com""" +    __config__ = [("activated", "bool", "Activated", False), +                  ("username", "str", "Username", ""), +                  ("passkey", "password", "Password", ""), +                  ("force", "bool", "Force DBC even if client is connected", False)] +    __author_name__ = ("RaNaN", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz") + +    API_URL = "http://api.dbcapi.me/api/" + +    def setup(self): +        self.info = {} + +    def call_api(self, api="captcha", post=False, multipart=False): +        req = getRequest() +        req.c.setopt(HTTPHEADER, ["Accept: application/json",  +                                  "User-Agent: pyLoad %s" % self.core.version]) +         +        if post: +            if not isinstance(post, dict): +                post = {} +            post.update({"username": self.getConfig("username"), +                         "password": self.getConfig("passkey")})                           +         +        response = None +        try: +            json = req.load("%s%s" % (self.API_URL, api),  +                            post = post, +                            multipart=multipart) +            self.logDebug(json)             +            response = json_loads(json) +             +            if "error" in response: +                raise DeathByCaptchaException(response['error']) +            elif "status" not in response: +                raise DeathByCaptchaException(str(response)) +             +        except BadHeader, e: +            if 403 == e.code: +                raise DeathByCaptchaException('not-logged-in')             +            elif 413 == e.code: +                raise DeathByCaptchaException('invalid-captcha') +            elif 503 == e.code: +                raise DeathByCaptchaException('service-overload') +            elif e.code in (400, 405): +                raise DeathByCaptchaException('invalid-request') +            else: +                raise +                                                    +        finally: +            req.close() +             +        return response +         +    def getCredits(self): +        response = self.call_api("user", True) + +        if 'is_banned' in response and response['is_banned']: +            raise DeathByCaptchaException('banned') +        elif 'balance' in response and 'rate' in response: +            self.info.update(response) +        else: +            raise DeathByCaptchaException(response) +             +    def getStatus(self): +        response = self.call_api("status", False) + +        if 'is_service_overloaded' in response and response['is_service_overloaded']: +            raise DeathByCaptchaException('service-overload') + +    def submit(self, captcha, captchaType="file", match=None): +        #workaround multipart-post bug in HTTPRequest.py  +        if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")): +            multipart = True +            data = (FORM_FILE, captcha) +        else: +            multipart = False +            with open(captcha, 'rb') as f: +                data = f.read() +            data = "base64:" + b64encode(data)          +         +        response = self.call_api("captcha", {"captchafile": data}, multipart) +         +        if "captcha" not in response: +            raise DeathByCaptchaException(response) +        ticket = response['captcha'] +         +        for i in range(24): +            sleep(5) +            response = self.call_api("captcha/%d" % ticket, False)                +            if response['text'] and response['is_correct']: +                break +        else: +            raise DeathByCaptchaException('timed-out')             +                 +        result = response['text'] +        self.logDebug("result %s : %s" % (ticket,result)) + +        return ticket, result + +    def newCaptchaTask(self, task): +        if "service" in task.data: +            return False +     +        if not task.isTextual(): +            return False + +        if not self.getConfig("username") or not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False +         +        try: +            self.getStatus() +            self.getCredits()                                                          +        except DeathByCaptchaException, e: +            self.logError(e.getDesc()) +            return False +         +        balance, rate = self.info["balance"], self.info["rate"] +        self.logInfo("Account balance: US$%.3f (%d captchas left at %.2f cents each)" % (balance / 100, balance // rate, rate)) +             +        if balance > rate:             +            task.handler.append(self) +            task.data['service'] = self.__name__ +            task.setWaiting(180) +            start_new_thread(self.processCaptcha, (task,))         + +    def captchaInvalid(self, task): +        if task.data['service'] == self.__name__ and "ticket" in task.data: +            try: +                response = self.call_api("captcha/%d/report" % task.data["ticket"], True) +            except DeathByCaptchaException, e: +                self.logError(e.getDesc()) +            except Exception, e: +                self.logError(e) + +    def processCaptcha(self, task): +        c = task.captchaFile +        try: +            ticket, result = self.submit(c) +        except DeathByCaptchaException, e: +            task.error = e.getCode() +            self.logError(e.getDesc()) +            return + +        task.data["ticket"] = ticket +        task.setResult(result)
\ No newline at end of file diff --git a/pyload/plugins/addons/DebridItaliaCom.py b/pyload/plugins/addons/DebridItaliaCom.py new file mode 100644 index 000000000..80cdc45f6 --- /dev/null +++ b/pyload/plugins/addons/DebridItaliaCom.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# This program is free software: you can redistribute it and/or modify     # +# it under the terms of the GNU Affero 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 Affero General Public License for more details.                      # +#                                                                          # +# You should have received a copy of the GNU Affero General Public License # +# along with this program.  If not, see <http://www.gnu.org/licenses/>.    # +############################################################################ + +from module.plugins.internal.MultiHoster import MultiHoster + + +class DebridItaliaCom(MultiHoster): +    __name__ = "DebridItaliaCom" +    __version__ = "0.05" +    __type__ = "hook" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + +    __description__ = """Debriditalia.com hook plugin""" +    __author_name__ = ("stickell") +    __author_mail__ = ("l.stickell@yahoo.it") + +    def getHoster(self): +        return ["netload.in", "hotfile.com", "rapidshare.com", "multiupload.com", +                "uploading.com", "megashares.com", "crocko.com", "filepost.com", +                "bitshare.com", "share-links.biz", "putlocker.com", "uploaded.to", +                "speedload.org", "rapidgator.net", "likeupload.net", "cyberlocker.ch", +                "depositfiles.com", "extabit.com", "filefactory.com", "sharefiles.co", +                "ryushare.com", "tusfiles.net", "nowvideo.co", "cloudzer.net", "letitbit.net"] diff --git a/pyload/plugins/addons/DeleteFinished.py b/pyload/plugins/addons/DeleteFinished.py new file mode 100644 index 000000000..e0df69eef --- /dev/null +++ b/pyload/plugins/addons/DeleteFinished.py @@ -0,0 +1,86 @@ +  # -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: Walter Purcaro +""" + +from module.plugins.Hook import Hook +from time import time + + +class DeleteFinished(Hook): +    __name__ = "DeleteFinished" +    __version__ = "0.5" +    __description__ = "Automatically delete finished packages from queue" +    __config__ = [ +        ("activated", "bool", "Activated", "False"), +        ("interval", "int", "Delete every (hours)", "72") +    ] +    __author_name__ = ("Walter Purcaro") +    __author_mail__ = ("vuolter@gmail.com") + +    def wakeup(self, pypack): +        # self.logDebug("self.wakeup") +        self.removeEvent("packageFinished", self.wakeup) +        self.info["sleep"] = False + +    def periodical(self): +        # self.logDebug("self.periodical") +        if not self.info["sleep"]: +            self.core.api.deleteFinished() +            self.logDebug("called self.core.api.deleteFinished") +            self.info["sleep"] = True +            self.addEvent("packageFinished", self.wakeup) + +    def addEvent(self, event, handler): +        if event in self.manager.events: +            if handler not in self.manager.events[event]: +                self.manager.events[event].append(handler) +                # self.logDebug("self.addEvent: " + event + ": added handler") +            else: +                # self.logDebug("self.addEvent: " + event + ": NOT added handler") +                return False +        else: +            self.manager.events[event] = [handler] +            # self.logDebug("self.addEvent: " + event + ": added event and handler") +        return True + +    def removeEvent(self, event, handler): +        if event in self.manager.events and handler in self.manager.events[event]: +            self.manager.events[event].remove(handler) +            # self.logDebug("self.removeEvent: " + event + ": removed handler") +            return True +        else: +            # self.logDebug("self.removeEvent: " + event + ": NOT removed handler") +            return False + +    def configEvents(self, plugin=None, name=None, value=None): +        # self.logDebug("self.configEvents") +        interval = self.getConfig("interval") * 3600 +        if interval != self.interval: +            self.interval = interval + +    def unload(self): +        # self.logDebug("self.unload") +        self.removeEvent("pluginConfigChanged", self.configEvents) +        self.removeEvent("packageFinished", self.wakeup) + +    def coreReady(self): +        # self.logDebug("self.coreReady") +        self.info = {"sleep": True} +        self.addEvent("pluginConfigChanged", self.configEvents) +        self.configEvents() +        self.addEvent("packageFinished", self.wakeup) diff --git a/pyload/plugins/addons/DownloadScheduler.py b/pyload/plugins/addons/DownloadScheduler.py new file mode 100644 index 000000000..4049d71c5 --- /dev/null +++ b/pyload/plugins/addons/DownloadScheduler.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +""" +    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. +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. +     +    @author: zoidberg +    Original idea by new.cze +""" + +import re +from time import localtime + +from module.plugins.Hook import Hook + + +class DownloadScheduler(Hook): +    __name__ = "DownloadScheduler" +    __version__ = "0.21" +    __description__ = """Download Scheduler""" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("timetable", "str", "List time periods as hh:mm full or number(kB/s)", +                   "0:00 full, 7:00 250, 10:00 0, 17:00 150"), +                  ("abort", "bool", "Abort active downloads when start period with speed 0", "False")] +    __author_name__ = ("zoidberg", "stickell") +    __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it") + +    def setup(self): +        self.cb = None  # callback to scheduler job; will be by removed hookmanager when hook unloaded + +    def coreReady(self): +        self.updateSchedule() + +    def updateSchedule(self, schedule=None): +        if schedule is None: +            schedule = self.getConfig("timetable") + +        schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)", +                              schedule.lower().replace("full", "-1").replace("none", "0")) +        if not schedule: +            self.logError("Invalid schedule") +            return + +        t0 = localtime() +        now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X") +        schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now]) + +        self.logDebug("Schedule", schedule) + +        for i, v in enumerate(schedule): +            if v[3] == "X": +                last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)] +                self.logDebug("Now/Last/Next", now, last, next) + +                self.setDownloadSpeed(last[3]) + +                next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400 +                self.core.scheduler.removeJob(self.cb) +                self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False) + +    def setDownloadSpeed(self, speed): +        if speed == 0: +            abort = self.getConfig("abort") +            self.logInfo("Stopping download server. (Running downloads will %sbe aborted.)" % ('' if abort else 'not ')) +            self.core.api.pauseServer() +            if abort: +                self.core.api.stopAllDownloads() +        else: +            self.core.api.unpauseServer() + +            if speed > 0: +                self.logInfo("Setting download speed to %d kB/s" % speed) +                self.core.api.setConfigValue("download", "limit_speed", 1) +                self.core.api.setConfigValue("download", "max_speed", speed) +            else: +                self.logInfo("Setting download speed to FULL") +                self.core.api.setConfigValue("download", "limit_speed", 0) +                self.core.api.setConfigValue("download", "max_speed", -1) diff --git a/pyload/plugins/addons/EasybytezCom.py b/pyload/plugins/addons/EasybytezCom.py new file mode 100644 index 000000000..6a4ded85b --- /dev/null +++ b/pyload/plugins/addons/EasybytezCom.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster +import re + +class EasybytezCom(MultiHoster): +    __name__ = "EasybytezCom" +    __version__ = "0.03" +    __type__ = "hook" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", "")] +    __description__ = """EasyBytez.com hook plugin""" +    __author_name__ = ("zoidberg") +    __author_mail__ = ("zoidberg@mujmail.cz") + +    def getHoster(self): +        self.account = self.core.accountManager.getAccountPlugin(self.__name__) +        user = self.account.selectAccount()[0] +         +        try: +            req = self.account.getAccountRequest(user) +            page = req.load("http://www.easybytez.com") +         +            found = re.search(r'</textarea>\s*Supported sites:(.*)', page) +            return found.group(1).split(',') +        except Exception, e: +            self.logDebug(e) +            self.logWarning("Unable to load supported hoster list, using last known") +            return ['bitshare.com', 'crocko.com', 'ddlstorage.com', 'depositfiles.com', 'extabit.com', 'hotfile.com', 'mediafire.com', 'netload.in', 'rapidgator.net', 'rapidshare.com', 'uploading.com', 'uload.to', 'uploaded.to']
\ No newline at end of file diff --git a/pyload/plugins/addons/Ev0InFetcher.py b/pyload/plugins/addons/Ev0InFetcher.py new file mode 100644 index 000000000..608baf217 --- /dev/null +++ b/pyload/plugins/addons/Ev0InFetcher.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay +""" +from module.lib import feedparser +from time import mktime, time + +from module.plugins.Addon import Addon + +class Ev0InFetcher(Addon): +    __name__ = "Ev0InFetcher" +    __version__ = "0.2" +    __description__ = """checks rss feeds for ev0.in""" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("interval", "int", "Check interval in minutes", "10"), +                  ("queue", "bool", "Move new shows directly to Queue", False), +                  ("shows", "str", "Shows to check for (comma separated)", ""), +                  ("quality", "xvid;x264;rmvb", "Video Format", "xvid"), +                  ("hoster", "str", "Hoster to use (comma separated)", "NetloadIn,RapidshareCom,MegauploadCom,HotfileCom")] +    __author_name__ = ("mkaay") +    __author_mail__ = ("mkaay@mkaay.de") + +    def setup(self): +        self.interval = self.getConfig("interval") * 60 + +    def filterLinks(self, links): +        results = self.core.pluginManager.parseUrls(links) +        sortedLinks = {} +         +        for url, hoster in results[0]: +            if hoster not in sortedLinks: +                sortedLinks[hoster] = [] +            sortedLinks[hoster].append(url) +         +        for h in self.getConfig("hoster").split(","): +            try: +                return sortedLinks[h.strip()] +            except: +                continue +        return [] +     +    def periodical(self): +        def normalizefiletitle(filename): +            filename = filename.replace('.', ' ') +            filename = filename.replace('_', ' ') +            filename = filename.lower() +            return filename +         +        shows = [s.strip() for s in self.getConfig("shows").split(",")] +         +        feed = feedparser.parse("http://feeds.feedburner.com/ev0in/%s?format=xml" % self.getConfig("quality")) + +        showStorage = {} +        for show in shows: +            showStorage[show] = int(self.getStorage("show_%s_lastfound" % show, 0)) +         +        found = False +        for item in feed['items']: +            for show, lastfound in showStorage.iteritems(): +                if show.lower() in normalizefiletitle(item['title']) and lastfound < int(mktime(item.date_parsed)): +                    links = self.filterLinks(item['description'].split("<br />")) +                    packagename = item['title'].encode("utf-8") +                    self.core.log.info("Ev0InFetcher: new episode '%s' (matched '%s')" % (packagename, show)) +                    self.core.api.addPackage(packagename, links, 1 if self.getConfig("queue") else 0) +                    self.setStorage("show_%s_lastfound" % show, int(mktime(item.date_parsed))) +                    found = True +        if not found: +            #self.core.log.debug("Ev0InFetcher: no new episodes found") +            pass + +        for show, lastfound in self.getStorage().iteritems(): +            if int(lastfound) > 0 and int(lastfound) + (3600*24*30) < int(time()): +                self.delStorage("show_%s_lastfound" % show) +                self.core.log.debug("Ev0InFetcher: cleaned '%s' record" % show) diff --git a/pyload/plugins/addons/ExpertDecoders.py b/pyload/plugins/addons/ExpertDecoders.py new file mode 100644 index 000000000..2e66e49ca --- /dev/null +++ b/pyload/plugins/addons/ExpertDecoders.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay, RaNaN, zoidberg +""" +from __future__ import with_statement + +from thread import start_new_thread +from pycurl import FORM_FILE, LOW_SPEED_TIME +from uuid import uuid4 +from base64 import b64encode + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader + +from module.plugins.Hook import Hook + +class ExpertDecoders(Hook): +    __name__ = "ExpertDecoders" +    __version__ = "0.01" +    __description__ = """send captchas to expertdecoders.com""" +    __config__ = [("activated", "bool", "Activated", False), +                  ("force", "bool", "Force CT even if client is connected", False), +                  ("passkey", "password", "Access key", ""),] +    __author_name__ = ("RaNaN", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz") + +    API_URL = "http://www.fasttypers.org/imagepost.ashx" + +    def setup(self): +        self.info = {} + +    def getCredits(self):     +        response = getURL(self.API_URL, post = { "key": self.getConfig("passkey"), "action": "balance" }) +         +        if response.isdigit():                 +            self.logInfo(_("%s credits left") % response) +            self.info["credits"] = credits = int(response) +            return credits  +        else: +            self.logError(response) +            return 0 +         +    def processCaptcha(self, task):         +        task.data["ticket"] = ticket = uuid4() +        result = None +         +        with open(task.captchaFile, 'rb') as f: +            data = f.read()         +        data = b64encode(data)          +        #self.logDebug("%s: %s : %s" % (ticket, task.captchaFile, data)) + +        req = getRequest() +        #raise timeout threshold +        req.c.setopt(LOW_SPEED_TIME, 80) +         +        try: +            result = req.load(self.API_URL,  +                              post={ "action": "upload", +                                     "key": self.getConfig("passkey"), +                                     "file": data,  +                            		   	 "gen_task_id": ticket } +                              ) +        finally: +            req.close() + +        self.logDebug("result %s : %s" % (ticket, result)) +        task.setResult(result) + +    def newCaptchaTask(self, task): +        if not task.isTextual(): +            return False + +        if not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False + +        if self.getCredits() > 0: +            task.handler.append(self) +            task.setWaiting(100) +            start_new_thread(self.processCaptcha, (task,)) + +        else: +            self.logInfo(_("Your ExpertDecoders Account has not enough credits")) + +    def captchaInvalid(self, task): +        if "ticket" in task.data: +             +            try: +                response = getURL(self.API_URL,  +                              post={ "action": "refund", +                                     "key": self.getConfig("passkey"), +                                     "gen_task_id": task.data["ticket"] } +                              ) +                self.logInfo("Request refund: %s" % response) + +            except BadHeader, e: +                self.logError("Could not send refund request.", str(e))
\ No newline at end of file diff --git a/pyload/plugins/addons/ExternalScripts.py b/pyload/plugins/addons/ExternalScripts.py new file mode 100644 index 000000000..8f5a5841e --- /dev/null +++ b/pyload/plugins/addons/ExternalScripts.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: RaNaN +""" + +import subprocess +from os import access, X_OK, makedirs +from os.path import basename + +from module.plugins.Addon import Addon +from module.utils.fs import save_join, exists, join, listdir + +class ExternalScripts(Addon): +    __name__ = "ExternalScripts" +    __version__ = "0.22" +    __description__ = """Run external scripts""" +    __config__ = [("activated", "bool", "Activated", "True")] +    __author_name__ = ("mkaay", "RaNaN", "spoob") +    __author_mail__ = ("mkaay@mkaay.de", "ranan@pyload.org", "spoob@pyload.org") + +    event_list = ["unrarFinished", "allDownloadsFinished", "allDownloadsProcessed"] + +    def setup(self): +        self.scripts = {} + +        folders = ['download_preparing', 'download_finished', 'package_finished', +                   'before_reconnect', 'after_reconnect', 'unrar_finished', +                   'all_dls_finished', 'all_dls_processed'] + +        for folder in folders: + +            self.scripts[folder] = [] + +            self.initPluginType(folder, join(pypath, 'scripts', folder)) +            self.initPluginType(folder, join('scripts', folder)) + +        for script_type, names in self.scripts.iteritems(): +            if names: +                self.logInfo((_("Installed scripts for %s: ") % script_type ) + ", ".join([basename(x) for x in names])) + + +    def initPluginType(self, folder, path): +        if not exists(path): +            try: +                makedirs(path) +            except : +                self.logDebug("Script folder %s not created" % folder) +                return + +        for f in listdir(path): +            if f.startswith("#") or f.startswith(".") or f.startswith("_") or f.endswith("~") or f.endswith(".swp"): +                continue + +            if not access(join(path,f), X_OK): +                self.logWarning(_("Script not executable:") + " %s/%s" % (folder, f)) + +            self.scripts[folder].append(join(path, f)) + +    def callScript(self, script, *args): +        try: +            cmd = [script] + [str(x) if not isinstance(x, basestring) else x for x in args] +            #output goes to pyload +            subprocess.Popen(cmd, bufsize=-1) +        except Exception, e: +            self.logError(_("Error in %(script)s: %(error)s") % { "script" :basename(script), "error": str(e)}) + +    def downloadPreparing(self, pyfile): +        for script in self.scripts['download_preparing']: +            self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.id) + +    def downloadFinished(self, pyfile): +        for script in self.scripts['download_finished']: +            self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.name, +                            save_join(self.core.config['general']['download_folder'], pyfile.package().folder, pyfile.name), +                            pyfile.id) + + +    def packageFinished(self, pypack): +        for script in self.scripts['package_finished']: +            folder = self.core.config['general']['download_folder'] +            folder = save_join(folder, pypack.folder) + +            self.callScript(script, pypack.name, folder, pypack.password, pypack.id) + +    def beforeReconnecting(self, ip): +        for script in self.scripts['before_reconnect']: +            self.callScript(script, ip) + +    def afterReconnecting(self, ip): +        for script in self.scripts['after_reconnect']: +            self.callScript(script, ip) + +    def unrarFinished(self, folder, fname): +        for script in self.scripts["unrar_finished"]: +            self.callScript(script, folder, fname) + +    def allDownloadsFinished(self): +        for script in self.scripts["all_dls_finished"]: +            self.callScript(script) + +    def allDownloadsProcessed(self): +        for script in self.scripts["all_dls_processed"]: +            self.callScript(script) + diff --git a/pyload/plugins/addons/ExtractArchive.py b/pyload/plugins/addons/ExtractArchive.py new file mode 100644 index 000000000..369b20ba9 --- /dev/null +++ b/pyload/plugins/addons/ExtractArchive.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os +from os.path import basename, isfile, isdir, join +from traceback import print_exc +from copy import copy + +# monkey patch bug in python 2.6 and lower +# see http://bugs.python.org/issue6122 +# http://bugs.python.org/issue1236 +# http://bugs.python.org/issue1731717 +if sys.version_info < (2, 7) and os.name != "nt": +    from subprocess import Popen +    import errno + +    def _eintr_retry_call(func, *args): +        while True: +            try: +                return func(*args) +            except OSError, e: +                if e.errno == errno.EINTR: +                    continue +                raise + +    # unsued timeout option for older python version +    def wait(self, timeout=0): +        """Wait for child process to terminate.  Returns returncode +        attribute.""" +        if self.returncode is None: +            try: +                pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) +            except OSError, e: +                if e.errno != errno.ECHILD: +                    raise +                    # This happens if SIGCLD is set to be ignored or waiting +                # for child processes has otherwise been disabled for our +                # process.  This child is dead, we can't get the status. +                sts = 0 +            self._handle_exitstatus(sts) +        return self.returncode + +    Popen.wait = wait + +if os.name != "nt": +    from os import chown +    from pwd import getpwnam +    from grp import getgrnam + +from module.utils.fs import save_join, fs_encode, exists, remove, chmod, makedirs +from module.plugins.Addon import Addon, threaded, Expose +from module.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword + +class ExtractArchive(Addon): +    """ +    Provides: unrarFinished (folder, filename) +    """ +    __name__ = "ExtractArchive" +    __version__ = "0.12" +    __description__ = "Extract different kind of archives" +    __config__ = [("activated", "bool", "Activated", True), +        ("fullpath", "bool", "Extract full path", True), +        ("overwrite", "bool", "Overwrite files", True), +        ("passwordfile", "file", "password file", "unrar_passwords.txt"), +        ("deletearchive", "bool", "Delete archives when done", False), +        ("subfolder", "bool", "Create subfolder for each package", False), +        ("destination", "folder", "Extract files to", ""), +        ("recursive", "bool", "Extract archives in archvies", True), +        ("queue", "bool", "Wait for all downloads to be finished", True), +        ("renice", "int", "CPU Priority", 0), ] +    __author_name__ = ("pyload Team") +    __author_mail__ = ("admin<at>pyload.org") + +    event_list = ["allDownloadsProcessed"] + +    def setup(self): +        self.plugins = [] +        self.passwords = [] +        names = [] + +        for p in ("UnRar", "UnZip"): +            try: +                module = self.core.pluginManager.loadModule("internal", p) +                klass = getattr(module, p) +                if klass.checkDeps(): +                    names.append(p) +                    self.plugins.append(klass) + +            except OSError, e: +                if e.errno == 2: +                    self.logInfo(_("No %s installed") % p) +                else: +                    self.logWarning(_("Could not activate %s") % p, str(e)) +                    if self.core.debug: +                        print_exc() + +            except Exception, e: +                self.logWarning(_("Could not activate %s") % p, str(e)) +                if self.core.debug: +                    print_exc() + +        if names: +            self.logInfo(_("Activated") + " " + " ".join(names)) +        else: +            self.logInfo(_("No Extract plugins activated")) + +        # queue with package ids +        self.queue = [] + +    @Expose +    def extractPackage(self, id): +        """ Extract package with given id""" +        self.manager.startThread(self.extract, [id]) + +    def packageFinished(self, pypack): +        if self.getConfig("queue"): +            self.logInfo(_("Package %s queued for later extracting") % pypack.name) +            self.queue.append(pypack.id) +        else: +            self.manager.startThread(self.extract, [pypack.id]) + + +    @threaded +    def allDownloadsProcessed(self, thread): +        local = copy(self.queue) +        del self.queue[:] +        self.extract(local, thread) + + +    def extract(self, ids, thread=None): +        # reload from txt file +        self.reloadPasswords() + +        # dl folder +        dl = self.config['general']['download_folder'] + +        extracted = [] + +        #iterate packages -> plugins -> targets +        for pid in ids: +            p = self.core.files.getPackage(pid) +            self.logInfo(_("Check package %s") % p.name) +            if not p: continue + +            # determine output folder +            out = save_join(dl, p.folder, "") +            # force trailing slash + +            if self.getConfig("destination") and self.getConfig("destination").lower() != "none": + +                out = save_join(dl, p.folder, self.getConfig("destination"), "") +                #relative to package folder if destination is relative, otherwise absolute path overwrites them + +                if self.getConf("subfolder"): +                    out = join(out, fs_encode(p.folder)) + +                if not exists(out): +                    makedirs(out) + +            files_ids = [(save_join(dl, p.folder, x["name"]), x["id"]) for x in p.getChildren().itervalues()] +            matched = False + +            # check as long there are unseen files +            while files_ids: +                new_files_ids = [] + +                for plugin in self.plugins: +                    targets = plugin.getTargets(files_ids) +                    if targets: +                        self.logDebug("Targets for %s: %s" % (plugin.__name__, targets)) +                        matched = True +                    for target, fid in targets: +                        if target in extracted: +                            self.logDebug(basename(target), "skipped") +                            continue +                        extracted.append(target) #prevent extracting same file twice + +                        klass = plugin(self, target, out, self.getConfig("fullpath"), self.getConfig("overwrite"), +                            self.getConfig("renice")) +                        klass.init() + +                        self.logInfo(basename(target), _("Extract to %s") % out) +                        new_files = self.startExtracting(klass, fid, p.password.strip().splitlines(), thread) +                        self.logDebug("Extracted: %s" % new_files) +                        self.setPermissions(new_files) + +                        for file in new_files: +                            if not exists(file): +                                self.logDebug("new file %s does not exists" % file) +                                continue +                            if self.getConfig("recursive") and isfile(file): +                                new_files_ids.append((file, fid)) #append as new target + +                files_ids = new_files_ids # also check extracted files + +            if not matched: self.logInfo(_("No files found to extract")) + +    def startExtracting(self, plugin, fid, passwords, thread): +        pyfile = self.core.files.getFile(fid) +        if not pyfile: return [] + +        pyfile.setCustomStatus(_("extracting")) +        thread.addActive(pyfile) #keep this file until everything is done + +        try: +            progress = lambda x: pyfile.setProgress(x) +            success = False + +            if not plugin.checkArchive(): +                plugin.extract(progress) +                success = True +            else: +                self.logInfo(basename(plugin.file), _("Password protected")) +                self.logDebug("Passwords: %s" % str(passwords)) + +                pwlist = copy(self.getPasswords()) +                #remove already supplied pws from list (only local) +                for pw in passwords: +                    if pw in pwlist: pwlist.remove(pw) + +                for pw in passwords + pwlist: +                    try: +                        self.logDebug("Try password: %s" % pw) +                        if plugin.checkPassword(pw): +                            plugin.extract(progress, pw) +                            self.addPassword(pw) +                            success = True +                            break +                    except WrongPassword: +                        self.logDebug("Password was wrong") + +            if not success: +                self.logError(basename(plugin.file), _("Wrong password")) +                return [] + +            if self.core.debug: +                self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles())) + +            if self.getConfig("deletearchive"): +                files = plugin.getDeleteFiles() +                self.logInfo(_("Deleting %s files") % len(files)) +                for f in files: +                    if exists(f): remove(f) +                    else: self.logDebug("%s does not exists" % f) + +            self.logInfo(basename(plugin.file), _("Extracting finished")) +            self.manager.dispatchEvent("unrarFinished", plugin.out, plugin.file) + +            return plugin.getExtractedFiles() + + +        except ArchiveError, e: +            self.logError(basename(plugin.file), _("Archive Error"), str(e)) +        except CRCError: +            self.logError(basename(plugin.file), _("CRC Mismatch")) +        except Exception, e: +            if self.core.debug: +                print_exc() +            self.logError(basename(plugin.file), _("Unknown Error"), str(e)) + +        return [] + +    @Expose +    def getPasswords(self): +        """ List of saved passwords """ +        return self.passwords + + +    def reloadPasswords(self): +        pwfile = self.getConfig("passwordfile") +        if not exists(pwfile): +            open(pwfile, "wb").close() + +        passwords = [] +        f = open(pwfile, "rb") +        for pw in f.read().splitlines(): +            passwords.append(pw) +        f.close() + +        self.passwords = passwords + + +    @Expose +    def addPassword(self, pw): +        """  Adds a password to saved list""" +        pwfile = self.getConfig("passwordfile") + +        if pw in self.passwords: self.passwords.remove(pw) +        self.passwords.insert(0, pw) + +        f = open(pwfile, "wb") +        for pw in self.passwords: +            f.write(pw + "\n") +        f.close() + +    def setPermissions(self, files): +        for f in files: +            if not exists(f): continue +            try: +                if self.core.config["permission"]["change_file"]: +                    if isfile(f): +                        chmod(f, int(self.core.config["permission"]["file"], 8)) +                    elif isdir(f): +                        chmod(f, int(self.core.config["permission"]["folder"], 8)) + +                if self.core.config["permission"]["change_dl"] and os.name != "nt": +                    uid = getpwnam(self.config["permission"]["user"])[2] +                    gid = getgrnam(self.config["permission"]["group"])[2] +                    chown(f, uid, gid) +            except Exception, e: +                self.log.warning(_("Setting User and Group failed"), e) diff --git a/pyload/plugins/addons/HotFolder.py b/pyload/plugins/addons/HotFolder.py new file mode 100644 index 000000000..d05026448 --- /dev/null +++ b/pyload/plugins/addons/HotFolder.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: RaNaN +    @interface-version: 0.2 +""" + +from os import makedirs +from os import listdir +from os.path import exists +from os.path import join +from os.path import isfile +from shutil import move +import time + +from module.plugins.Addon import Addon + +class HotFolder(Addon): +    __name__ = "HotFolder" +    __version__ = "0.1" +    __description__ = """observe folder and file for changes and add container and links""" +    __config__ = [ ("activated", "bool", "Activated" , "False"), +                   ("folder", "str", "Folder to observe", "container"), +                   ("watch_file", "bool", "Observe link file", "False"), +                   ("keep", "bool", "Keep added containers", "True"), +                   ("file", "str", "Link file", "links.txt")] +    __threaded__ = [] +    __author_name__ = ("RaNaN") +    __author_mail__ = ("RaNaN@pyload.de") +     +    def setup(self): +        self.interval = 10 +         +    def periodical(self): +         +        if not exists(join(self.getConfig("folder"), "finished")): +            makedirs(join(self.getConfig("folder"), "finished")) +           +        if self.getConfig("watch_file"): + +            if not exists(self.getConfig("file")): +                f = open(self.getConfig("file"), "wb") +                f.close() +             +             +            f = open(self.getConfig("file"), "rb") +            content = f.read().strip() +            f.close() +            f = open(self.getConfig("file"), "wb") +            f.close() +            if content: +                name = "%s_%s.txt" % (self.getConfig("file"), time.strftime("%H-%M-%S_%d%b%Y") ) + +                f = open(join(self.getConfig("folder"), "finished", name), "wb") +                f.write(content) +                f.close() + +                self.core.api.addPackage(f.name, [f.name], 1) +               +        for f in listdir(self.getConfig("folder")): +            path = join(self.getConfig("folder"), f) +             +            if not isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."): +                continue +             +            newpath = join(self.getConfig("folder"), "finished", f if self.getConfig("keep") else "tmp_"+f) +            move(path, newpath) +             +            self.log.info(_("Added %s from HotFolder") % f) +            self.core.api.addPackage(f, [newpath], 1) +             +        
\ No newline at end of file diff --git a/pyload/plugins/addons/IRCInterface.py b/pyload/plugins/addons/IRCInterface.py new file mode 100644 index 000000000..c261fc6f3 --- /dev/null +++ b/pyload/plugins/addons/IRCInterface.py @@ -0,0 +1,431 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: RaNaN +    @author: jeix +    @interface-version: 0.2 +""" + +from select import select +import socket +from threading import Thread +import time +from time import sleep +from traceback import print_exc +import re + +from module.plugins.Addon import Addon +from module.network.RequestFactory import getURL +from module.utils import formatSize +from module.Api import PackageDoesNotExists, FileDoesNotExists + +from pycurl import FORM_FILE + +class IRCInterface(Thread, Addon): +    __name__ = "IRCInterface" +    __version__ = "0.1" +    __description__ = """connect to irc and let owner perform different tasks""" +    __config__ = [("activated", "bool", "Activated", "False"), +        ("host", "str", "IRC-Server Address", "Enter your server here!"), +        ("port", "int", "IRC-Server Port", "6667"), +        ("ident", "str", "Clients ident", "pyload-irc"), +        ("realname", "str", "Realname", "pyload-irc"), +        ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"), +        ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"), +        ("info_file", "bool", "Inform about every file finished", "False"), +        ("info_pack", "bool", "Inform about every package finished", "True"), +        ("captcha", "bool", "Send captcha requests", "True")] +    __author_name__ = ("Jeix") +    __author_mail__ = ("Jeix@hasnomail.com") +     +    def __init__(self, core, manager): +        Thread.__init__(self) +        Addon.__init__(self, core, manager) +        self.setDaemon(True) +        #   self.sm = core.server_methods +        self.api = core.api #todo, only use api +         +    def coreReady(self): +        self.new_package = {} +         +        self.abort = False +         +        self.links_added = 0 +        self.more = [] + +        self.start() +         +         +    def packageFinished(self, pypack): +        try: +            if self.getConfig("info_pack"): +                self.response(_("Package finished: %s") % pypack.name) +        except: +            pass +         +    def downloadFinished(self, pyfile): +        try: +            if self.getConfig("info_file"): +                self.response(_("Download finished: %(name)s @ %(plugin)s ") % { "name" : pyfile.name, "plugin": pyfile.pluginname} ) +        except: +            pass + +    def newCaptchaTask(self, task): +        if self.getConfig("captcha") and task.isTextual(): +            task.handler.append(self) +            task.setWaiting(60) + +            page = getURL("http://www.freeimagehosting.net/upload.php", post={"attached" : (FORM_FILE, task.captchaFile)}, multipart=True) + +            url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).group(1) +            self.response(_("New Captcha Request: %s") % url) +            self.response(_("Answer with 'c %s text on the captcha'") % task.id) + +    def run(self): +        # connect to IRC etc. +        self.sock = socket.socket() +        host = self.getConfig("host") +        self.sock.connect((host, self.getConfig("port"))) +        nick = self.getConfig("nick") +        self.sock.send("NICK %s\r\n" % nick) +        self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick)) +        for t in self.getConfig("owner").split(): +            if t.strip().startswith("#"): +                self.sock.send("JOIN %s\r\n" % t.strip()) +        self.log.info("pyLoad IRC: Connected to %s!" % host) +        self.log.info("pyLoad IRC: Switching to listening mode!") +        try:         +            self.main_loop() +             +        except IRCError, ex: +            self.sock.send("QUIT :byebye\r\n") +            print_exc() +            self.sock.close() + +             +    def main_loop(self): +        readbuffer = "" +        while True: +            sleep(1) +            fdset = select([self.sock], [], [], 0) +            if self.sock not in fdset[0]: +                continue +             +            if self.abort: +                raise IRCError("quit") +             +            readbuffer += self.sock.recv(1024) +            temp = readbuffer.split("\n") +            readbuffer = temp.pop() + +            for line in temp: +                line  = line.rstrip() +                first = line.split() + +                if first[0] == "PING": +                    self.sock.send("PONG %s\r\n" % first[1]) +                     +                if first[0] == "ERROR": +                    raise IRCError(line) +                     +                msg = line.split(None, 3) +                if len(msg) < 4: +                    continue +                     +                msg = { +                    "origin":msg[0][1:], +                    "action":msg[1], +                    "target":msg[2], +                    "text":msg[3][1:] +                } +                 +                self.handle_events(msg) +         +         +    def handle_events(self, msg): +        if not msg["origin"].split("!", 1)[0] in self.getConfig("owner").split(): +            return +             +        if msg["target"].split("!", 1)[0] != self.getConfig("nick"): +            return +             +        if msg["action"] != "PRIVMSG": +            return +             +        # HANDLE CTCP ANTI FLOOD/BOT PROTECTION +        if msg["text"] == "\x01VERSION\x01": +            self.log.debug("Sending CTCP VERSION.") +            self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface")) +            return +        elif msg["text"] == "\x01TIME\x01": +            self.log.debug("Sending CTCP TIME.") +            self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time())) +            return +        elif msg["text"] == "\x01LAG\x01": +            self.log.debug("Received CTCP LAG.") # don't know how to answer +            return +          +        trigger = "pass" +        args = None + +        try: +            temp = msg["text"].split() +            trigger = temp[0] +            if len(temp) > 1: +                args = temp[1:] +        except: +            pass + +        handler = getattr(self, "event_%s" % trigger, self.event_pass) +        try: +            res = handler(args) +            for line in res: +                self.response(line, msg["origin"]) +        except Exception, e: +            self.log.error("pyLoad IRC: "+ repr(e)) +         +         +    def response(self, msg, origin=""): +        if origin == "": +            for t in self.getConfig("owner").split(): +                self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg)) +        else: +            self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg)) +         +         +#### Events +    def event_pass(self, args): +        return [] +         +    def event_status(self, args): +        downloads = self.api.statusDownloads() +        if not downloads: +            return ["INFO: There are no active downloads currently."] +             +        temp_progress = "" +        lines = ["ID - Name - Status - Speed - ETA - Progress"] +        for data in downloads: + +            if data.status == 5: +                temp_progress = data.format_wait +            else: +                temp_progress = "%d%% (%s)" % (data.percent, data.format_size) + +            lines.append("#%d - %s - %s - %s - %s - %s" % +                     ( +                     data.fid, +                     data.name, +                     data.statusmsg, +                     "%s/s" % formatSize(data.speed), +                     "%s" % data.format_eta, +                     temp_progress +                     ) +                     ) +        return lines +             +    def event_queue(self, args): +        ps = self.api.getQueueData() +         +        if not ps: +            return ["INFO: There are no packages in queue."] +         +        lines = [] +        for pack in ps: +            lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links) )) +                 +        return lines +         +    def event_collector(self, args): +        ps = self.api.getCollectorData() +        if not ps: +            return ["INFO: No packages in collector!"] +         +        lines = [] +        for pack in ps: +            lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links) )) +                 +        return lines +             +    def event_info(self, args): +        if not args: +            return ['ERROR: Use info like this: info <id>'] +             +        info = None +        try: +            info = self.api.getFileData(int(args[0])) +         +        except FileDoesNotExists: +            return ["ERROR: Link doesn't exists."] + +        return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, +                                                info.plugin)] +         +    def event_packinfo(self, args): +        if not args: +            return ['ERROR: Use packinfo like this: packinfo <id>'] +             +        lines = [] +        pack = None +        try: +            pack = self.api.getPackageData(int(args[0])) +         +        except PackageDoesNotExists: +            return ["ERROR: Package doesn't exists."] +         +        id = args[0] + +        self.more = [] +         +        lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links)) ) +        for pyfile in pack.links: +            self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size, +                                                             pyfile.statusmsg, pyfile.plugin)) + +        if len(self.more) < 6: +            lines.extend(self.more) +            self.more = [] +        else: +            lines.extend(self.more[:6]) +            self.more = self.more[6:] +            lines.append("%d more links do display." % len(self.more)) +             +             +        return lines +     +    def event_more(self, args): +        if not self.more: +            return ["No more information to display."] +         +        lines = self.more[:6] +        self.more = self.more[6:] +        lines.append("%d more links do display." % len(self.more)) +         +        return lines +     +    def event_start(self, args): +         +        self.api.unpauseServer() +        return ["INFO: Starting downloads."] +         +    def event_stop(self, args): +     +        self.api.pauseServer() +        return ["INFO: No new downloads will be started."] +     +     +    def event_add(self, args): +        if len(args) < 2: +            return ['ERROR: Add links like this: "add <packagename|id> links". ', +                     'This will add the link <link> to to the package <package> / the package with id <id>!'] +             + +             +        pack = args[0].strip() +        links = [x.strip() for x in args[1:]] +         +        count_added = 0 +        count_failed = 0 +        try: +            id = int(pack)  +            pack = self.api.getPackageData(id) +            if not pack: +                return ["ERROR: Package doesn't exists."] +             +            #TODO add links +             +            return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack["name"], id)] +             +        except: +            # create new package +            id = self.api.addPackage(pack, links, 1) +            return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))] +              +         +    def event_del(self, args): +        if len(args) < 2: +            return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] +             +        if args[0] == "-p": +            ret = self.api.deletePackages(map(int, args[1:])) +            return ["INFO: Deleted %d packages!" % len(args[1:])] +             +        elif args[0] == "-l": +            ret = self.api.delLinks(map(int, args[1:])) +            return ["INFO: Deleted %d links!" % len(args[1:])] + +        else: +            return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] +             +    def event_push(self, args): +        if not args: +            return ["ERROR: Push package to queue like this: push <package id>"] + +        id = int(args[0]) +        try: +            info = self.api.getPackageInfo(id) +        except PackageDoesNotExists: +            return ["ERROR: Package #%d does not exist." % id] + +        self.api.pushToQueue(id) +        return ["INFO: Pushed package #%d to queue." % id] + +    def event_pull(self, args): +        if not args: +            return ["ERROR: Pull package from queue like this: pull <package id>."] + +        id = int(args[0]) +        if not self.api.getPackageData(id): +            return ["ERROR: Package #%d does not exist." % id] + +        self.api.pullFromQueue(id) +        return ["INFO: Pulled package #%d from queue to collector." % id] + +    def event_c(self, args): +        """ captcha answer """ +        if not args: +            return ["ERROR: Captcha ID missing."] + +        task = self.core.captchaManager.getTaskByID(args[0]) +        if not task: +            return ["ERROR: Captcha Task with ID %s does not exists." % args[0]] +         +        task.setResult(" ".join(args[1:])) +        return ["INFO: Result %s saved." % " ".join(args[1:])] + + +    def event_help(self, args): +        lines = ["The following commands are available:", +                 "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)", +                 "queue                       Shows all packages in the queue", +                 "collector                   Shows all packages in collector", +                 "del -p|-l <id> [...]        Deletes all packages|links with the ids specified", +                 "info <id>                   Shows info of the link with id <id>", +                 "packinfo <id>               Shows info of the package with id <id>", +                 "more                        Shows more info when the result was truncated", +                 "start                       Starts all downloads", +                 "stop                        Stops the download (but not abort active downloads)", +                 "push <id>                   Push package to queue", +                 "pull <id>                   Pull package from queue", +                 "status                      Show general download status", +                 "help                        Shows this help message"] +        return lines +         +         +class IRCError(Exception): +    def __init__(self, value): +        self.value = value +    def __str__(self): +        return repr(self.value) diff --git a/pyload/plugins/addons/ImageTyperz.py b/pyload/plugins/addons/ImageTyperz.py new file mode 100644 index 000000000..59b6334a7 --- /dev/null +++ b/pyload/plugins/addons/ImageTyperz.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: mkaay, RaNaN, zoidberg +""" +from __future__ import with_statement + +from thread import start_new_thread +from pycurl import FORM_FILE, LOW_SPEED_TIME + +from module.network.RequestFactory import getURL, getRequest +from module.network.HTTPRequest import BadHeader + +from module.plugins.Hook import Hook +import re +from base64 import b64encode + +class ImageTyperzException(Exception): +    def __init__(self, err): +        self.err = err + +    def getCode(self): +        return self.err + +    def __str__(self): +        return "<ImageTyperzException %s>" % self.err + +    def __repr__(self): +        return "<ImageTyperzException %s>" % self.err + +class ImageTyperz(Hook): +    __name__ = "ImageTyperz" +    __version__ = "0.03" +    __description__ = """send captchas to ImageTyperz.com""" +    __config__ = [("activated", "bool", "Activated", True), +                  ("username", "str", "Username", ""), +                  ("passkey", "password", "Password", ""), +                  ("force", "bool", "Force IT even if client is connected", False)] +    __author_name__ = ("RaNaN", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz") + +    SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx" +    RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx" +    GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx" + +    def setup(self): +        self.info = {} + +    def getCredits(self): +        response = getURL(self.GETCREDITS_URL, +                      post = {"action": "REQUESTBALANCE", +                              "username": self.getConfig("username"), +                              "password": self.getConfig("passkey")} +                      ) +                                                                          +        if response.startswith('ERROR'): +            raise ImageTyperzException(response) +             +        try: +            balance = float(response) +        except: +            raise ImageTyperzException("invalid response") +             +        self.logInfo("Account balance: $%s left" % response) +        return balance  + +    def submit(self, captcha, captchaType="file", match=None): +        req = getRequest() +        #raise timeout threshold +        req.c.setopt(LOW_SPEED_TIME, 80) +         +        try: +            #workaround multipart-post bug in HTTPRequest.py  +            if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")): +                multipart = True +                data = (FORM_FILE, captcha) +            else: +                multipart = False +                with open(captcha, 'rb') as f: +                    data = f.read() +                data = b64encode(data) +                 +            response = req.load(self.SUBMIT_URL, +                                post={ "action": "UPLOADCAPTCHA", +                                       "username": self.getConfig("username"), +                                       "password": self.getConfig("passkey"), +                                       "file": data}, +                                multipart = multipart) +        finally: +            req.close() + +        if response.startswith("ERROR"): +            raise ImageTyperzException(response) +        else: +            data = response.split('|') +            if len(data) == 2: +                ticket, result = data +            else: +                raise ImageTyperzException("Unknown response %s" % response)       +         +        return ticket, result + +    def newCaptchaTask(self, task): +        if "service" in task.data: +            return False +         +        if not task.isTextual(): +            return False + +        if not self.getConfig("username") or not self.getConfig("passkey"): +            return False + +        if self.core.isClientConnected() and not self.getConfig("force"): +            return False + +        if self.getCredits() > 0: +            task.handler.append(self) +            task.data['service'] = self.__name__ +            task.setWaiting(100) +            start_new_thread(self.processCaptcha, (task,)) + +        else: +            self.logInfo("Your %s account has not enough credits" % self.__name__) + +    def captchaInvalid(self, task): +        if task.data['service'] == self.__name__ and "ticket" in task.data: +            response = getURL(self.RESPOND_URL, +                              post={"action": "SETBADIMAGE", +                                    "username": self.getConfig("username"), +                                    "password": self.getConfig("passkey"), +                                    "imageid": task.data["ticket"]} +                              ) +             +            if response == "SUCCESS": +                self.logInfo("Bad captcha solution received, requested refund") +            else: +                self.logError("Bad captcha solution received, refund request failed", response)  + +    def processCaptcha(self, task): +        c = task.captchaFile +        try: +            ticket, result = self.submit(c) +        except ImageTyperzException, e: +            task.error = e.getCode() +            return + +        task.data["ticket"] = ticket +        task.setResult(result)
\ No newline at end of file diff --git a/pyload/plugins/addons/LinkdecrypterCom.py b/pyload/plugins/addons/LinkdecrypterCom.py new file mode 100644 index 000000000..d3d6bce68 --- /dev/null +++ b/pyload/plugins/addons/LinkdecrypterCom.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: zoidberg +""" + +import re + +from module.plugins.Hook import Hook +from module.network.RequestFactory import getURL +from module.utils import remove_chars + +class LinkdecrypterCom(Hook): +    __name__ = "LinkdecrypterCom" +    __version__ = "0.17" +    __description__ = """linkdecrypter.com - regexp loader""" +    __config__ = [ ("activated", "bool", "Activated" , "True") ] +    __author_name__ = ("zoidberg") + +    def coreReady(self): +        page = getURL("http://linkdecrypter.com/") +        m = re.search(r'<b>Supported\(\d+\)</b>: <i>([^+<]*)', page) +        if not m: +            self.logError(_("Crypter list not found")) +            return + +        builtin = [ name.lower() for name in self.core.pluginManager.crypterPlugins.keys() ] +        builtin.extend([ "downloadserienjunkiesorg" ]) + +        crypter_pattern = re.compile("(\w[\w.-]+)") +        online = [] +        for crypter in m.group(1).split(', '): +            m = re.match(crypter_pattern, crypter) +            if m and remove_chars(m.group(1), "-.") not in builtin: +                online.append(m.group(1).replace(".", "\\.")) + +        if not online: +            self.logError(_("Crypter list is empty")) +            return +              +        regexp = r"https?://([^.]+\.)*?(%s)/.*" % "|".join(online) + +        dict = self.core.pluginManager.crypterPlugins[self.__name__] +        dict["pattern"] = regexp +        dict["re"] = re.compile(regexp) +         +        self.logDebug("REGEXP: " + regexp) diff --git a/pyload/plugins/addons/MergeFiles.py b/pyload/plugins/addons/MergeFiles.py new file mode 100644 index 000000000..48f997681 --- /dev/null +++ b/pyload/plugins/addons/MergeFiles.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: and9000 +""" + +import os +import re +import sys +import traceback + +from os.path import join +from module.utils import save_join, fs_encode +from module.plugins.Addon import Addon + +BUFFER_SIZE = 4096 + +class MergeFiles(Addon): +    __name__ = "MergeFiles" +    __version__ = "0.1" +    __description__ = "Merges parts splitted with hjsplit" +    __config__ = [ +        ("activated" , "bool" , "Activated"  , "False"), +        ] +    __threaded__ = ["packageFinished"] +    __author_name__ = ("and9000") +    __author_mail__ = ("me@has-no-mail.com") + +    def setup(self): +        # nothing to do +        pass +         +    def packageFinished(self, pack): +        files = {} +        fid_dict = {} +        for fid, data in pack.getChildren().iteritems(): +            if re.search("\.[0-9]{3}$", data["name"]): +                if data["name"][:-4] not in files: +                    files[data["name"][:-4]] = [] +                files[data["name"][:-4]].append(data["name"]) +                files[data["name"][:-4]].sort() +                fid_dict[data["name"]] = fid +                 +        download_folder = self.core.config['general']['download_folder'] +                 +        if self.core.config['general']['folder_per_package']: +            download_folder = save_join(download_folder, pack.folder) + +        for name, file_list in files.iteritems(): +            self.core.log.info("Starting merging of %s" % name) +            final_file = open(join(download_folder, fs_encode(name)), "wb") + +            for splitted_file in file_list: +                self.core.log.debug("Merging part %s" % splitted_file) +                pyfile = self.core.files.getFile(fid_dict[splitted_file]) +                pyfile.setStatus("processing") +                try: +                    s_file = open(os.path.join(download_folder, splitted_file), "rb") +                    size_written = 0 +                    s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file))) +                    while True: +                        f_buffer = s_file.read(BUFFER_SIZE) +                        if f_buffer: +                            final_file.write(f_buffer) +                            size_written += BUFFER_SIZE +                            pyfile.setProgress((size_written*100)/s_file_size) +                        else: +                            break +                    s_file.close() +                    self.core.log.debug("Finished merging part %s" % splitted_file) +                except Exception, e: +                    print traceback.print_exc() +                finally: +                    pyfile.setProgress(100) +                    pyfile.setStatus("finished") +                    pyfile.release() +                     +            final_file.close() +            self.core.log.info("Finished merging of %s" % name) +                 + diff --git a/pyload/plugins/addons/MultiDebridCom.py b/pyload/plugins/addons/MultiDebridCom.py new file mode 100644 index 000000000..c95138648 --- /dev/null +++ b/pyload/plugins/addons/MultiDebridCom.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# This program is free software: you can redistribute it and/or modify     # +# it under the terms of the GNU Affero 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 Affero General Public License for more details.                      # +#                                                                          # +# You should have received a copy of the GNU Affero General Public License # +# along with this program.  If not, see <http://www.gnu.org/licenses/>.    # +############################################################################ + +from module.plugins.internal.MultiHoster import MultiHoster +from module.network.RequestFactory import getURL +from module.common.json_layer import json_loads + + +class MultiDebridCom(MultiHoster): +    __name__ = "MultiDebridCom" +    __version__ = "0.01" +    __type__ = "hook" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to standard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + +    __description__ = """Multi-debrid.com hook plugin""" +    __author_name__ = ("stickell") +    __author_mail__ = ("l.stickell@yahoo.it") + +    def getHoster(self): +        json_data = getURL('http://multi-debrid.com/api.php?hosts', decode=True) +        self.logDebug('JSON data: ' + json_data) +        json_data = json_loads(json_data) + +        return json_data['hosts'] diff --git a/pyload/plugins/addons/MultiHome.py b/pyload/plugins/addons/MultiHome.py new file mode 100644 index 000000000..af3f55416 --- /dev/null +++ b/pyload/plugins/addons/MultiHome.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: mkaay +""" + +from module.plugins.Addon import Addon +from time import time + +class MultiHome(Addon): +    __name__ = "MultiHome" +    __version__ = "0.1" +    __description__ = """ip address changer""" +    __config__ = [ ("activated", "bool", "Activated" , "False"), +                   ("interfaces", "str", "Interfaces" , "None") ] +    __author_name__ = ("mkaay") +    __author_mail__ = ("mkaay@mkaay.de") +     +    def setup(self): +        self.register = {} +        self.interfaces = [] +        self.parseInterfaces(self.getConfig("interfaces").split(";")) +        if not self.interfaces: +            self.parseInterfaces([self.config["download"]["interface"]]) +            self.setConfig("interfaces", self.toConfig()) +     +    def toConfig(self): +        return ";".join([i.adress for i in self.interfaces]) +     +    def parseInterfaces(self, interfaces): +        for interface in interfaces: +            if not interface or str(interface).lower() == "none": +                continue +            self.interfaces.append(Interface(interface)) +     +    def coreReady(self): +        requestFactory = self.core.requestFactory +        oldGetRequest = requestFactory.getRequest +        def getRequest(pluginName, account=None): +            iface = self.bestInterface(pluginName, account) +            if iface: +                iface.useFor(pluginName, account) +                requestFactory.iface = lambda: iface.adress +                self.log.debug("Multihome: using address: "+iface.adress) +            return oldGetRequest(pluginName, account) +        requestFactory.getRequest = getRequest +     +    def bestInterface(self, pluginName, account): +        best = None +        for interface in self.interfaces: +            if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account): +                best = interface +        return best + +class Interface(object): +    def __init__(self, adress): +        self.adress = adress +        self.history = {} +     +    def lastPluginAccess(self, pluginName, account): +        if (pluginName, account) in self.history: +            return self.history[(pluginName, account)] +        return 0 +     +    def useFor(self, pluginName, account): +        self.history[(pluginName, account)] = time() +     +    def __repr__(self): +        return "<Interface - %s>" % self.adress diff --git a/pyload/plugins/addons/MultiHoster.py b/pyload/plugins/addons/MultiHoster.py new file mode 100644 index 000000000..825085df8 --- /dev/null +++ b/pyload/plugins/addons/MultiHoster.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from types import MethodType + +from module.plugins.MultiHoster import MultiHoster as MultiHosterAccount, normalize +from module.plugins.Addon import Addon, AddEventListener +from module.plugins.PluginManager import PluginTuple + +class MultiHoster(Addon): +    __version__ = "0.1" +    __internal__ = True +    __description__ = "Gives ability to use MultiHoster services. You need to add your account first." +    __config__ = [] +    __author_mail__ = ("pyLoad Team",) +    __author_mail__ = ("support@pyload.org",) + +    #TODO: multiple accounts - multihoster / config options + +    def init(self): + +        # overwritten plugins +        self.plugins = {} + +    def addHoster(self, account): + +        self.logDebug("New MultiHoster %s" % account.__name__) + +        pluginMap = {} +        for name in self.core.pluginManager.getPlugins("hoster").keys(): +            pluginMap[name.lower()] = name + +        supported = [] +        new_supported = [] + +        for hoster in account.getHosterList(): +            name = normalize(hoster) + +            if name in pluginMap: +                supported.append(pluginMap[name]) +            else: +                new_supported.append(hoster) + +        if not supported and not new_supported: +            account.logError(_("No Hoster loaded")) +            return + +        klass = self.core.pluginManager.getPluginClass(account.__name__) + +        # inject plugin plugin +        account.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(supported))) +        for hoster in supported: +            self.plugins[hoster] = klass + +        account.logDebug("New Hosters: %s" % ", ".join(sorted(new_supported))) + +        # create new regexp +        regexp = r".*(%s).*" % "|".join([klass.__pattern__] + [x.replace(".", "\\.") for x in new_supported]) + +        # recreate plugin tuple for new regexp +        hoster = self.core.pluginManager.getPlugins("hoster") +        p = hoster[account.__name__] +        new = PluginTuple(p.version, re.compile(regexp), p.deps, p.user, p.path) +        hoster[account.__name__] = new + + + +    @AddEventListener("account:deleted") +    def refreshAccounts(self, plugin=None, user=None): + +        self.plugins = {} + +        for name, account in self.core.accountManager.iterAccounts(): +            if isinstance(account, MultiHosterAccount) and account.isUsable(): +                self.addHoster(account) + +    @AddEventListener("account:updated") +    def refreshAccount(self, plugin, user): + +        account = self.core.accountManager.getAccount(plugin, user) +        if isinstance(account, MultiHosterAccount) and account.isUsable(): +            self.addHoster(account) + +    def activate(self): +        self.refreshAccounts() + +        # new method for plugin manager +        def getPlugin(self2, name): +            if name in self.plugins: +                return self.plugins[name] +            return self2.getPluginClass(name) + +        pm = self.core.pluginManager +        pm.getPlugin = MethodType(getPlugin, pm, object) + + +    def deactivate(self): +        #restore state +        pm = self.core.pluginManager +        pm.getPlugin = pm.getPluginClass + diff --git a/pyload/plugins/addons/MultishareCz.py b/pyload/plugins/addons/MultishareCz.py new file mode 100644 index 000000000..7e5a3e007 --- /dev/null +++ b/pyload/plugins/addons/MultishareCz.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster +import re + +class MultishareCz(MultiHoster): +    __name__ = "MultishareCz" +    __version__ = "0.04" +    __type__ = "hook" +    __config__ = [("activated", "bool", "Activated", "False"), +        ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +        ("hosterList", "str", "Hoster list (comma separated)", "uloz.to")] +    __description__ = """MultiShare.cz hook plugin""" +    __author_name__ = ("zoidberg") +    __author_mail__ = ("zoidberg@mujmail.cz") + +    HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="([^"]+)"></td>\s*<td class="stav">[^>]*?alt="OK"' + +    def getHoster(self): + +        page = getURL("http://www.multishare.cz/monitoring/") +        return re.findall(self.HOSTER_PATTERN, page)
\ No newline at end of file diff --git a/pyload/plugins/addons/Premium4Me.py b/pyload/plugins/addons/Premium4Me.py new file mode 100644 index 000000000..b4e4f822f --- /dev/null +++ b/pyload/plugins/addons/Premium4Me.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class Premium4Me(MultiHoster): +    __name__ = "Premium4Me" +    __version__ = "0.02" +    __type__ = "hook" + +    __config__ = [("activated", "bool", "Activated", "False"), +        ("hosterListMode", "all;listed;unlisted", "Use for downloads from supported hosters:", "all"), +        ("hosterList", "str", "Hoster list (comma separated)", "")] +    __description__ = """premium4.me hook plugin""" +    __author_name__ = ("RaNaN", "zoidberg") +    __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz") + +    def getHoster(self): + +        page = getURL("http://premium4.me/api/hosters.php?authcode=%s" % self.account.authcode) +        return [x.strip() for x in page.replace("\"", "").split(";")] + +    def coreReady(self): + +        self.account = self.core.accountManager.getAccountPlugin("Premium4Me") + +        user = self.account.selectAccount()[0] + +        if not user: +            self.logError(_("Please add your premium4.me account first and restart pyLoad")) +            return + +        return MultiHoster.coreReady(self)
\ No newline at end of file diff --git a/pyload/plugins/addons/PremiumizeMe.py b/pyload/plugins/addons/PremiumizeMe.py new file mode 100644 index 000000000..a10c24f85 --- /dev/null +++ b/pyload/plugins/addons/PremiumizeMe.py @@ -0,0 +1,50 @@ +from module.plugins.internal.MultiHoster import MultiHoster + +from module.common.json_layer      import json_loads +from module.network.RequestFactory import getURL + +class PremiumizeMe(MultiHoster): +    __name__ = "PremiumizeMe" +    __version__ = "0.12" +    __type__ = "hook" +    __description__ = """Premiumize.Me hook plugin""" + +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),  +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to stanard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + +    __author_name__ = ("Florian Franzen") +    __author_mail__ = ("FlorianFranzen@gmail.com") +     +    def getHoster(self):      +        # If no accounts are available there will be no hosters available +        if not self.account or not self.account.canUse(): +            return [] +         +        # Get account data +        (user, data) = self.account.selectAccount() +         +        # Get supported hosters list from premiumize.me using the json API v1 (see https://secure.premiumize.me/?show=api) +        answer = getURL("https://api.premiumize.me/pm-api/v1.php?method=hosterlist¶ms[login]=%s¶ms[pass]=%s" % (user, data['password'])) +        data = json_loads(answer) +         +         +        # If account is not valid thera are no hosters available +        if data['status'] != 200: +            return [] +         +        # Extract hosters from json file  +        return data['result']['hosterlist']  +             +    def coreReady(self): +        # Get account plugin and check if there is a valid account available +        self.account = self.core.accountManager.getAccountPlugin("PremiumizeMe")      +        if not self.account.canUse(): +            self.account = None +            self.logError(_("Please add a valid premiumize.me account first and restart pyLoad.")) +            return +                   +        # Run the overwriten core ready which actually enables the multihoster hook  +        return MultiHoster.coreReady(self)
\ No newline at end of file diff --git a/pyload/plugins/addons/RealdebridCom.py b/pyload/plugins/addons/RealdebridCom.py new file mode 100644 index 000000000..be74b47c3 --- /dev/null +++ b/pyload/plugins/addons/RealdebridCom.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class RealdebridCom(MultiHoster): +    __name__ = "RealdebridCom" +    __version__ = "0.43" +    __type__ = "hook" + +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("https", "bool", "Enable HTTPS", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to stanard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] +    __description__ = """Real-Debrid.com hook plugin""" +    __author_name__ = ("Devirex, Hazzard") +    __author_mail__ = ("naibaf_11@yahoo.de") + +    def getHoster(self): +        https = "https" if self.getConfig("https") else "http" +        page = getURL(https + "://real-debrid.com/api/hosters.php").replace("\"","").strip() + +        return [x.strip() for x in page.split(",") if x.strip()] diff --git a/pyload/plugins/addons/RehostTo.py b/pyload/plugins/addons/RehostTo.py new file mode 100644 index 000000000..7ca5e5cde --- /dev/null +++ b/pyload/plugins/addons/RehostTo.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class RehostTo(MultiHoster): +    __name__ = "RehostTo" +    __version__ = "0.42" +    __type__ = "hook" + +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", ""), +                  ("unloadFailing", "bool", "Revert to stanard download if download fails", "False"), +                  ("interval", "int", "Reload interval in hours (0 to disable)", "24")] + +    __description__ = """rehost.to hook plugin""" +    __author_name__ = ("RaNaN") +    __author_mail__ = ("RaNaN@pyload.org") + +    def getHoster(self): + +        page = getURL("http://rehost.to/api.php?cmd=get_supported_och_dl&long_ses=%s" % self.long_ses) +        return [x.strip() for x in page.replace("\"", "").split(",")] + + +    def coreReady(self): + +        self.account = self.core.accountManager.getAccountPlugin("RehostTo") + +        user = self.account.selectAccount()[0] + +        if not user: +            self.log.error("Rehost.to: "+ _("Please add your rehost.to account first and restart pyLoad")) +            return + +        data = self.account.getAccountInfo(user) +        self.ses = data["ses"] +        self.long_ses = data["long_ses"] + +        return MultiHoster.coreReady(self)
\ No newline at end of file diff --git a/pyload/plugins/addons/RestartFailed.py b/pyload/plugins/addons/RestartFailed.py new file mode 100644 index 000000000..7ee53deb9 --- /dev/null +++ b/pyload/plugins/addons/RestartFailed.py @@ -0,0 +1,124 @@ +  # -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: Walter Purcaro +""" + +from module.plugins.Hook import Hook +from time import time + + +class RestartFailed(Hook): +    __name__ = "RestartFailed" +    __version__ = "1.5" +    __description__ = "Automatically restart failed/aborted downloads" +    __config__ = [ +        ("activated", "bool", "Activated", "True"), +        ("dlFail", "bool", "Restart when download fail", "True"), +        ("dlFail_n", "int", "Only when failed downloads are at least", "5"), +        ("dlFail_i", "int", "Only when elapsed time since last restart is (min)", "10"), +        ("dlPrcs", "bool", "Restart after all downloads are processed", "True"), +        ("recnt", "bool", "Restart after reconnecting", "True"), +        ("rsLoad", "bool", "Restart on plugin activation", "False") +    ] +    __author_name__ = ("Walter Purcaro") +    __author_mail__ = ("vuolter@gmail.com") + +    def restart(self, arg=None): +        # self.logDebug("self.restart") +        self.info["timerflag"] = False +        self.info["dlfailed"] = 0 +        self.core.api.restartFailed() +        self.logDebug("self.restart: self.core.api.restartFailed") +        self.info["lastrstime"] = time() + +    def periodical(self): +        # self.logDebug("self.periodical") +        if self.info["timerflag"]: +            self.restart() + +    def checkInterval(self, arg=None): +        # self.logDebug("self.checkInterval") +        now = time() +        lastrstime = self.info["lastrstime"] +        interval = self.getConfig("dlFail_i") * 60 +        if now < lastrstime + interval: +            self.info["timerflag"] = True +        else: +            self.restart() + +    def checkFailed(self, pyfile): +        # self.logDebug("self.checkFailed") +        self.info["dlfailed"] += 1 +        curr = self.info["dlfailed"] +        max = self.getConfig("dlFail_n") +        if curr >= max: +            self.checkInterval() + +    def addEvent(self, event, handler): +        if event in self.manager.events: +            if handler not in self.manager.events[event]: +                self.manager.events[event].append(handler) +                # self.logDebug("self.addEvent: " + event + ": added handler") +            else: +                # self.logDebug("self.addEvent: " + event + ": NOT added handler") +                return False +        else: +            self.manager.events[event] = [handler] +            # self.logDebug("self.addEvent: " + event + ": added event and handler") +        return True + +    def removeEvent(self, event, handler): +        if event in self.manager.events and handler in self.manager.events[event]: +            self.manager.events[event].remove(handler) +            # self.logDebug("self.removeEvent: " + event + ": removed handler") +            return True +        else: +            # self.logDebug("self.removeEvent: " + event + ": NOT removed handler") +            return False + +    def configEvents(self, plugin=None, name=None, value=None): +        # self.logDebug("self.configEvents") +        self.interval = self.getConfig("dlFail_i") * 60 +        dlFail = self.getConfig("dlFail") +        dlPrcs = self.getConfig("dlPrcs") +        recnt = self.getConfig("recnt") +        if dlPrcs: +            self.addEvent("allDownloadsProcessed", self.checkInterval) +        else: +            self.removeEvent("allDownloadsProcessed", self.checkInterval) +            if not dlFail: +                self.info["timerflag"] = False +        if recnt: +            self.addEvent("afterReconnecting", self.restart) +        else: +            self.removeEvent("afterReconnecting", self.restart) + +    def unload(self): +        # self.logDebug("self.unload") +        self.removeEvent("pluginConfigChanged", self.configEvents) +        self.removeEvent("downloadFailed", self.checkFailed) +        self.removeEvent("allDownloadsProcessed", self.checkInterval) +        self.removeEvent("afterReconnecting", self.restart) + +    def coreReady(self): +        # self.logDebug("self.coreReady") +        self.info = {"dlfailed": 0, "lastrstime": 0, "timerflag": False} +        if self.getConfig("rsLoad"): +            self.restart() +        self.addEvent("downloadFailed", self.checkFailed) +        self.addEvent("pluginConfigChanged", self.configEvents) +        self.configEvents() diff --git a/pyload/plugins/addons/UnSkipOnFail.py b/pyload/plugins/addons/UnSkipOnFail.py new file mode 100644 index 000000000..4b7a58be8 --- /dev/null +++ b/pyload/plugins/addons/UnSkipOnFail.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: hgg +""" +from os.path import basename + +from module.utils import fs_encode +from module.plugins.Hook import Hook +from module.PyFile import PyFile + +class UnSkipOnFail(Hook): +    __name__ = 'UnSkipOnFail' +    __version__ = '0.01' +    __description__ = 'When a download fails, restart "skipped" duplicates.' +    __config__ = [('activated', 'bool', 'Activated', True),] +    __author_name__ = ('hagg',) +    __author_mail__ = ('') + +    def downloadFailed(self, pyfile): +        pyfile_name = basename(pyfile.name) +        pid = pyfile.package().id +        msg = 'look for skipped duplicates for %s (pid:%s)...' +        self.logInfo(msg % (pyfile_name, pid)) +        dups = self.findDuplicates(pyfile) +        for link in dups: +            # check if link is "skipped"(=4) +            if link.status == 4: +                lpid = link.packageID +                self.logInfo('restart "%s" (pid:%s)...' % (pyfile_name, lpid)) +                self.setLinkStatus(link, "queued") + +    def findDuplicates(self, pyfile): +        """ Search all packages for duplicate links to "pyfile". +            Duplicates are links that would overwrite "pyfile". +            To test on duplicity the package-folder and link-name +            of twolinks are compared (basename(link.name)). +            So this method returns a list of all links with equal +            package-folders and filenames as "pyfile", but except +            the data for "pyfile" iotselöf. +            It does MOT check the link's status. +        """ +        dups = [] +        pyfile_name = fs_encode(basename(pyfile.name)) +        # get packages (w/o files, as most file data is useless here) +        queue = self.core.api.getQueue() +        for package in queue: +            # check if package-folder equals pyfile's package folder +            if fs_encode(package.folder) == fs_encode(pyfile.package().folder): +                # now get packaged data w/ files/links +                pdata = self.core.api.getPackageData(package.pid) +                if pdata.links: +                    for link in pdata.links: +                        link_name = fs_encode(basename(link.name)) +                        # check if link name collides with pdata's name +                        if link_name == pyfile_name: +                            # at last check if it is not pyfile itself +                            if link.fid != pyfile.id: +                                dups.append(link) +        return dups + +    def setLinkStatus(self, link, new_status): +        """ Change status of "link" to "new_status". +            "link" has to be a valid FileData object, +            "new_status" has to be a valid status name +              (i.e. "queued" for this Plugin) +            It creates a temporary PyFile object using +            "link" data, changes its status, and tells +            the core.files-manager to save its data. +        """ +        pyfile = PyFile(self.core.files, +                        link.fid, +                        link.url, +                        link.name, +                        link.size, +                        link.status, +                        link.error, +                        link.plugin, +                        link.packageID, +                        link.order) +        pyfile.setStatus(new_status) +        self.core.files.save() +        pyfile.release() + diff --git a/pyload/plugins/addons/UpdateManager.py b/pyload/plugins/addons/UpdateManager.py new file mode 100644 index 000000000..c800b44bf --- /dev/null +++ b/pyload/plugins/addons/UpdateManager.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. + +    @author: RaNaN +""" + +import sys +import re +from os import stat +from os.path import join, exists +from time import time + +from module.ConfigParser import IGNORE +from module.network.RequestFactory import getURL +from module.plugins.Hook import threaded, Expose, Hook + +class UpdateManager(Hook): +    __name__ = "UpdateManager" +    __version__ = "0.13" +    __description__ = """checks for updates""" +    __config__ = [("activated", "bool", "Activated", "True"), +        ("interval", "int", "Check interval in minutes", "480"), +        ("debug", "bool", "Check for plugin changes when in debug mode", False)] +    __author_name__ = ("RaNaN") +    __author_mail__ = ("ranan@pyload.org") + +    URL = "http://get.pyload.org/check2/%s/" +    MIN_TIME = 3 * 60 * 60 # 3h minimum check interval + +    @property +    def debug(self): +        return self.core.debug and self.getConfig("debug") + +    def setup(self): +        if self.debug: +            self.logDebug("Monitoring file changes") +            self.interval = 4 +            self.last_check = 0 #timestamp of updatecheck +            self.old_periodical = self.periodical +            self.periodical = self.checkChanges +            self.mtimes = {}  #recordes times +        else: +            self.interval = max(self.getConfig("interval") * 60, self.MIN_TIME) + +        self.updated = False +        self.reloaded = True +        self.version = "None" + +        self.info = {"pyload": False, "plugins": False} + +    @threaded +    def periodical(self): + +        updates = self.checkForUpdate() +        if updates: +            self.checkPlugins(updates) + +        if self.updated and not self.reloaded: +            self.info["plugins"] = True +            self.log.info(_("*** Plugins have been updated, please restart pyLoad ***")) +        elif self.updated and self.reloaded: +            self.log.info(_("Plugins updated and reloaded")) +            self.updated = False +        elif self.version == "None": +            self.log.info(_("No plugin updates available")) + +    @Expose +    def recheckForUpdates(self): +        """recheck if updates are available""" +        self.periodical() + +    def checkForUpdate(self): +        """checks if an update is available, return result""" + +        try: +            if self.version == "None": # No updated known +                version_check = getURL(self.URL % self.core.api.getServerVersion()).splitlines() +                self.version = version_check[0] + +                # Still no updates, plugins will be checked +                if self.version == "None": +                    self.log.info(_("No Updates for pyLoad")) +                    return version_check[1:] + + +            self.info["pyload"] = True +            self.log.info(_("***  New pyLoad Version %s available  ***") % self.version) +            self.log.info(_("***  Get it here: http://pyload.org/download  ***")) + +        except: +            self.log.warning(_("Not able to connect server for updates")) + +        return None # Nothing will be done + + +    def checkPlugins(self, updates): +        """ checks for plugins updates""" + +        # plugins were already updated +        if self.info["plugins"]: return + +        reloads = [] + +        vre = re.compile(r'__version__.*=.*("|\')([0-9.]+)') +        url = updates[0] +        schema = updates[1].split("|") +        updates = updates[2:] + +        for plugin in updates: +            info = dict(zip(schema, plugin.split("|"))) +            filename = info["name"] +            prefix = info["type"] +            version = info["version"] + +            if filename.endswith(".pyc"): +                name = filename[:filename.find("_")] +            else: +                name = filename.replace(".py", "") + +            #TODO: obsolete in 0.5.0 +            if prefix.endswith("s"): +                type = prefix[:-1] +            else: +                type = prefix + +            plugins = getattr(self.core.pluginManager, "%sPlugins" % type) + +            if name in plugins: +                if float(plugins[name]["v"]) >= float(version): +                    continue + +            if name in IGNORE or (type, name) in IGNORE: +                continue + +            self.log.info(_("New version of %(type)s|%(name)s : %(version).2f") % { +                "type": type, +                "name": name, +                "version": float(version) +            }) + +            try: +                content = getURL(url % info) +            except Exception, e: +                self.logWarning(_("Error when updating %s") % filename, str(e)) +                continue + +            m = vre.search(content) +            if not m or m.group(2) != version: +                self.logWarning(_("Error when updating %s") % name, _("Version mismatch")) +                continue + +            f = open(join("userplugins", prefix, filename), "wb") +            f.write(content) +            f.close() +            self.updated = True + +            reloads.append((prefix, name)) + +        self.reloaded = self.core.pluginManager.reloadPlugins(reloads) + +    def checkChanges(self): + +        if self.last_check + max(self.getConfig("interval") * 60, self.MIN_TIME) < time(): +            self.old_periodical() +            self.last_check = time() + +        modules = filter( +            lambda m: m and (m.__name__.startswith("module.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 exists(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 + +        self.core.pluginManager.reloadPlugins(reloads) diff --git a/pyload/plugins/addons/XFileSharingPro.py b/pyload/plugins/addons/XFileSharingPro.py new file mode 100644 index 000000000..105c70113 --- /dev/null +++ b/pyload/plugins/addons/XFileSharingPro.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +from module.plugins.Hook import Hook +import re + +class XFileSharingPro(Hook): +    __name__ = "XFileSharingPro" +    __version__ = "0.04" +    __type__ = "hook" +    __config__ = [ ("activated" , "bool" , "Activated"  , "True"), +                   ("loadDefault", "bool", "Include default (built-in) hoster list" , "True"), +                   ("includeList", "str", "Include hosters (comma separated)", ""), +                   ("excludeList", "str", "Exclude hosters (comma separated)", "") ] +    __description__ = """Hoster URL pattern loader for the generic XFileSharingPro plugin""" +    __author_name__ = ("zoidberg") +    __author_mail__ = ("zoidberg@mujmail.cz") +           +    def coreReady(self): +        self.loadPattern() +       +    def loadPattern(self):          +        hosterList = self.getConfigSet('includeList') +        excludeList = self.getConfigSet('excludeList')          +         +        if self.getConfig('loadDefault'):         +            hosterList |= set(( +            #WORKING HOSTERS: +            "aieshare.com", "asixfiles.com", "banashare.com", "cyberlocker.ch", "eyesfile.co", "eyesfile.com", +            "fileband.com", "filedwon.com", "filedownloads.org", "hipfile.com", "kingsupload.com", "mlfat4arab.com",  +            "netuploaded.com", "odsiebie.pl", "q4share.com", "ravishare.com", "uptobox.com", "verzend.be",   +            #NOT TESTED:             +            "bebasupload.com", "boosterking.com", "divxme.com", "filevelocity.com", "glumbouploads.com", "grupload.com", "heftyfile.com",  +            "host4desi.com", "laoupload.com", "linkzhost.com", "movreel.com", "rockdizfile.com", "limfile.com" +            "share76.com", "sharebeast.com", "sharehut.com", "sharerun.com", "shareswift.com", "sharingonline.com", "6ybh-upload.com",  +            "skipfile.com", "spaadyshare.com", "space4file.com", "uploadbaz.com", "uploadc.com",  +            "uploaddot.com", "uploadfloor.com", "uploadic.com", "uploadville.com", "vidbull.com", "zalaa.com",  +            "zomgupload.com", "kupload.org", "movbay.org", "multishare.org", "omegave.org", "toucansharing.org", "uflinq.org", +            "banicrazy.info", "flowhot.info", "upbrasil.info", "shareyourfilez.biz", "bzlink.us", "cloudcache.cc", "fileserver.cc" +            "farshare.to", "filemaze.ws", "filehost.ws", "filestock.ru", "moidisk.ru", "4up.im", "100shared.com", +            #WRONG FILE NAME: +            "sendmyway.com", "upchi.co.il", "180upload.com",  +            #NOT WORKING: +            "amonshare.com", "imageporter.com", "file4safe.com", +            #DOWN OR BROKEN: +            "ddlanime.com", "fileforth.com", "loombo.com", "goldfile.eu", "putshare.com" +            ))              +               +        hosterList -= (excludeList) +        hosterList -= set(('', u'')) +         +        if not hosterList: +            self.unload() +            return +                                             +        regexp = r"http://(?:[^/]*\.)?(%s)/\w{12}" % ("|".join(sorted(hosterList)).replace('.','\.')) +        #self.logDebug(regexp) +         +        dict = self.core.pluginManager.hosterPlugins['XFileSharingPro'] +        dict["pattern"] = regexp +        dict["re"] = re.compile(regexp) +        self.logDebug("Pattern loaded - handling %d hosters" % len(hosterList)) +         +    def getConfigSet(self, option): +        s = self.getConfig(option).lower().replace('|',',').replace(';',',') +        return set([x.strip() for x in s.split(',')]) +         +    def unload(self): +        dict = self.core.pluginManager.hosterPlugins['XFileSharingPro'] +        dict["pattern"] = r"^unmatchable$" +        dict["re"] = re.compile(r"^unmatchable$")
\ No newline at end of file diff --git a/pyload/plugins/addons/XMPPInterface.py b/pyload/plugins/addons/XMPPInterface.py new file mode 100644 index 000000000..e8ef1d2ca --- /dev/null +++ b/pyload/plugins/addons/XMPPInterface.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- + +""" +    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 <http://www.gnu.org/licenses/>. +     +    @author: RaNaN +    @interface-version: 0.2 +""" + +from pyxmpp import streamtls +from pyxmpp.all import JID, Message, Presence +from pyxmpp.jabber.client import JabberClient +from pyxmpp.interface import implements +from pyxmpp.interfaces import * + +from module.plugins.addons.IRCInterface import IRCInterface + +class XMPPInterface(IRCInterface, JabberClient): +    __name__ = "XMPPInterface" +    __version__ = "0.1" +    __description__ = """connect to jabber and let owner perform different tasks""" +    __config__ = [("activated", "bool", "Activated", "False"), +            ("jid", "str", "Jabber ID", "user@exmaple-jabber-server.org"), +            ("pw", "str", "Password", ""), +            ("tls", "bool", "Use TLS", False), +            ("owners", "str", "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"), +            ("info_file", "bool", "Inform about every file finished", "False"), +            ("info_pack", "bool", "Inform about every package finished", "True"), +            ("captcha", "bool", "Send captcha requests", "True")] +    __author_name__ = ("RaNaN") +    __author_mail__ = ("RaNaN@pyload.org") + +    implements(IMessageHandlersProvider) + +    def __init__(self, core, manager): +        IRCInterface.__init__(self, core, manager) + +        self.jid = JID(self.getConfig("jid")) +        password = self.getConfig("pw") + +        # if bare JID is provided add a resource -- it is required +        if not self.jid.resource: +            self.jid = JID(self.jid.node, self.jid.domain, "pyLoad") + +        if self.getConfig("tls"): +            tls_settings = streamtls.TLSSettings(require=True, verify_peer=False) +            auth = ("sasl:PLAIN", "sasl:DIGEST-MD5") +        else: +            tls_settings = None +            auth = ("sasl:DIGEST-MD5", "digest") + +        # setup client with provided connection information +        # and identity data +        JabberClient.__init__(self, self.jid, password, +                              disco_name="pyLoad XMPP Client", disco_type="bot", +                              tls_settings=tls_settings, auth_methods=auth) + +        self.interface_providers = [ +            VersionHandler(self), +            self, +            ] + +    def coreReady(self): +        self.new_package = {} + +        self.start() + +    def packageFinished(self, pypack): +        try: +            if self.getConfig("info_pack"): +                self.announce(_("Package finished: %s") % pypack.name) +        except: +            pass + +    def downloadFinished(self, pyfile): +        try: +            if self.getConfig("info_file"): +                self.announce( +                    _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname}) +        except: +            pass + +    def run(self): +        # connect to IRC etc. +        self.connect() +        try: +            self.loop() +        except Exception, ex: +            self.core.log.error("pyLoad XMPP: %s" % str(ex)) + +    def stream_state_changed(self, state, arg): +        """This one is called when the state of stream connecting the component +        to a server changes. This will usually be used to let the user +        know what is going on.""" +        self.log.debug("pyLoad XMPP: *** State changed: %s %r ***" % (state, arg)) + +    def disconnected(self): +        self.log.debug("pyLoad XMPP: Client was disconnected") + +    def stream_closed(self, stream): +        self.log.debug("pyLoad XMPP: Stream was closed | %s" % stream) + +    def stream_error(self, err): +        self.log.debug("pyLoad XMPP: Stream Error: %s" % err) + +    def get_message_handlers(self): +        """Return list of (message_type, message_handler) tuples. + +        The handlers returned will be called when matching message is received +        in a client session.""" +        return [ +                ("normal", self.message), +                ] + +    def presence_control(self, stanza): +        from_jid = unicode(stanza.get_from_jid()) +        stanza_type = stanza.get_type() +        self.log.debug("pyLoad XMPP: %s stanza from %s" % (stanza_type, +            from_jid)) + +        if from_jid in self.getConfig("owners"): +            return stanza.make_accept_response() + +        return stanza.make_deny_response() + +    def session_started(self): +        self.stream.send(Presence()) + +        self.stream.set_presence_handler("subscribe", self.presence_control) +        self.stream.set_presence_handler("subscribed", self.presence_control) +        self.stream.set_presence_handler("unsubscribe", self.presence_control) +        self.stream.set_presence_handler("unsubscribed", self.presence_control) + +    def message(self, stanza): +        """Message handler for the component.""" +        subject = stanza.get_subject() +        body = stanza.get_body() +        t = stanza.get_type() +        self.log.debug(u'pyLoad XMPP: Message from %s received.' % (unicode(stanza.get_from(), ))) +        self.log.debug(u'pyLoad XMPP: Body: %s Subject: %s Type: %s' % (body, subject, t)) + +        if t == "headline": +            # 'headline' messages should never be replied to +            return True +        if subject: +            subject = u"Re: " + subject + +        to_jid = stanza.get_from() +        from_jid = stanza.get_to() + +        #j = JID() +        to_name = to_jid.as_utf8() +        from_name = from_jid.as_utf8() + +        names = self.getConfig("owners").split(";") + +        if to_name in names or to_jid.node + "@" + to_jid.domain in names: +            messages = [] + +            trigger = "pass" +            args = None + +            try: +                temp = body.split() +                trigger = temp[0] +                if len(temp) > 1: +                    args = temp[1:] +            except: +                pass + +            handler = getattr(self, "event_%s" % trigger, self.event_pass) +            try: +                res = handler(args) +                for line in res: +                    m = Message( +                        to_jid=to_jid, +                        from_jid=from_jid, +                        stanza_type=stanza.get_type(), +                        subject=subject, +                        body=line) + +                    messages.append(m) +            except Exception, e: +                self.log.error("pyLoad XMPP: " + repr(e)) + +            return messages + +        else: +            return True + +    def response(self, msg, origin=""): +        return self.announce(msg) + +    def announce(self, message): +        """ send message to all owners""" +        for user in self.getConfig("owners").split(";"): +            self.log.debug("pyLoad XMPP: Send message to %s" % user) + +            to_jid = JID(user) + +            m = Message(from_jid=self.jid, +                        to_jid=to_jid, +                        stanza_type="chat", +                        body=message) + +            stream = self.get_stream() +            if not stream: +                self.connect() +                stream = self.get_stream() + +            stream.send(m) + +    def beforeReconnecting(self, ip): +        self.disconnect() + +    def afterReconnecting(self, ip): +        self.connect() + + +class VersionHandler(object): +    """Provides handler for a version query. +     +    This class will answer version query and announce 'jabber:iq:version' namespace +    in the client's disco#info results.""" + +    implements(IIqHandlersProvider, IFeaturesProvider) + +    def __init__(self, client): +        """Just remember who created this.""" +        self.client = client + +    def get_features(self): +        """Return namespace which should the client include in its reply to a +        disco#info query.""" +        return ["jabber:iq:version"] + +    def get_iq_get_handlers(self): +        """Return list of tuples (element_name, namespace, handler) describing +        handlers of <iq type='get'/> stanzas""" +        return [ +                ("query", "jabber:iq:version", self.get_version), +                                                                ] + +    def get_iq_set_handlers(self): +        """Return empty list, as this class provides no <iq type='set'/> stanza handler.""" +        return [] + +    def get_version(self, iq): +        """Handler for jabber:iq:version queries. + +        jabber:iq:version queries are not supported directly by PyXMPP, so the +        XML node is accessed directly through the libxml2 API.  This should be +        used very carefully!""" +        iq = iq.make_result_response() +        q = iq.new_query("jabber:iq:version") +        q.newTextChild(q.ns(), "name", "Echo component") +        q.newTextChild(q.ns(), "version", "1.0") +        return iq + +    def unload(self): +        self.log.debug("pyLoad XMPP: unloading") +        self.disconnect() + +    def deactivate(self): +        self.unload() diff --git a/pyload/plugins/addons/ZeveraCom.py b/pyload/plugins/addons/ZeveraCom.py new file mode 100644 index 000000000..cadf60069 --- /dev/null +++ b/pyload/plugins/addons/ZeveraCom.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class ZeveraCom(MultiHoster): +    __name__ = "ZeveraCom" +    __version__ = "0.02" +    __type__ = "hook" +    __config__ = [("activated", "bool", "Activated", "False"), +                  ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"), +                  ("hosterList", "str", "Hoster list (comma separated)", "")] +    __description__ = """Real-Debrid.com hook plugin""" +    __author_name__ = ("zoidberg") +    __author_mail__ = ("zoidberg@mujmail.cz") + +    def getHoster(self): +        page = getURL("http://www.zevera.com/jDownloader.ashx?cmd=gethosters")         +        return [x.strip() for x in page.replace("\"", "").split(",")]                           
\ No newline at end of file diff --git a/pyload/plugins/addons/__init__.py b/pyload/plugins/addons/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/plugins/addons/__init__.py | 
