diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-03-06 13:36:39 +0100 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-03-06 13:36:39 +0100 |
commit | 4df2b77fdf42046fe19bd371be7c7255986b5980 (patch) | |
tree | 2a7227a0d22e03dc2c085514eaab36a7e5e612c4 /module/plugins/addons | |
parent | ssl fix (diff) | |
download | pyload-4df2b77fdf42046fe19bd371be7c7255986b5980.tar.xz |
renamed hooks to addons, new filemanager and database, many new api methods
you will loose ALL your LINKS, webinterface will NOT work
Diffstat (limited to 'module/plugins/addons')
-rw-r--r-- | module/plugins/addons/CaptchaTrader.py | 159 | ||||
-rw-r--r-- | module/plugins/addons/ClickAndLoad.py | 89 | ||||
-rw-r--r-- | module/plugins/addons/EasybytezCom.py | 32 | ||||
-rw-r--r-- | module/plugins/addons/Ev0InFetcher.py | 87 | ||||
-rw-r--r-- | module/plugins/addons/ExternalScripts.py | 118 | ||||
-rw-r--r-- | module/plugins/addons/ExtractArchive.py | 314 | ||||
-rw-r--r-- | module/plugins/addons/HotFolder.py | 85 | ||||
-rw-r--r-- | module/plugins/addons/IRCInterface.py | 425 | ||||
-rw-r--r-- | module/plugins/addons/MergeFiles.py | 94 | ||||
-rw-r--r-- | module/plugins/addons/MultiHome.py | 82 | ||||
-rw-r--r-- | module/plugins/addons/MultiHoster.py | 102 | ||||
-rw-r--r-- | module/plugins/addons/MultishareCz.py | 36 | ||||
-rw-r--r-- | module/plugins/addons/Premium4Me.py | 46 | ||||
-rw-r--r-- | module/plugins/addons/RehostTo.py | 39 | ||||
-rw-r--r-- | module/plugins/addons/UpdateManager.py | 199 | ||||
-rw-r--r-- | module/plugins/addons/XMPPInterface.py | 276 | ||||
-rw-r--r-- | module/plugins/addons/__init__.py | 0 |
17 files changed, 2183 insertions, 0 deletions
diff --git a/module/plugins/addons/CaptchaTrader.py b/module/plugins/addons/CaptchaTrader.py new file mode 100644 index 000000000..b3374ec1d --- /dev/null +++ b/module/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.13" + __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://captchatrader.com/api/submit" + RESPOND_URL = "http://captchatrader.com/api/respond" + GETCREDITS_URL = "http://captchatrader.com/api/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/module/plugins/addons/ClickAndLoad.py b/module/plugins/addons/ClickAndLoad.py new file mode 100644 index 000000000..6d6928557 --- /dev/null +++ b/module/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/module/plugins/addons/EasybytezCom.py b/module/plugins/addons/EasybytezCom.py new file mode 100644 index 000000000..4dd39cca6 --- /dev/null +++ b/module/plugins/addons/EasybytezCom.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster +import re + +def getConfigSet(option): + s = set(option.lower().replace(',','|').split('|')) + s.discard(u'') + return s + +class EasybytezCom(MultiHoster): + __name__ = "EasybytezCom" + __version__ = "0.01" + __type__ = "hook" + __config__ = [("activated", "bool", "Activated", "False"), + ("includeHoster", "str", "Use only for downloads from (comma-separated hosters)", ""), + ("excludeHoster", "str", "Do not use for downloads from (comma-separated hosters)", "")] + __description__ = """EasyBytez.com hook plugin""" + __author_name__ = ("zoidberg") + __author_mail__ = ("zoidberg@mujmail.cz") + + def getHoster(self): + + hoster = set(['2shared.com', 'easy-share.com', 'filefactory.com', 'fileserve.com', 'filesonic.com', 'hotfile.com', 'mediafire.com', 'megaupload.com', 'netload.in', 'rapidshare.com', 'uploading.com', 'wupload.com', 'oron.com', 'uploadstation.com', 'ul.to', 'uploaded.to']) + + option = self.getConfig('includeHoster').strip() + if option: hoster &= getConfigSet(option) + option = self.getConfig('excludeHoster').strip() + if option: hoster -= getConfigSet(option) + + return list(hoster)
\ No newline at end of file diff --git a/module/plugins/addons/Ev0InFetcher.py b/module/plugins/addons/Ev0InFetcher.py new file mode 100644 index 000000000..aeb46320a --- /dev/null +++ b/module/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 seperated)", ""), + ("quality", "xvid;x264;rmvb", "Video Format", "xvid"), + ("hoster", "str", "Hoster to use (comma seperated)", "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/module/plugins/addons/ExternalScripts.py b/module/plugins/addons/ExternalScripts.py new file mode 100644 index 000000000..00fc7c114 --- /dev/null +++ b/module/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.21" + __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, pyfile.id, + 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.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/module/plugins/addons/ExtractArchive.py b/module/plugins/addons/ExtractArchive.py new file mode 100644 index 000000000..5f749ed0d --- /dev/null +++ b/module/plugins/addons/ExtractArchive.py @@ -0,0 +1,314 @@ +#!/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/module/plugins/addons/HotFolder.py b/module/plugins/addons/HotFolder.py new file mode 100644 index 000000000..d05026448 --- /dev/null +++ b/module/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/module/plugins/addons/IRCInterface.py b/module/plugins/addons/IRCInterface.py new file mode 100644 index 000000000..ddaa40613 --- /dev/null +++ b/module/plugins/addons/IRCInterface.py @@ -0,0 +1,425 @@ +# -*- 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 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.getQueue() + + 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.fids) )) + + return lines + + def event_collector(self, args): + ps = self.api.getCollector() + 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.fids) )) + + return lines + + def event_info(self, args): + if not args: + return ['ERROR: Use info like this: info <id>'] + + info = self.api.getFileData(int(args[0])) + + if not info: + return ["ERROR: Link doesn't exists."] + + return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.status_msg, + info.plugin)] + + def event_packinfo(self, args): + if not args: + return ['ERROR: Use packinfo like this: packinfo <id>'] + + lines = [] + pack = self.api.getPackageData(int(args[0])) + + if not pack: + 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]) + if not self.api.getPackage_data(id): + 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): + Exception.__init__(value) + self.value = value + def __str__(self): + return repr(self.value) diff --git a/module/plugins/addons/MergeFiles.py b/module/plugins/addons/MergeFiles.py new file mode 100644 index 000000000..48f997681 --- /dev/null +++ b/module/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/module/plugins/addons/MultiHome.py b/module/plugins/addons/MultiHome.py new file mode 100644 index 000000000..af3f55416 --- /dev/null +++ b/module/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/module/plugins/addons/MultiHoster.py b/module/plugins/addons/MultiHoster.py new file mode 100644 index 000000000..05d25b958 --- /dev/null +++ b/module/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("accountDeleted") + 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("accountUpdated") + 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/module/plugins/addons/MultishareCz.py b/module/plugins/addons/MultishareCz.py new file mode 100644 index 000000000..a934f43ef --- /dev/null +++ b/module/plugins/addons/MultishareCz.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster +import re + +def getConfigSet(option): + s = set(option.lower().split('|')) + s.discard(u'') + return s + +class MultishareCz(MultiHoster): + __name__ = "MultishareCz" + __version__ = "0.01" + __type__ = "hook" + __config__ = [("activated", "bool", "Activated", "False"), + ("includeHoster", "str", "Use only for downloads from (bar-separated hosters)", ""), + ("excludeHoster", "str", "Do not use for downloads from (bar-separated hosters)", "rapidshare.com|uloz.to")] + __description__ = """MultiShare.cz hook plugin""" + __author_name__ = ("zoidberg") + __author_mail__ = ("zoidberg@mujmail.cz") + + #replacements = [("freakshare.net", "freakshare.com")] + HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*alt="([^"]+)"></td>\s*<td class="stav"><img src="/img/loga/ok.png" alt="OK">' + + def getHoster(self): + + page = getURL("http://www.multishare.cz/monitoring/") + hoster = set(m.group(1).lower() for m in re.finditer(self.HOSTER_PATTERN, page)) + + option = self.getConfig('includeHoster').strip() + if option: hoster &= getConfigSet(option) + option = self.getConfig('excludeHoster').strip() + if option: hoster -= getConfigSet(option) + + return list(hoster)
\ No newline at end of file diff --git a/module/plugins/addons/Premium4Me.py b/module/plugins/addons/Premium4Me.py new file mode 100644 index 000000000..fc3ce2343 --- /dev/null +++ b/module/plugins/addons/Premium4Me.py @@ -0,0 +1,46 @@ +# -*- 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")
+
+ replacements = [("freakshare.net", "freakshare.com")]
+
+ def getHoster(self):
+
+ page = getURL("http://premium4.me/api/hosters.php?authcode=%s" % self.account.authcode)
+ hosters = set([x.strip() for x in page.replace("\"", "").split(";")])
+
+ configMode = self.getConfig('hosterListMode')
+ if configMode in ("listed", "unlisted"):
+ configList = set(self.getConfig('hosterList').strip().lower().replace(',','|').split('|'))
+ configList.discard(u'')
+ if configMode == "listed":
+ hosters &= configList
+ elif configMode == "unlisted":
+ hosters -= configList
+
+ return list(hosters)
+
+ 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/module/plugins/addons/RehostTo.py b/module/plugins/addons/RehostTo.py new file mode 100644 index 000000000..b16987f5c --- /dev/null +++ b/module/plugins/addons/RehostTo.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from module.network.RequestFactory import getURL +from module.plugins.internal.MultiHoster import MultiHoster + +class RehostTo(MultiHoster): + __name__ = "RehostTo" + __version__ = "0.41" + __type__ = "hook" + + __config__ = [("activated", "bool", "Activated", "False")] + + __description__ = """rehost.to hook plugin""" + __author_name__ = ("RaNaN") + __author_mail__ = ("RaNaN@pyload.org") + + replacements = [("freakshare.net", "freakshare.com")] + + 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) diff --git a/module/plugins/addons/UpdateManager.py b/module/plugins/addons/UpdateManager.py new file mode 100644 index 000000000..5bc6ac447 --- /dev/null +++ b/module/plugins/addons/UpdateManager.py @@ -0,0 +1,199 @@ +# -*- 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.plugins.PluginManager import IGNORE +from module.network.RequestFactory import getURL +from module.plugins.Addon import threaded, Expose, Addon + +class UpdateManager(Addon): + __name__ = "UpdateManager" + __version__ = "0.12" + __description__ = """checks for updates""" + __config__ = [("activated", "bool", "Activated", "True"), + ("interval", "int", "Check interval in minutes", "360"), + ("debug", "bool", "Check for plugin changes when in debug mode", False)] + __author_name__ = ("RaNaN") + __author_mail__ = ("ranan@pyload.org") + + @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 = self.getConfig("interval") * 60 + + self.updated = False + self.reloaded = True + + self.info = {"pyload": False, "plugins": False} + + @threaded + def periodical(self): + + if self.core.version.endswith("-dev"): + self.logDebug("No update check performed on dev version.") + return + + update = self.checkForUpdate() + if update: + self.info["pyload"] = True + else: + self.log.info(_("No Updates for pyLoad")) + self.checkPlugins() + + 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 + else: + 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""" + + try: + version_check = getURL("http://get.pyload.org/check/%s/" % self.core.api.getServerVersion()) + if version_check == "": + return False + else: + self.log.info(_("*** New pyLoad Version %s available ***") % version_check) + self.log.info(_("*** Get it here: http://pyload.org/download ***")) + return True + except: + self.log.warning(_("Not able to connect server for updates")) + return False + + + def checkPlugins(self): + """ checks for plugins updates""" + + # plugins were already updated + if self.info["plugins"]: return + + try: + updates = getURL("http://get.pyload.org/plugins/check/") + except: + self.log.warning(_("Not able to connect server for updates")) + return False + + updates = updates.splitlines() + reloads = [] + + vre = re.compile(r'__version__.*=.*("|\')([0-9.]+)') + + for plugin in updates: + path, version = plugin.split(":") + prefix, filename = path.split("/") + + if filename.endswith(".pyc"): + name = filename[:filename.find("_")] + else: + name = filename.replace(".py", "") + + #TODO: obsolete + if prefix.endswith("s"): + type = prefix[:-1] + else: + type = prefix + + plugins = self.core.pluginManager.getPlugins(type) + + if name in plugins: + if float(plugins[name].version) >= 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("http://get.pyload.org/plugins/get/" + path) + 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 + self.getConfig("interval") * 60 < 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/module/plugins/addons/XMPPInterface.py b/module/plugins/addons/XMPPInterface.py new file mode 100644 index 000000000..e8ef1d2ca --- /dev/null +++ b/module/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/module/plugins/addons/__init__.py b/module/plugins/addons/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/module/plugins/addons/__init__.py |