summaryrefslogtreecommitdiffstats
path: root/module/plugins/internal/Base.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/plugins/internal/Base.py')
-rw-r--r--module/plugins/internal/Base.py503
1 files changed, 503 insertions, 0 deletions
diff --git a/module/plugins/internal/Base.py b/module/plugins/internal/Base.py
new file mode 100644
index 000000000..bc9ef9158
--- /dev/null
+++ b/module/plugins/internal/Base.py
@@ -0,0 +1,503 @@
+# -*- coding: utf-8 -*-
+
+import inspect
+import mimetypes
+import os
+import time
+import urlparse
+
+from module.plugins.internal.Captcha import Captcha
+from module.plugins.internal.Plugin import (Plugin, Abort, Fail, Reconnect, Retry, Skip,
+ decode, encode, fixurl, parse_html_form,
+ parse_name, replace_patterns)
+
+
+#@TODO: Remove in 0.4.10
+def getInfo(urls):
+ #: result = [ .. (name, size, status, url) .. ]
+ pass
+
+
+#@TODO: Remove in 0.4.10
+def parse_fileInfo(klass, url="", html=""):
+ info = klass.get_info(url, html)
+ return encode(info['name']), info['size'], info['status'], info['url']
+
+
+#@TODO: Remove in 0.4.10
+def create_getInfo(klass):
+ def get_info(urls):
+ for url in urls:
+ try:
+ url = replace_patterns(url, klass.URL_REPLACEMENTS)
+
+ except Exception:
+ pass
+
+ yield parse_fileInfo(klass, url)
+
+ return get_info
+
+
+#@NOTE: `check_abort` decorator
+def check_abort(fn):
+
+ def wrapper(self, *args, **kwargs):
+ self.check_abort()
+ return fn(self, *args, **kwargs)
+
+ return wrapper
+
+
+class Base(Plugin):
+ __name__ = "Base"
+ __type__ = "base"
+ __version__ = "0.02"
+ __status__ = "testing"
+
+ __pattern__ = r'^unmatchable$'
+ __config__ = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description__ = """Base plugin for Hoster and Crypter"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def __init__(self, pyfile):
+ self._init(pyfile.m.core)
+
+ #: Engage wan reconnection
+ self.wantReconnect = False #@TODO: Change to `want_reconnect` in 0.4.10
+
+ #: Enable simultaneous processing of multiple downloads
+ self.multiDL = True #@TODO: Change to `multi_dl` in 0.4.10
+
+ #: time.time() + wait in seconds
+ self.wait_until = 0
+ self.waiting = False
+
+ #: Account handler instance, see :py:class:`Account`
+ self.account = None
+ self.user = None #@TODO: Remove in 0.4.10
+
+ #: Associated pyfile instance, see `PyFile`
+ self.pyfile = pyfile
+
+ self.thread = None #: Holds thread in future
+
+ #: Js engine, see `JsEngine`
+ self.js = self.pyload.js
+
+ #: Captcha stuff
+ self.captcha = Captcha(self)
+
+ #: Some plugins store html code here
+ self.html = None
+
+ #: Dict of the amount of retries already made
+ self.retries = {}
+
+
+ def _log(self, level, plugintype, pluginname, messages):
+ log = getattr(self.pyload.log, level)
+ msg = u" | ".join(decode(a).strip() for a in messages if a)
+ log("%(plugintype)s %(pluginname)s[%(id)s]: %(msg)s"
+ % {'plugintype': plugintype.upper(),
+ 'pluginname': pluginname,
+ 'id' : self.pyfile.id,
+ 'msg' : msg})
+
+
+ @classmethod
+ def get_info(cls, url="", html=""):
+ url = fixurl(url)
+ info = {'name' : parse_name(url),
+ 'size' : 0,
+ 'status': 3 if url else 8,
+ 'url' : url}
+
+ return info
+
+
+ def init(self):
+ """
+ Initialize the plugin (in addition to `__init__`)
+ """
+ pass
+
+
+ def setup(self):
+ """
+ Setup for enviroment and other things, called before downloading (possibly more than one time)
+ """
+ pass
+
+
+ def _setup(self):
+ #@TODO: Remove in 0.4.10
+ self.html = ""
+ self.pyfile.error = ""
+ self.last_html = None
+
+ if self.get_config('use_premium', True):
+ self.load_account() #@TODO: Move to PluginThread in 0.4.10
+ else:
+ self.account = False
+ self.user = None #@TODO: Remove in 0.4.10
+
+ try:
+ self.req.close()
+ except Exception:
+ pass
+
+ if self.account:
+ self.req = self.pyload.requestFactory.getRequest(self.__name__, self.account.user)
+ self.chunk_limit = -1 #: -1 for unlimited
+ self.resume_download = True
+ self.premium = self.account.premium
+ else:
+ self.req = self.pyload.requestFactory.getRequest(self.__name__)
+ self.chunk_limit = 1
+ self.resume_download = False
+ self.premium = False
+
+ self.setup()
+
+
+ def load_account(self):
+ if not self.account:
+ self.account = self.pyload.accountManager.getAccountPlugin(self.__name__)
+
+ if not self.account:
+ self.account = False
+ self.user = None #@TODO: Remove in 0.4.10
+
+ else:
+ self.account.choose()
+ self.user = self.account.user #@TODO: Remove in 0.4.10
+ if self.account.user is None:
+ self.account = False
+
+
+ def _process(self, thread):
+ """
+ Handles important things to do before starting
+ """
+ self.thread = thread
+
+ self._setup()
+
+ # self.pyload.hookManager.downloadPreparing(self.pyfile) #@TODO: Recheck in 0.4.10
+ self.check_abort()
+
+ self.pyfile.setStatus("starting")
+
+ self.log_debug("PROCESS URL " + self.pyfile.url, "PLUGIN VERSION %s" % self.__version__)
+ self.process(self.pyfile)
+
+
+ #: Deprecated method, use `_process` instead (Remove in 0.4.10)
+ def preprocessing(self, *args, **kwargs):
+ return self._process(*args, **kwargs)
+
+
+ def process(self, pyfile):
+ """
+ The "main" method of every hoster plugin, you **have to** overwrite it
+ """
+ raise NotImplementedError
+
+
+ def set_reconnect(self, reconnect):
+ self.log_debug("RECONNECT %s required" % ("" if reconnect else "not"),
+ "Previous wantReconnect: %s" % self.wantReconnect)
+ self.wantReconnect = bool(reconnect)
+
+
+ def set_wait(self, seconds, reconnect=None):
+ """
+ Set a specific wait time later used with `wait`
+
+ :param seconds: wait time in seconds
+ :param reconnect: True if a reconnect would avoid wait time
+ """
+ wait_time = max(int(seconds), 1)
+ wait_until = time.time() + wait_time + 1
+
+ self.log_debug("WAIT set to %d seconds" % wait_time,
+ "Previous waitUntil: %f" % self.pyfile.waitUntil)
+
+ self.pyfile.waitUntil = wait_until
+
+ if reconnect is not None:
+ self.set_reconnect(reconnect)
+
+
+ def wait(self, seconds=None, reconnect=None):
+ """
+ Waits the time previously set
+ """
+ pyfile = self.pyfile
+
+ if seconds is not None:
+ self.set_wait(seconds)
+
+ if reconnect is not None:
+ self.set_reconnect(reconnect)
+
+ self.waiting = True
+
+ status = pyfile.status #@NOTE: Recheck in 0.4.10
+ pyfile.setStatus("waiting")
+
+ self.log_info(_("Waiting %d seconds...") % (pyfile.waitUntil - time.time()))
+
+ if self.wantReconnect:
+ self.log_info(_("Requiring reconnection..."))
+ if self.account:
+ self.log_warning("Ignore reconnection due logged account")
+
+ if not self.wantReconnect or self.account:
+ while pyfile.waitUntil > time.time():
+ self.check_abort()
+ time.sleep(2)
+
+ else:
+ while pyfile.waitUntil > time.time():
+ self.check_abort()
+ self.thread.m.reconnecting.wait(1)
+
+ if self.thread.m.reconnecting.isSet():
+ self.waiting = False
+ self.wantReconnect = False
+ raise Reconnect
+
+ time.sleep(2)
+
+ self.waiting = False
+ pyfile.status = status #@NOTE: Recheck in 0.4.10
+
+
+ def skip(self, msg=""):
+ """
+ Skip and give msg
+ """
+ raise Skip(encode(msg or self.pyfile.error or self.pyfile.pluginname)) #@TODO: Remove `encode` in 0.4.10
+
+
+ #@TODO: Remove in 0.4.10
+ def fail(self, msg):
+ """
+ Fail and give msg
+ """
+ msg = msg.strip()
+
+ if msg:
+ self.pyfile.error = msg
+ else:
+ msg = self.pyfile.error or (self.info['error'] if 'error' in self.info else self.pyfile.getStatusName())
+
+ raise Fail(encode(msg)) #@TODO: Remove `encode` in 0.4.10
+
+
+ def error(self, msg="", type=_("Parse")):
+ type = _("%s error") % type.strip().capitalize() if type else _("Unknown")
+ msg = _("%(type)s: %(msg)s | Plugin may be out of date"
+ % {'type': type, 'msg': msg or self.pyfile.error})
+
+ self.fail(msg)
+
+
+ def abort(self, msg=""):
+ """
+ Abort and give msg
+ """
+ if msg: #@TODO: Remove in 0.4.10
+ self.pyfile.error = encode(msg)
+
+ raise Abort
+
+
+ #@TODO: Recheck in 0.4.10
+ def offline(self, msg=""):
+ """
+ Fail and indicate file is offline
+ """
+ self.fail("offline")
+
+
+ #@TODO: Recheck in 0.4.10
+ def temp_offline(self, msg=""):
+ """
+ Fail and indicates file ist temporary offline, the core may take consequences
+ """
+ self.fail("temp. offline")
+
+
+ def retry(self, attemps=5, wait=1, msg=""):
+ """
+ Retries and begin again from the beginning
+
+ :param attemps: number of maximum retries
+ :param wait: time to wait in seconds before retry
+ :param msg: message passed to fail if attemps value was reached
+ """
+ id = inspect.currentframe().f_back.f_lineno
+ if id not in self.retries:
+ self.retries[id] = 0
+
+ if 0 < attemps <= self.retries[id]:
+ self.fail(msg or _("Max retries reached"))
+
+ self.wait(wait, False)
+
+ self.retries[id] += 1
+ raise Retry(encode(msg)) #@TODO: Remove `encode` in 0.4.10
+
+
+ def retry_captcha(self, attemps=10, wait=1, msg=_("Max captcha retries reached")):
+ self.captcha.invalid()
+ self.retry(attemps, wait, msg)
+
+
+ def fixurl(self, url, baseurl=None, unquote=True):
+ url = fixurl(url)
+
+ if not baseurl:
+ baseurl = fixurl(self.pyfile.url)
+
+ if not urlparse.urlparse(url).scheme:
+ url_p = urlparse.urlparse(baseurl)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+ url = urlparse.urljoin(baseurl, url)
+
+ return fixurl(url, unquote)
+
+
+ @check_abort
+ def load(self, *args, **kwargs):
+ return super(Base, self).load(*args, **kwargs)
+
+
+ def check_abort(self):
+ if not self.pyfile.abort:
+ return
+
+ if self.pyfile.status is 8:
+ self.fail()
+
+ elif self.pyfile.status is 4:
+ self.skip(self.pyfile.statusname)
+
+ elif self.pyfile.status is 1:
+ self.offline()
+
+ elif self.pyfile.status is 6:
+ self.temp_offline()
+
+ else:
+ self.abort()
+
+
+ def direct_link(self, url, follow_location=None):
+ link = ""
+
+ if follow_location is None:
+ redirect = 1
+
+ elif type(follow_location) is int:
+ redirect = max(follow_location, 1)
+
+ else:
+ redirect = self.get_config("maxredirs", 10, "UserAgentSwitcher")
+
+ for i in xrange(redirect):
+ try:
+ self.log_debug("Redirect #%d to: %s" % (i, url))
+ header = self.load(url, just_header=True)
+
+ except Exception: #: Bad bad bad... rewrite this part in 0.4.10
+ res = self.load(url,
+ just_header=True,
+ req=self.pyload.requestFactory.getRequest(self.__name__))
+
+ header = {'code': req.code}
+ for line in res.splitlines():
+ line = line.strip()
+ if not line or ":" not in line:
+ continue
+
+ key, none, value = line.partition(":")
+ key = key.lower().strip()
+ value = value.strip()
+
+ if key in header:
+ if type(header[key]) is list:
+ header[key].append(value)
+ else:
+ header[key] = [header[key], value]
+ else:
+ header[key] = value
+
+ if 'content-disposition' in header:
+ link = url
+
+ elif header.get('location'):
+ location = self.fixurl(header['location'], url)
+
+ if header.get('code') == 302:
+ link = location
+
+ if follow_location:
+ url = location
+ continue
+
+ else:
+ extension = os.path.splitext(parse_name(url))[-1]
+
+ if header.get('content-type'):
+ mimetype = header['content-type'].split(';')[0].strip()
+
+ elif extension:
+ mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream"
+
+ else:
+ mimetype = ""
+
+ if mimetype and (link or 'html' not in mimetype):
+ link = url
+ else:
+ link = ""
+
+ break
+
+ else:
+ try:
+ self.log_error(_("Too many redirects"))
+
+ except Exception:
+ pass
+
+ return link
+
+
+ def parse_html_form(self, attr_str="", input_names={}):
+ return parse_html_form(attr_str, self.html, input_names)
+
+
+ def get_password(self):
+ """
+ Get the password the user provided in the package
+ """
+ return self.pyfile.package().password or ""
+
+
+ def clean(self):
+ """
+ Clean everything and remove references
+ """
+ super(Base, self).clean()
+
+ for attr in ("account", "html", "pyfile", "thread"):
+ if hasattr(self, attr):
+ setattr(self, attr, None)