summaryrefslogtreecommitdiffstats
path: root/module/plugins/addons
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2012-03-06 13:36:39 +0100
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2012-03-06 13:36:39 +0100
commit4df2b77fdf42046fe19bd371be7c7255986b5980 (patch)
tree2a7227a0d22e03dc2c085514eaab36a7e5e612c4 /module/plugins/addons
parentssl fix (diff)
downloadpyload-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.py159
-rw-r--r--module/plugins/addons/ClickAndLoad.py89
-rw-r--r--module/plugins/addons/EasybytezCom.py32
-rw-r--r--module/plugins/addons/Ev0InFetcher.py87
-rw-r--r--module/plugins/addons/ExternalScripts.py118
-rw-r--r--module/plugins/addons/ExtractArchive.py314
-rw-r--r--module/plugins/addons/HotFolder.py85
-rw-r--r--module/plugins/addons/IRCInterface.py425
-rw-r--r--module/plugins/addons/MergeFiles.py94
-rw-r--r--module/plugins/addons/MultiHome.py82
-rw-r--r--module/plugins/addons/MultiHoster.py102
-rw-r--r--module/plugins/addons/MultishareCz.py36
-rw-r--r--module/plugins/addons/Premium4Me.py46
-rw-r--r--module/plugins/addons/RehostTo.py39
-rw-r--r--module/plugins/addons/UpdateManager.py199
-rw-r--r--module/plugins/addons/XMPPInterface.py276
-rw-r--r--module/plugins/addons/__init__.py0
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