diff options
Diffstat (limited to 'pyload')
91 files changed, 1280 insertions, 701 deletions
diff --git a/pyload/Api.py b/pyload/Api.py index 32a077c08..81e39d82d 100644 --- a/pyload/Api.py +++ b/pyload/Api.py @@ -128,8 +128,6 @@ class Api(Iface): # Auth+User Information ############################# - # TODO - @RequirePerm(Permission.All) def login(self, username, password, remoteip=None): """Login into pyLoad, this **must** be called when using rpc before any methods can be used. @@ -153,7 +151,8 @@ class Api(Iface): return self.core.db.checkAuth(username, password) - def isAuthorized(self, func, user): + @staticmethod + def isAuthorized(func, user): """checks if the user is authorized for specific method :param func: function name @@ -167,28 +166,6 @@ class Api(Iface): else: return False - # TODO - @RequirePerm(Permission.All) - def getUserData(self, username, password): - """similar to `checkAuth` but returns UserData thrift type """ - user = self.checkAuth(username, password) - if not user: - raise UserDoesNotExists(username) - - return user.toUserData() - - def getAllUserData(self): - """returns all known user and info""" - return self.core.db.getAllUserData() - - def changePassword(self, username, oldpw, newpw): - """ changes password for specific user """ - return self.core.db.changePassword(username, oldpw, newpw) - - def setUserPermission(self, user, permission, role): - self.core.db.setPermission(user, permission) - self.core.db.setRole(user, role) - class UserApi(Api): """ Proxy object for api that provides all methods in user context """ diff --git a/pyload/Core.py b/pyload/Core.py index 5e083a14e..4f20ae33e 100644 --- a/pyload/Core.py +++ b/pyload/Core.py @@ -277,10 +277,8 @@ class Core(object): self.version = CURRENT_VERSION - # TODO: Re-enable when its working again - # TODO: Don't forget it - if False and not exists("pyload.conf") and not tests: - from Setup import Setup + if not exists("pyload.conf") and not tests: + from setup.Setup import Setup print "This is your first start, running configuration assistant now." self.config = ConfigParser() diff --git a/pyload/api/DownloadPreparingApi.py b/pyload/api/DownloadPreparingApi.py index 131f73b1d..a7e32c4eb 100644 --- a/pyload/api/DownloadPreparingApi.py +++ b/pyload/api/DownloadPreparingApi.py @@ -44,9 +44,9 @@ class DownloadPreparingApi(ApiComponent): #: TODO: withhold crypter, derypt or add later # initial result does not contain the crypter links - tmp = [(url, LinkStatus(url, url, -1, DS.Queued, pluginname)) for url, pluginname in hoster + crypter] + tmp = [(url, LinkStatus(url, url, -1, DS.Queued, pluginname)) for url, pluginname in hoster] data = parseNames(tmp) - rid = self.core.threadManager.createResultThread(self.primaryUID, data) + rid = self.core.threadManager.createResultThread(self.primaryUID, hoster + crypter) return OnlineCheck(rid, data) diff --git a/pyload/api/UserApi.py b/pyload/api/UserApi.py new file mode 100644 index 000000000..d6fbb2646 --- /dev/null +++ b/pyload/api/UserApi.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pyload.Api import Api, RequirePerm, Permission + +from ApiComponent import ApiComponent + +class UserApi(ApiComponent): + """ Api methods to retrieve user profile and manage users. """ + + @RequirePerm(Permission.All) + def getUserData(self): + """ Retrieves :class:`UserData` for the currently logged in user. """ + + @RequirePerm(Permission.All) + def setPassword(self, username, old_password, new_password): + """ Changes password for specific user. User can only change their password. + Admins can change every password! """ + + def getAllUserData(self): + """ Retrieves :class:`UserData` of all exisitng users.""" + + def addUser(self, username, password): + """ Adds an user to the db. + + :param username: desired username + :param password: password for authentication + """ + + def updateUserData(self, data): + """ Change parameters of user account. """ + + def removeUser(self, uid): + """ Removes user from the db. + + :param uid: users uid + """ + + +if Api.extend(UserApi): + del UserApi
\ No newline at end of file diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py index bea46011d..a2b292a27 100644 --- a/pyload/api/__init__.py +++ b/pyload/api/__init__.py @@ -1,5 +1,5 @@ __all__ = ["CoreApi", "ConfigApi", "DownloadApi", "DownloadPreparingApi", "FileApi", - "UserInteractionApi", "AccountApi", "AddonApi"] + "UserInteractionApi", "AccountApi", "AddonApi", "UserApi"] # Import all components # from .import * diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py index db67faa4a..40d8c8071 100644 --- a/pyload/network/Bucket.py +++ b/pyload/network/Bucket.py @@ -1,24 +1,6 @@ #!/usr/bin/env python # -*- 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 -""" - from time import time -from threading import Lock # 10kb minimum rate MIN_RATE = 10240 @@ -28,31 +10,24 @@ class Bucket: self.rate = 0 # bytes per second, maximum targeted throughput self.tokens = 0 self.timestamp = time() - self.lock = Lock() def __nonzero__(self): return False if self.rate < MIN_RATE else True def setRate(self, rate): - self.lock.acquire() self.rate = int(rate) - self.lock.release() def consumed(self, amount): """ return the time the process has to sleep, after it consumed a specified amount """ if self.rate < MIN_RATE: return 0 #May become unresponsive otherwise - self.lock.acquire() self.calc_tokens() self.tokens -= amount if self.tokens < 0: - time = -self.tokens/float(self.rate) + return -self.tokens/float(self.rate) else: - time = 0 - - self.lock.release() - return time + return 0 def calc_tokens(self): if self.tokens < self.rate: diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py index 2a65a9da2..af3d5aba7 100644 --- a/pyload/plugins/Crypter.py +++ b/pyload/plugins/Crypter.py @@ -3,10 +3,9 @@ from pyload.Api import LinkStatus, DownloadStatus as DS from pyload.utils import to_list, has_method, uniqify from pyload.utils.fs import exists, remove, fs_encode -from pyload.utils.packagetools import parseNames - from Base import Base, Retry + class Package: """ Container that indicates that a new package should be created """ @@ -186,15 +185,6 @@ class Crypter(Base): """ raise NotImplementedError - def generatePackages(self, urls): - """Generates :class:`Package` instances and names from urls. Useful for many different links and no\ - given package name. - - :param urls: list of urls - :return: list of `Package` - """ - return [Package(name, purls) for name, purls in parseNames([(url, url) for url in urls]).iteritems()] - def _decrypt(self, urls): """Internal method to select decrypting method @@ -205,16 +195,9 @@ class Crypter(Base): # separate local and remote files content, urls = self.getLocalContent(urls) + result = [] - if has_method(cls, "decryptURLs"): - self.setup() - result = to_list(self.decryptURLs(urls)) - elif has_method(cls, "decryptURL"): - result = [] - for url in urls: - self.setup() - result.extend(to_list(self.decryptURL(url))) - elif has_method(cls, "decrypt"): + if urls and has_method(cls, "decrypt"): self.logDebug("Deprecated .decrypt() method in Crypter plugin") result = [] for url in urls: @@ -222,20 +205,28 @@ class Crypter(Base): self.setup() self.decrypt(self.pyfile) result.extend(self.convertPackages()) - else: - if not has_method(cls, "decryptFile") or urls: - self.logDebug("No suited decrypting method was overwritten in plugin") - result = [] - - if has_method(cls, "decryptFile"): - for f, c in content: + elif urls: + method = True + try: self.setup() - result.extend(to_list(self.decryptFile(c))) - try: - if f.startswith("tmp_"): remove(f) - except IOError: - self.logWarning(_("Could not remove file '%s'") % f) - self.core.print_exc() + result = to_list(self.decryptURLs(urls)) + except NotImplementedError: + method = False + + # this will raise error if not implemented + if not method: + for url in urls: + self.setup() + result.extend(to_list(self.decryptURL(url))) + + for f, c in content: + self.setup() + result.extend(to_list(self.decryptFile(c))) + try: + if f.startswith("tmp_"): remove(f) + except IOError: + self.logWarning(_("Could not remove file '%s'") % f) + self.core.print_exc() return result diff --git a/pyload/plugins/accounts/DdlstorageCom.py b/pyload/plugins/accounts/DdlstorageCom.py index 6c610aa84..7404348a4 100644 --- a/pyload/plugins/accounts/DdlstorageCom.py +++ b/pyload/plugins/accounts/DdlstorageCom.py @@ -1,13 +1,51 @@ # -*- coding: utf-8 -*- +from hashlib import md5 +from time import mktime, strptime + from module.plugins.internal.XFSPAccount import XFSPAccount +from module.common.json_layer import json_loads +from module.utils import parseFileSize + +# DDLStorage API Documentation: +# http://www.ddlstorage.com/cgi-bin/api_req.cgi?req_type=doc class DdlstorageCom(XFSPAccount): __name__ = "DdlstorageCom" - __version__ = "0.01" + __version__ = "1.00" __type__ = "account" __description__ = """DDLStorage.com account plugin""" - __author_name__ = ("zoidberg") - __author_mail__ = ("zoidberg@mujmail.cz") + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") MAIN_PAGE = "http://ddlstorage.com/" + + def loadAccountInfo(self, user, req): + password = self.accounts[user]['password'] + api_data = req.load('http://www.ddlstorage.com/cgi-bin/api_req.cgi', + post={'req_type': 'user_info', + 'client_id': 53472, + 'user_login': user, + 'user_password': md5(password).hexdigest(), + 'sign': md5('user_info%d%s%s%s' % (53472, user, md5(password).hexdigest(), + '25JcpU2dPOKg8E2OEoRqMSRu068r0Cv3')).hexdigest()}) + api_data = api_data.replace('<pre>', '').replace('</pre>', '') + self.logDebug('Account Info API data: ' + api_data) + api_data = json_loads(api_data) + + if api_data['status'] != 'OK': # 'status' must be always OK for a working account + return {"premium": False, "valid": False} + + if api_data['account_type'] == 'REGISTERED': + premium = False + validuntil = None + else: + premium = True + validuntil = int(mktime(strptime(api_data['premium_expire'], "%Y-%m-%d %H:%M:%S"))) + + if api_data['usr_bandwidth_available'] == 'UNLIMITED': + trafficleft = -1 + else: + trafficleft = parseFileSize(api_data['usr_bandwidth_available']) / 1024 + + return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft} diff --git a/pyload/plugins/accounts/FilebeerInfo.py b/pyload/plugins/accounts/FilebeerInfo.py deleted file mode 100644 index 3c3a9edfd..000000000 --- a/pyload/plugins/accounts/FilebeerInfo.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see <http://www.gnu.org/licenses/>. - - @author: zoidberg -""" - -import re -from time import mktime, strptime -from module.plugins.Account import Account - - -class FilebeerInfo(Account): - __name__ = "FilebeerInfo" - __version__ = "0.02" - __type__ = "account" - __description__ = """filebeer.info account plugin""" - __author_name__ = ("zoidberg") - __author_mail__ = ("zoidberg@mujmail.cz") - - VALID_UNTIL_PATTERN = r'Reverts To Free Account:\s</td>\s*<td>\s*(.*?)\s*</td>' - - def loadAccountInfo(self, user, req): - html = req.load("http://filebeer.info/upgrade.php", decode=True) - premium = not 'Free User </td>' in html - - validuntil = None - if premium: - try: - validuntil = mktime(strptime(re.search(self.VALID_UNTIL_PATTERN, html).group(1), "%d/%m/%Y %H:%M:%S")) - except Exception, e: - self.logError("Unable to parse account info", e) - - return {"validuntil": validuntil, "trafficleft": -1, "premium": premium} - - def login(self, user, data, req): - html = req.load('http://filebeer.info/login.php', post={ - "submit": 'Login', - "loginPassword": data['password'], - "loginUsername": user, - "submitme": '1' - }, decode=True) - - if "<ul class='pageErrors'>" in html or ">Your username and password are invalid<" in html: - self.wrongPassword() diff --git a/pyload/plugins/accounts/FilecloudIo.py b/pyload/plugins/accounts/FilecloudIo.py index 5de722ea7..93ae02006 100644 --- a/pyload/plugins/accounts/FilecloudIo.py +++ b/pyload/plugins/accounts/FilecloudIo.py @@ -18,18 +18,41 @@ """ from module.plugins.Account import Account +from module.common.json_layer import json_loads class FilecloudIo(Account): __name__ = "FilecloudIo" - __version__ = "0.01" + __version__ = "0.02" __type__ = "account" __description__ = """FilecloudIo account plugin""" - __author_name__ = ("zoidberg") - __author_mail__ = ("zoidberg@mujmail.cz") + __author_name__ = ("zoidberg", "stickell") + __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it") def loadAccountInfo(self, user, req): - return {"validuntil": -1, "trafficleft": -1, "premium": False} + # It looks like the first API request always fails, so we retry 5 times, it should work on the second try + for _ in range(5): + rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api", + post={"username": user, "password": self.accounts[user]['password']}) + rep = json_loads(rep) + if rep['status'] == 'ok': + break + elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password': + self.logError("Wrong username or password") + return {"valid": False, "premium": False} + else: + return {"premium": False} + + akey = rep['akey'] + self.accounts[user]['akey'] = akey # Saved for hoster plugin + rep = req.load("http://api.filecloud.io/api-fetch_account_details.api", + post={"akey": akey}) + rep = json_loads(rep) + + if rep['is_premium'] == 1: + return {"validuntil": int(rep["premium_until"]), "trafficleft": -1} + else: + return {"premium": False} def login(self, user, data, req): req.cj.setCookie("secure.filecloud.io", "lang", "en") diff --git a/pyload/plugins/accounts/SpeedLoadOrg.py b/pyload/plugins/accounts/SpeedLoadOrg.py deleted file mode 100644 index bb9fb05fb..000000000 --- a/pyload/plugins/accounts/SpeedLoadOrg.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from module.plugins.internal.XFSPAccount import XFSPAccount - - -class SpeedLoadOrg(XFSPAccount): - __name__ = "SpeedLoadOrg" - __version__ = "0.01" - __type__ = "account" - __description__ = """SpeedLoadOrg account plugin""" - __author_name__ = ("stickell") - __author_mail__ = ("l.stickell@yahoo.it") - - MAIN_PAGE = "http://speedload.org/" diff --git a/pyload/plugins/addons/Captcha9kw.py b/pyload/plugins/addons/Captcha9kw.py index e13f93dec..d6fef549f 100755 --- a/pyload/plugins/addons/Captcha9kw.py +++ b/pyload/plugins/addons/Captcha9kw.py @@ -19,11 +19,9 @@ from __future__ import with_statement from thread import start_new_thread from base64 import b64encode -import cStringIO -import pycurl import time -from module.network.RequestFactory import getURL, getRequest +from module.network.RequestFactory import getURL from module.network.HTTPRequest import BadHeader from module.plugins.Hook import Hook @@ -31,7 +29,7 @@ from module.plugins.Hook import Hook class Captcha9kw(Hook): __name__ = "Captcha9kw" - __version__ = "0.07" + __version__ = "0.08" __description__ = """send captchas to 9kw.eu""" __config__ = [("activated", "bool", "Activated", False), ("force", "bool", "Force CT even if client is connected", True), @@ -39,6 +37,8 @@ class Captcha9kw(Hook): ("confirm", "bool", "Confirm Captcha (Cost +6)", "False"), ("captchaperhour", "int", "Captcha per hour (max. 9999)", "9999"), ("prio", "int", "Prio 1-10 (Cost +1-10)", "0"), + ("selfsolve", "bool", + "If enabled and you have a 9kw client active only you will get your captcha to solve it", "False"), ("timeout", "int", "Timeout (max. 300)", "220"), ("passkey", "password", "API key", ""), ] __author_name__ = ("RaNaN") @@ -80,6 +80,7 @@ class Captcha9kw(Hook): "confirm": self.getConfig("confirm"), "captchaperhour": self.getConfig("captchaperhour"), "maxtimeout": self.getConfig("timeout"), + "selfsolve": self.getConfig("selfsolve"), "pyload": "1", "source": "pyload", "base64": "1", diff --git a/pyload/plugins/addons/Checksum.py b/pyload/plugins/addons/Checksum.py index 08fd623b8..081e8ac3b 100644 --- a/pyload/plugins/addons/Checksum.py +++ b/pyload/plugins/addons/Checksum.py @@ -54,7 +54,7 @@ def computeChecksum(local_file, algorithm): class Checksum(Hook): __name__ = "Checksum" - __version__ = "0.08" + __version__ = "0.10" __description__ = "Verify downloaded file size and checksum (enable in general preferences)" __config__ = [("activated", "bool", "Activated", True), ("action", "fail;retry;nothing", "What to do if check fails?", "retry"), diff --git a/pyload/plugins/crypter/FilebeerInfoFolder.py b/pyload/plugins/crypter/FilebeerInfoFolder.py index 86ce5b697..b6bf4fd07 100644 --- a/pyload/plugins/crypter/FilebeerInfoFolder.py +++ b/pyload/plugins/crypter/FilebeerInfoFolder.py @@ -1,36 +1,14 @@ # -*- coding: utf-8 -*- -import re -from module.plugins.Crypter import Crypter +from module.plugins.internal.DeadCrypter import DeadCrypter -class FilebeerInfoFolder(Crypter): +class FilebeerInfoFolder(DeadCrypter): __name__ = "FilebeerInfoFolder" __type__ = "crypter" __pattern__ = r"http://(?:www\.)?filebeer\.info/(\d+~f).*" - __version__ = "0.01" + __version__ = "0.02" __description__ = """Filebeer.info Folder Plugin""" __author_name__ = ("zoidberg") __author_mail__ = ("zoidberg@mujmail.cz") - LINK_PATTERN = r'<td title="[^"]*"><a href="([^"]+)" target="_blank">' - PAGE_COUNT_PATTERN = r'<p class="introText">\s*Total Pages (\d+)' - - def decrypt(self, pyfile): - pyfile.url = re.sub(self.__pattern__, r'http://filebeer.info/\1?page=1', pyfile.url) - html = self.load(pyfile.url) - - page_count = int(re.search(self.PAGE_COUNT_PATTERN, html).group(1)) - new_links = [] - - for i in range(1, page_count + 1): - self.logInfo("Fetching links from page %i" % i) - new_links.extend(re.findall(self.LINK_PATTERN, html)) - - if i < page_count: - html = self.load("%s?page=%d" % (pyfile.url, i + 1)) - - if new_links: - self.core.files.addLinks(new_links, self.pyfile.package().id) - else: - self.fail('Could not extract any links') diff --git a/pyload/plugins/crypter/FiletramCom.py b/pyload/plugins/crypter/FiletramCom.py new file mode 100644 index 000000000..886b8be30 --- /dev/null +++ b/pyload/plugins/crypter/FiletramCom.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU Affero General Public License as # +# published by the Free Software Foundation, either version 3 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +from module.plugins.internal.SimpleCrypter import SimpleCrypter + + +class FiletramCom(SimpleCrypter): + __name__ = "FiletramCom" + __type__ = "crypter" + __pattern__ = r"http://(?:www\.)?filetram.com/[^/]+/.+" + __version__ = "0.01" + __description__ = """Filetram.com Plugin""" + __author_name__ = ("igel", "stickell") + __author_mail__ = ("igelkun@myopera.com", "l.stickell@yahoo.it") + + LINK_PATTERN = r"\s+(http://.+)" + TITLE_PATTERN = r"<title>(?P<title>[^<]+) - Free Download[^<]*</title>" diff --git a/pyload/plugins/crypter/LofCc.py b/pyload/plugins/crypter/LofCc.py index ec9cdaac6..5fee776c7 100644 --- a/pyload/plugins/crypter/LofCc.py +++ b/pyload/plugins/crypter/LofCc.py @@ -5,14 +5,14 @@ import re from os.path import join from module.plugins.Crypter import Crypter -from module.plugins.ReCaptcha import ReCaptcha +from module.plugins.internal.CaptchaService import ReCaptcha class LofCc(Crypter): __name__ = "LofCc" __type__ = "container" __pattern__ = r"http://lof.cc/(.*)" - __version__ = "0.1" + __version__ = "0.2" __description__ = """lof.cc Plugin""" __author_name__ = ("mkaay") __author_mail__ = ("mkaay@mkaay.de") diff --git a/pyload/plugins/crypter/NCryptIn.py b/pyload/plugins/crypter/NCryptIn.py index 6e0c35e92..170a5291d 100644 --- a/pyload/plugins/crypter/NCryptIn.py +++ b/pyload/plugins/crypter/NCryptIn.py @@ -6,17 +6,17 @@ import re from Crypto.Cipher import AES from module.plugins.Crypter import Crypter -from module.plugins.ReCaptcha import ReCaptcha +from module.plugins.internal.CaptchaService import ReCaptcha class NCryptIn(Crypter): __name__ = "NCryptIn" __type__ = "crypter" - __pattern__ = r"http://(?:www\.)?ncrypt.in/folder-([^/\?]+)" - __version__ = "1.23" + __pattern__ = r"http://(?:www\.)?ncrypt.in/(?P<type>folder|link|frame)-([^/\?]+)" + __version__ = "1.25" __description__ = """NCrypt.in Crypter Plugin""" - __author_name__ = ("fragonib") - __author_mail__ = ("fragonib[AT]yahoo[DOT]es") + __author_name__ = ("fragonib", "stickell") + __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "l.stickell@yahoo.it") # Constants _JK_KEY_ = "jk" @@ -33,31 +33,46 @@ class NCryptIn(Crypter): # Init self.package = pyfile.package() - # Request package - self.html = self.load(self.pyfile.url) - self.cleanedHtml = self.removeCrap(self.html) - if not self.isOnline(): - self.offline() - - # Check for protection - if self.isProtected(): - self.html = self.unlockProtection() + self.type = re.search(self.__pattern__, pyfile.url).group('type') + if self.type in ('link', 'frame'): + self.handleSingle() + else: + # Request package + self.html = self.load(self.pyfile.url) self.cleanedHtml = self.removeCrap(self.html) - self.handleErrors() - - # Get package name and folder - (package_name, folder_name) = self.getPackageInfo() - - # Extract package links - package_links = [] - package_links.extend(self.handleWebLinks()) - package_links.extend(self.handleContainers()) - package_links.extend(self.handleCNL2()) - package_links = self.removeContainers(package_links) - package_links = set(package_links) - - # Pack - self.packages = [(package_name, package_links, folder_name)] + if not self.isOnline(): + self.offline() + + # Check for protection + if self.isProtected(): + self.html = self.unlockProtection() + self.cleanedHtml = self.removeCrap(self.html) + self.handleErrors() + + # Get package name and folder + (package_name, folder_name) = self.getPackageInfo() + + # Extract package links + package_links = [] + package_links.extend(self.handleWebLinks()) + package_links.extend(self.handleContainers()) + package_links.extend(self.handleCNL2()) + package_links = self.removeContainers(package_links) + package_links = set(package_links) + + # Pack + self.packages = [(package_name, package_links, folder_name)] + + def handleSingle(self): + if self.type == 'link': + self.pyfile.url = self.pyfile.url.replace('link', 'frame') + header = self.load(self.pyfile.url, just_header=True) + if 'location' not in header: + self.fail("Unable to decrypt link") + loc = header['location'] + self.logDebug("Link decrypted: " + loc) + self.package_links = [loc] + self.packages = [(self.package.name, self.package_links, self.package.folder)] def removeCrap(self, content): patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")', diff --git a/pyload/plugins/crypter/SpeedLoadOrgFolder.py b/pyload/plugins/crypter/SpeedLoadOrgFolder.py index 8223eb7b9..7472e28fe 100644 --- a/pyload/plugins/crypter/SpeedLoadOrgFolder.py +++ b/pyload/plugins/crypter/SpeedLoadOrgFolder.py @@ -15,17 +15,14 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################ -from module.plugins.internal.SimpleCrypter import SimpleCrypter +from module.plugins.internal.DeadCrypter import DeadCrypter -class SpeedLoadOrgFolder(SimpleCrypter): +class SpeedLoadOrgFolder(DeadCrypter): __name__ = "SpeedLoadOrgFolder" __type__ = "crypter" __pattern__ = r"http://(www\.)?speedload\.org/(\d+~f$|folder/\d+/)" - __version__ = "0.2" + __version__ = "0.3" __description__ = """Speedload Crypter Plugin""" __author_name__ = ("stickell") __author_mail__ = ("l.stickell@yahoo.it") - - LINK_PATTERN = r'<div class="link"><a href="(http://speedload.org/\w+)"' - TITLE_PATTERN = r'<title>Files of: (?P<title>[^<]+) folder</title>' diff --git a/pyload/plugins/crypter/TurbobitNetFolder.py b/pyload/plugins/crypter/TurbobitNetFolder.py new file mode 100644 index 000000000..e172f8037 --- /dev/null +++ b/pyload/plugins/crypter/TurbobitNetFolder.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU Affero General Public License as # +# published by the Free Software Foundation, either version 3 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import math +import re + +from module.plugins.internal.SimpleCrypter import SimpleCrypter +from module.common.json_layer import json_loads + + +def format_links(fid): + return 'http://turbobit.net/%s.html' % fid + + +class TurbobitNetFolder(SimpleCrypter): + __name__ = "TurbobitNetFolder" + __type__ = "crypter" + __pattern__ = r"http://(?:w{3}.)?turbobit\.net/download/folder/(?P<id>\w+)" + __version__ = "0.01" + __description__ = """Turbobit.net Folder Plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + TITLE_PATTERN = r"<img src='/js/lib/grid/icon/folder.png'>(?P<title>.+)</div>" + + def getLinks(self): + folder_id = re.search(self.__pattern__, self.pyfile.url).group('id') + grid = self.load('http://turbobit.net/downloadfolder/gridFile', + get={'id_folder': folder_id, 'rows': 200}, decode=True) + grid = json_loads(grid) + + links_count = grid["records"] + pages = int(math.ceil(links_count / 200.0)) + + ids = list() + for i in grid['rows']: + ids.append(i['id']) + + for p in range(2, pages + 1): + grid = self.load('http://turbobit.net/downloadfolder/gridFile', + get={'id_folder': folder_id, 'rows': 200, 'page': p}, decode=True) + grid = json_loads(grid) + for i in grid['rows']: + ids.append(i['id']) + + return map(format_links, ids) diff --git a/pyload/plugins/crypter/XupPl.py b/pyload/plugins/crypter/XupPl.py new file mode 100644 index 000000000..09832f037 --- /dev/null +++ b/pyload/plugins/crypter/XupPl.py @@ -0,0 +1,18 @@ +from module.plugins.Crypter import Crypter + + +class XupPl(Crypter): + __name__ = "XupPl" + __type__ = "crypter" + __pattern__ = r"https?://.*\.xup\.pl/.*" + __version__ = "0.1" + __description__ = """Xup.pl Crypter Plugin""" + __author_name__ = ("z00nx") + __author_mail__ = ("z00nx0@gmail.com") + + def decrypt(self, pyfile): + header = self.load(self.pyfile.url, just_header=True) + if 'location' in header: + self.core.files.addLinks([header['location']], self.pyfile.package().id) + else: + self.fail('Unable to find link') diff --git a/pyload/plugins/hoster/CzshareCom.py b/pyload/plugins/hoster/CzshareCom.py index 8f6f76d84..fdfce6226 100644 --- a/pyload/plugins/hoster/CzshareCom.py +++ b/pyload/plugins/hoster/CzshareCom.py @@ -48,7 +48,7 @@ class CzshareCom(SimpleHoster): USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([0-9., ]+)([kKMG]i?B)</strong>\s*</div><!-- .credit -->' def setup(self): - self.multiDL = self.resumeDownload = True if self.premium else False + self.multiDL = self.resumeDownload = self.premium self.chunkLimit = 1 def checkTrafficLeft(self): diff --git a/pyload/plugins/hoster/DailymotionCom.py b/pyload/plugins/hoster/DailymotionCom.py index ab8ff7910..7d33540f8 100644 --- a/pyload/plugins/hoster/DailymotionCom.py +++ b/pyload/plugins/hoster/DailymotionCom.py @@ -36,7 +36,8 @@ class DailymotionCom(Hoster): for quality in ('hd720URL', 'hqURL', 'sdURL', 'ldURL', ''): dlLink = self.getQuality(quality, allLinksInfo) - if dlLink is not None: break + if dlLink is not None: + break else: self.fail(r'Unable to find video URL') diff --git a/pyload/plugins/hoster/DdlstorageCom.py b/pyload/plugins/hoster/DdlstorageCom.py index 5eaebf1d1..82072aadb 100644 --- a/pyload/plugins/hoster/DdlstorageCom.py +++ b/pyload/plugins/hoster/DdlstorageCom.py @@ -1,13 +1,45 @@ # -*- coding: utf-8 -*- +import re +from hashlib import md5 -from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo +from module.plugins.hoster.XFileSharingPro import XFileSharingPro +from module.network.RequestFactory import getURL +from module.plugins.Plugin import chunks +from module.common.json_layer import json_loads + + +def getInfo(urls): + # DDLStorage API Documentation: + # http://www.ddlstorage.com/cgi-bin/api_req.cgi?req_type=doc + ids = dict() + for url in urls: + m = re.search(DdlstorageCom.__pattern__, url) + ids[m.group('ID')] = url + + for chunk in chunks(ids.keys(), 5): + api = getURL('http://www.ddlstorage.com/cgi-bin/api_req.cgi', + post={'req_type': 'file_info_free', + 'client_id': 53472, + 'file_code': ','.join(chunk), + 'sign': md5('file_info_free%d%s%s' % (53472, ','.join(chunk), + '25JcpU2dPOKg8E2OEoRqMSRu068r0Cv3')).hexdigest()}) + api = api.replace('<pre>', '').replace('</pre>', '') + api = json_loads(api) + + result = list() + for el in api: + if el['status'] == 'online': + result.append((el['file_name'], int(el['file_size']), 2, ids[el['file_code']])) + else: + result.append((ids[el['file_code']], 0, 1, ids[el['file_code']])) + yield result class DdlstorageCom(XFileSharingPro): __name__ = "DdlstorageCom" __type__ = "hoster" - __pattern__ = r"http://(?:\w*\.)*?ddlstorage.com/\w{12}" - __version__ = "0.07" + __pattern__ = r"http://(?:\w*\.)*?ddlstorage.com/(?P<ID>\w{12})" + __version__ = "1.00" __description__ = """DDLStorage.com hoster plugin""" __author_name__ = ("zoidberg", "stickell") __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it") @@ -15,5 +47,38 @@ class DdlstorageCom(XFileSharingPro): FILE_INFO_PATTERN = r'<p class="sub_title"[^>]*>(?P<N>.+) \((?P<S>[^)]+)\)</p>' HOSTER_NAME = "ddlstorage.com" + def prepare(self): + self.getAPIData() + super(DdlstorageCom, self).prepare() + + def getAPIData(self): + file_id = re.search(self.__pattern__, self.pyfile.url).group('ID') + data = {'client_id': 53472, + 'file_code': file_id} + if self.user: + passwd = self.account.getAccountData(self.user)["password"] + data['req_type'] = 'file_info_reg' + data['user_login'] = self.user + data['user_password'] = md5(passwd).hexdigest() + data['sign'] = md5('file_info_reg%d%s%s%s%s' % (data['client_id'], data['user_login'], + data['user_password'], data['file_code'], + '25JcpU2dPOKg8E2OEoRqMSRu068r0Cv3')).hexdigest() + else: + data['req_type'] = 'file_info_free' + data['sign'] = md5('file_info_free%d%s%s' % (data['client_id'], data['file_code'], + '25JcpU2dPOKg8E2OEoRqMSRu068r0Cv3')).hexdigest() + + self.api_data = self.load('http://www.ddlstorage.com/cgi-bin/api_req.cgi', post=data) + self.api_data = self.api_data.replace('<pre>', '').replace('</pre>', '') + self.logDebug('API Data: ' + self.api_data) + self.api_data = json_loads(self.api_data)[0] + + if self.api_data['status'] == 'offline': + self.offline() -getInfo = create_getInfo(DdlstorageCom)
\ No newline at end of file + if 'file_name' in self.api_data: + self.pyfile.name = self.api_data['file_name'] + if 'file_size' in self.api_data: + self.pyfile.size = self.api_data['size'] = self.api_data['file_size'] + if 'file_md5_base64' in self.api_data: + self.api_data['md5_ddlstorage'] = self.api_data['file_md5_base64'] diff --git a/pyload/plugins/hoster/DlFreeFr.py b/pyload/plugins/hoster/DlFreeFr.py index 1f0e38acd..35b9ca6b8 100644 --- a/pyload/plugins/hoster/DlFreeFr.py +++ b/pyload/plugins/hoster/DlFreeFr.py @@ -119,8 +119,8 @@ class DlFreeFr(SimpleHoster): #FILE_URL_PATTERN = r'href="(?P<url>http://.*?)">Télécharger ce fichier' def setup(self): + self.multiDL = self.resumeDownload = True self.limitDL = 5 - self.resumeDownload = True self.chunkLimit = 1 def init(self): diff --git a/pyload/plugins/hoster/FilecloudIo.py b/pyload/plugins/hoster/FilecloudIo.py index 92735d579..c7684a05d 100644 --- a/pyload/plugins/hoster/FilecloudIo.py +++ b/pyload/plugins/hoster/FilecloudIo.py @@ -26,9 +26,9 @@ class FilecloudIo(SimpleHoster): __name__ = "FilecloudIo" __type__ = "hoster" __pattern__ = r"http://(?:\w*\.)*(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+).*" - __version__ = "0.01" + __version__ = "0.02" __description__ = """Filecloud.io (formerly Ifile.it) plugin - free account only""" - __author_name__ = ("zoidberg") + __author_name__ = ("zoidberg", "stickell") FILE_SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}' FILE_NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?) <' @@ -109,5 +109,18 @@ class FilecloudIo(SimpleHoster): else: self.fail("Unexpected server response") + def handlePremium(self): + akey = self.account.getAccountData(self.user)['akey'] + ukey = self.file_info['ID'] + self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey)) + rep = self.load("http://api.filecloud.io/api-fetch_download_url.api", + post={"akey": akey, "ukey": ukey}) + self.logDebug("FetchDownloadUrl: " + rep) + rep = json_loads(rep) + if rep['status'] == 'ok': + self.download(rep['download_url'], disposition=True) + else: + self.fail(rep['message']) + getInfo = create_getInfo(FilecloudIo) diff --git a/pyload/plugins/hoster/FileserveCom.py b/pyload/plugins/hoster/FileserveCom.py index e8e78f9b0..a9ff24d19 100644 --- a/pyload/plugins/hoster/FileserveCom.py +++ b/pyload/plugins/hoster/FileserveCom.py @@ -65,7 +65,7 @@ class FileserveCom(Hoster): # shares code with FilejungleCom and UploadstationCom def setup(self): - self.resumeDownload = self.multiDL = True if self.premium else False + self.resumeDownload = self.multiDL = self.premium self.file_id = re.search(self.__pattern__, self.pyfile.url).group('id') self.url = "%s%s" % (self.URLS[0], self.file_id) diff --git a/pyload/plugins/hoster/FileshareInUa.py b/pyload/plugins/hoster/FileshareInUa.py index d3724f728..11adc4e9c 100644 --- a/pyload/plugins/hoster/FileshareInUa.py +++ b/pyload/plugins/hoster/FileshareInUa.py @@ -19,8 +19,7 @@ class FileshareInUa(Hoster): PATTERN_OFFLINE = "This file doesn't exist, or has been removed." def setup(self): - self.resumeDownload = True - self.multiDL = True + self.resumeDownload = self.multiDL = True def process(self, pyfile): self.pyfile = pyfile diff --git a/pyload/plugins/hoster/FreevideoCz.py b/pyload/plugins/hoster/FreevideoCz.py index c5da074ed..3d8921c38 100644 --- a/pyload/plugins/hoster/FreevideoCz.py +++ b/pyload/plugins/hoster/FreevideoCz.py @@ -47,8 +47,7 @@ class FreevideoCz(Hoster): FILE_OFFLINE_PATTERN = r'<h2 class="red-corner-full">Str.nka nebyla nalezena</h2>' def setup(self): - self.multiDL = True - self.resumeDownload = True + self.multiDL = self.resumeDownload = True def process(self, pyfile): @@ -58,7 +57,8 @@ class FreevideoCz(Hoster): self.offline() found = re.search(self.URL_PATTERN, self.html) - if found is None: self.fail("Parse error (URL)") + if found is None: + self.fail("Parse error (URL)") download_url = found.group(1) pyfile.name = re.search(self.__pattern__, pyfile.url).group(1) + ".mp4" diff --git a/pyload/plugins/hoster/GamefrontCom.py b/pyload/plugins/hoster/GamefrontCom.py index a0ee03f26..c82cfdf50 100644 --- a/pyload/plugins/hoster/GamefrontCom.py +++ b/pyload/plugins/hoster/GamefrontCom.py @@ -8,7 +8,7 @@ class GamefrontCom(Hoster): __name__ = "GamefrontCom" __type__ = "hoster" __pattern__ = r"http://(?:\w*\.)*?gamefront.com/files/[A-Za-z0-9]+" - __version__ = "0.03" + __version__ = "0.04" __description__ = """gamefront.com hoster plugin""" __author_name__ = ("fwannmacher") __author_mail__ = ("felipe@warhammerproject.com") @@ -19,8 +19,8 @@ class GamefrontCom(Hoster): PATTERN_OFFLINE = "This file doesn't exist, or has been removed." def setup(self): - self.resumeDownload = True - self.multiDL = True + self.resumeDownload = self.multiDL = True + self.chunkLimit = -1 def process(self, pyfile): self.pyfile = pyfile diff --git a/pyload/plugins/hoster/GooIm.py b/pyload/plugins/hoster/GooIm.py new file mode 100644 index 000000000..f96e6e6cc --- /dev/null +++ b/pyload/plugins/hoster/GooIm.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +############################################################################ +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU Affero General Public License as # +# published by the Free Software Foundation, either version 3 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import re + +from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo + + +class GooIm(SimpleHoster): + __name__ = "GooIm" + __type__ = "hoster" + __pattern__ = r"http://(?:www\.)?goo\.im/.+" + __version__ = "0.02" + __description__ = """Goo.im hoster plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + FILE_NAME_PATTERN = r'<h3>Filename: (?P<N>.+)</h3>' + FILE_OFFLINE_PATTERN = r'The file you requested was not found' + + def setup(self): + self.chunkLimit = -1 + self.multiDL = self.resumeDownload = True + + def handleFree(self): + self.html = self.load(self.pyfile.url) + m = re.search(r'MD5sum: (?P<MD5>[0-9a-z]{32})</h3>', self.html) + if m: + self.check_data = {"md5": m.group('MD5')} + self.setWait(10) + self.wait() + + header = self.load(self.pyfile.url, just_header=True) + if header['location']: + self.logDebug("Direct link: " + header['location']) + self.download(header['location']) + else: + self.parseError("Unable to detect direct download link") + + +getInfo = create_getInfo(GooIm) diff --git a/pyload/plugins/hoster/HotfileCom.py b/pyload/plugins/hoster/HotfileCom.py index 992899ef5..a7a46e03b 100644 --- a/pyload/plugins/hoster/HotfileCom.py +++ b/pyload/plugins/hoster/HotfileCom.py @@ -49,8 +49,7 @@ class HotfileCom(Hoster): self.url = None if self.premium: - self.multiDL = True - self.resumeDownload = True + self.multiDL = self.resumeDownload = True self.chunkLimit = -1 else: self.multiDL = False diff --git a/pyload/plugins/hoster/IfolderRu.py b/pyload/plugins/hoster/IfolderRu.py index dc1ef8fe2..14e568f8f 100644 --- a/pyload/plugins/hoster/IfolderRu.py +++ b/pyload/plugins/hoster/IfolderRu.py @@ -24,7 +24,7 @@ class IfolderRu(SimpleHoster): __name__ = "IfolderRu" __type__ = "hoster" __pattern__ = r"http://(?:[^.]*\.)?(?:ifolder\.ru|rusfolder\.(?:com|net|ru))/(?:files/)?(?P<ID>\d+).*" - __version__ = "0.37" + __version__ = "0.38" __description__ = """rusfolder.com / ifolder.ru""" __author_name__ = ("zoidberg") __author_mail__ = ("zoidberg@mujmail.cz") @@ -49,7 +49,7 @@ class IfolderRu(SimpleHoster): self.html = self.load("http://rusfolder.com/%s" % file_id, cookies=True, decode=True) self.getFileInfo() - url = re.search('<a href="(http://ints\..*?=)"', self.html).group(1) + url = re.search(r"location\.href = '(http://ints\..*?=)'", self.html).group(1) self.html = self.load(url, cookies=True, decode=True) url, session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups() diff --git a/pyload/plugins/hoster/JumbofilesCom.py b/pyload/plugins/hoster/JumbofilesCom.py index 93885a6a3..1b8a2d73b 100644 --- a/pyload/plugins/hoster/JumbofilesCom.py +++ b/pyload/plugins/hoster/JumbofilesCom.py @@ -17,8 +17,7 @@ class JumbofilesCom(SimpleHoster): DIRECT_LINK_PATTERN = '<meta http-equiv="refresh" content="10;url=(.+)">' def setup(self): - self.resumeDownload = True - self.multiDL = True + self.resumeDownload = self.multiDL = True def handleFree(self): ukey = re.search(self.__pattern__, self.pyfile.url).group(1) diff --git a/pyload/plugins/hoster/Keep2shareCC.py b/pyload/plugins/hoster/Keep2shareCC.py new file mode 100644 index 000000000..5e4f5f540 --- /dev/null +++ b/pyload/plugins/hoster/Keep2shareCC.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 Affero General Public License as # +# published by the Free Software Foundation, either version 3 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU Affero General Public License for more details. # +# # +# You should have received a copy of the GNU Affero General Public License # +# along with this program. If not, see <http://www.gnu.org/licenses/>. # +############################################################################ + +import re + +from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo +from module.plugins.internal.CaptchaService import ReCaptcha + + +class Keep2shareCC(SimpleHoster): + __name__ = "Keep2shareCC" + __type__ = "hoster" + __pattern__ = r"http://(?:www\.)?keep2share\.cc/file/\w+" + __version__ = "0.03" + __description__ = """Keep2share.cc hoster plugin""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + FILE_NAME_PATTERN = r'File: <span>(?P<N>.+)</span>' + FILE_SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>' + FILE_OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted' + + DIRECT_LINK_PATTERN = r'To download this file with slow speed, use <a href="([^"]+)">this link</a>' + WAIT_PATTERN = r'Please wait ([\d:]+) to download this file' + + RECAPTCHA_KEY = '6LcYcN0SAAAAABtMlxKj7X0hRxOY8_2U86kI1vbb' + + def handleFree(self): + fid = re.search(r'<input type="hidden" name="slow_id" value="([^"]+)">', self.html).group(1) + self.html = self.load(self.pyfile.url, post={'yt0': '', 'slow_id': fid}) + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + wait_string = m.group(1) + wait_time = int(wait_string[0:2]) * 3600 + int(wait_string[3:5]) * 60 + int(wait_string[6:8]) + self.setWait(wait_time, True) + self.wait() + self.process(self.pyfile) + + recaptcha = ReCaptcha(self) + for i in xrange(5): + challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY) + post_data = {'recaptcha_challenge_field': challenge, + 'recaptcha_response_field': response, + 'CaptchaForm%5Bcode%5D': '', + 'free': 1, + 'freeDownloadRequest': 1, + 'uniqueId': fid, + 'yt0': ''} + + self.html = self.load(self.pyfile.url, post=post_data) + + if 'recaptcha' not in self.html: + self.correctCaptcha() + self.setWait(30) + self.wait() + break + else: + self.logInfo('Wrong captcha') + self.invalidCaptcha() + else: + self.fail("All captcha attempts failed") + + self.html = self.load(self.pyfile.url, post={'uniqueId': fid, 'free': 1}) + + dl = 'http://keep2share.cc' + m = re.search(self.DIRECT_LINK_PATTERN, self.html) + if not m: + self.parseError("Unable to detect direct link") + dl += m.group(1) + self.logDebug('Direct Link: ' + dl) + self.download(dl, disposition=True) + + +getInfo = create_getInfo(Keep2shareCC) diff --git a/pyload/plugins/hoster/MediafireCom.py b/pyload/plugins/hoster/MediafireCom.py index 1e856c41d..494d0049e 100644 --- a/pyload/plugins/hoster/MediafireCom.py +++ b/pyload/plugins/hoster/MediafireCom.py @@ -36,7 +36,8 @@ def checkHTMLHeader(url): url = line.split(':', 1)[1].strip() if 'error.php?errno=320' in url: return url, 1 - if not url.startswith('http://'): url = 'http://www.mediafire.com' + url + if not url.startswith('http://'): + url = 'http://www.mediafire.com' + url break elif 'content-disposition' in line: return url, 2 @@ -114,7 +115,8 @@ class MediafireCom(SimpleHoster): self.fail("No or incorrect password") found = re.search(r'kNO = "(http://.*?)";', self.html) - if not found: self.parseError("Download URL") + if not found: + self.parseError("Download URL") download_url = found.group(1) self.logDebug("DOWNLOAD LINK:", download_url) diff --git a/pyload/plugins/hoster/MegaNz.py b/pyload/plugins/hoster/MegaNz.py index db97f6859..bf4223213 100644 --- a/pyload/plugins/hoster/MegaNz.py +++ b/pyload/plugins/hoster/MegaNz.py @@ -20,7 +20,7 @@ class MegaNz(Hoster): __name__ = "MegaNz" __type__ = "hoster" __pattern__ = r"https?://([a-z0-9]+\.)?mega\.co\.nz/#!([a-zA-Z0-9!_\-]+)" - __version__ = "0.13" + __version__ = "0.14" __description__ = """mega.co.nz hoster plugin""" __author_name__ = ("RaNaN", ) __author_mail__ = ("ranan@pyload.org", ) @@ -69,8 +69,11 @@ class MegaNz(Hoster): cipher = AES.new(self.getCipherKey(key), AES.MODE_CTR, counter=ctr) self.pyfile.setStatus("decrypting") - f = open(self.lastDownload, "rb") - df = open(self.lastDownload.rsplit(self.FILE_SUFFIX)[0], "wb") + + file_crypted = self.lastDownload + file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0] + f = open(file_crypted, "rb") + df = open(file_decrypted, "wb") # TODO: calculate CBC-MAC for checksum @@ -84,7 +87,9 @@ class MegaNz(Hoster): f.close() df.close() - remove(self.lastDownload) + remove(file_crypted) + + self.lastDownload = file_decrypted def process(self, pyfile): diff --git a/pyload/plugins/hoster/MegasharesCom.py b/pyload/plugins/hoster/MegasharesCom.py index 7d089f717..4e43d4a00 100644 --- a/pyload/plugins/hoster/MegasharesCom.py +++ b/pyload/plugins/hoster/MegasharesCom.py @@ -43,7 +43,7 @@ class MegasharesCom(SimpleHoster): def setup(self): self.resumeDownload = True - self.multiDL = True if self.premium else False + self.multiDL = self.premium def handlePremium(self): self.handleDownload(True) diff --git a/pyload/plugins/hoster/NetloadIn.py b/pyload/plugins/hoster/NetloadIn.py index 0e658761a..773f2e427 100644 --- a/pyload/plugins/hoster/NetloadIn.py +++ b/pyload/plugins/hoster/NetloadIn.py @@ -62,9 +62,8 @@ class NetloadIn(Hoster): def setup(self): self.multiDL = False if self.premium: - self.multiDL = True + self.multiDL = self.resumeDownload = True self.chunkLimit = -1 - self.resumeDownload = True def process(self, pyfile): self.url = pyfile.url diff --git a/pyload/plugins/hoster/NowDownloadEu.py b/pyload/plugins/hoster/NowDownloadEu.py index 4e4c32373..f1cace73b 100644 --- a/pyload/plugins/hoster/NowDownloadEu.py +++ b/pyload/plugins/hoster/NowDownloadEu.py @@ -39,9 +39,8 @@ class NowDownloadEu(SimpleHoster): def setup(self): self.wantReconnect = False - self.multiDL = True + self.multiDL = self.resumeDownload = True self.chunkLimit = -1 - self.resumeDownload = True def handleFree(self): tokenlink = re.search(self.FILE_TOKEN_PATTERN, self.html) diff --git a/pyload/plugins/hoster/OneFichierCom.py b/pyload/plugins/hoster/OneFichierCom.py index 9e2f53fd2..54bf1d1fa 100644 --- a/pyload/plugins/hoster/OneFichierCom.py +++ b/pyload/plugins/hoster/OneFichierCom.py @@ -8,7 +8,7 @@ class OneFichierCom(SimpleHoster): __name__ = "OneFichierCom" __type__ = "hoster" __pattern__ = r"(http://(\w+)\.((1fichier|d(es)?fichiers|pjointe)\.(com|fr|net|org)|(cjoint|mesfichiers|piecejointe|oi)\.(org|net)|tenvoi\.(com|org|net)|dl4free\.com|alterupload\.com|megadl.fr))" - __version__ = "0.47" + __version__ = "0.48" __description__ = """1fichier.com download hoster""" __author_name__ = ("fragonib", "the-razer", "zoidberg", "imclem") __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "daniel_ AT gmx DOT net", "zoidberg@mujmail.cz", "imclem on github") @@ -57,5 +57,8 @@ class OneFichierCom(SimpleHoster): self.wait() self.retry() + def setup(self): + self.multiDL = self.premium + self.resumeDownload = True getInfo = create_getInfo(OneFichierCom) diff --git a/pyload/plugins/hoster/Premium4Me.py b/pyload/plugins/hoster/Premium4Me.py index d5dd30e2a..d98fea4c4 100644 --- a/pyload/plugins/hoster/Premium4Me.py +++ b/pyload/plugins/hoster/Premium4Me.py @@ -61,7 +61,8 @@ class Premium4Me(Hoster): trb = self.getTraffic() self.logInfo("Filesize: %d, Traffic used %d, traffic left %d" % (pyfile.size, tra - trb, trb)) - if err: self.fail(err) + if err: + self.fail(err) def getTraffic(self): try: diff --git a/pyload/plugins/hoster/PutlockerCom.py b/pyload/plugins/hoster/PutlockerCom.py index 0f62ea96d..02205f9cc 100644 --- a/pyload/plugins/hoster/PutlockerCom.py +++ b/pyload/plugins/hoster/PutlockerCom.py @@ -37,9 +37,8 @@ class PutlockerCom(SimpleHoster): FILE_INFO_PATTERN = r'site-content">\s*<h1>(?P<N>.+)<strong>\( (?P<S>[^)]+) \)</strong></h1>' def handleFree(self): - self.multiDL = True + self.multiDL = self.resumeDownload = True self.chunkLimit = -1 - self.resumeDownload = True self.pyfile.url = re.sub(r'http://putlocker\.com', r'http://www.putlocker.com', self.pyfile.url) self.html = self.load(self.pyfile.url, decode=True) diff --git a/pyload/plugins/hoster/RapidgatorNet.py b/pyload/plugins/hoster/RapidgatorNet.py index 543d73966..64ed6a4b3 100644 --- a/pyload/plugins/hoster/RapidgatorNet.py +++ b/pyload/plugins/hoster/RapidgatorNet.py @@ -46,8 +46,7 @@ class RapidgatorNet(SimpleHoster): SOLVEMEDIA_PATTERN = r'http:\/\/api\.solvemedia\.com\/papi\/challenge\.script\?k=(.*?)"' def setup(self): - self.resumeDownload = False - self.multiDL = False + self.resumeDownload = self.multiDL = False self.sid = None self.chunkLimit = 1 self.req.setOption("timeout", 120) diff --git a/pyload/plugins/hoster/RgHostNet.py b/pyload/plugins/hoster/RgHostNet.py new file mode 100644 index 000000000..a46b51733 --- /dev/null +++ b/pyload/plugins/hoster/RgHostNet.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo + + +class RgHostNet(SimpleHoster): + __name__ = "RgHostNet" + __type__ = "hoster" + __pattern__ = r"http://(?:www\.)?rghost\.net/\d+(?:r=\d+)?" + __version__ = "0.01" + __description__ = """RgHost.net Download Hoster""" + __author_name__ = ("z00nx") + __author_mail__ = ("z00nx0@gmail.com") + + FILE_INFO_PATTERN = r'<h1>\s+(<a[^>]+>)?(?P<N>[^<]+)(</a>)?\s+<small[^>]+>\s+\((?P<S>[^)]+)\)\s+</small>\s+</h1>' + FILE_OFFLINE_PATTERN = r'File is deleted|this page is not found' + DOWNLOAD_LINK_PATTERN = '''<a\s+href="([^"]+)"\s+class="btn\s+large\s+download"[^>]+>Download</a>''' + + def handleFree(self): + found = re.search(self.DOWNLOAD_LINK_PATTERN, self.html) + if not found: + self.parseError("Unable to detect the direct link") + download_link = found.group(1) + self.download(download_link, disposition=True) + +getInfo = create_getInfo(RgHostNet) diff --git a/pyload/plugins/hoster/Share76Com.py b/pyload/plugins/hoster/Share76Com.py index aaa8cd950..b48780652 100644 --- a/pyload/plugins/hoster/Share76Com.py +++ b/pyload/plugins/hoster/Share76Com.py @@ -13,9 +13,5 @@ class Share76Com(XFileSharingPro): FILE_INFO_PATTERN = r'<h2>\s*File:\s*<font[^>]*>(?P<N>[^>]+)</font>\s*\[<font[^>]*>(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</font>\]</h2>' HOSTER_NAME = "share76.com" - def setup(self): - self.resumeDownload = self.multiDL = self.premium - self.chunkLimit = 1 - getInfo = create_getInfo(Share76Com) diff --git a/pyload/plugins/hoster/ShareRapidCom.py b/pyload/plugins/hoster/ShareRapidCom.py index 42bdaa4e3..82f98d73c 100644 --- a/pyload/plugins/hoster/ShareRapidCom.py +++ b/pyload/plugins/hoster/ShareRapidCom.py @@ -2,54 +2,32 @@ # -*- coding: utf-8 -*- import re + from pycurl import HTTPHEADER -from module.network.RequestFactory import getRequest, getURL from module.network.HTTPRequest import BadHeader -from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo -from module.common.json_layer import json_loads - - -def checkFile(url): - response = getURL("http://share-rapid.com/checkfiles.php", post={"files": url}, decode=True) - info = json_loads(response) - - if "error" in info: - if info['error'] == False: - info['name'] = info['filename'] - info['status'] = 2 - elif info['msg'] == "Not found": - info['status'] = 1 # offline - elif info['msg'] == "Service Unavailable": - info['status'] = 6 # temp.offline - - return info +from module.network.RequestFactory import getRequest +from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo, replace_patterns def getInfo(urls): + h = getRequest() + h.c.setopt(HTTPHEADER, + ["Accept: text/html", + "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"]) for url in urls: - info = checkFile(url) - if "filename" in info: - yield info['name'], info['size'], info['status'], url - else: - file_info = (url, 0, 3, url) - h = getRequest() - try: - h.c.setopt(HTTPHEADER, ["Accept: text/html"]) - html = h.load(url, cookies=True, decode=True) - file_info = parseFileInfo(ShareRapidCom, url, html) - finally: - h.close() - yield file_info + html = h.load(url, decode=True) + file_info = parseFileInfo(ShareRapidCom, replace_patterns(url, ShareRapidCom.FILE_URL_REPLACEMENTS), html) + yield file_info class ShareRapidCom(SimpleHoster): __name__ = "ShareRapidCom" __type__ = "hoster" - __pattern__ = r"http://(?:www\.)?((share(-?rapid\.(biz|com|cz|info|eu|net|org|pl|sk)|-(central|credit|free|net)\.cz|-ms\.net)|(s-?rapid|rapids)\.(cz|sk))|(e-stahuj|mediatack|premium-rapidshare|rapidshare-premium|qiuck)\.cz|kadzet\.com|stahuj-zdarma\.eu|strelci\.net|universal-share\.com)/stahuj/(\w+)" - __version__ = "0.52" + __pattern__ = r"http://(?:www\.)?((share(-?rapid\.(biz|com|cz|info|eu|net|org|pl|sk)|-(central|credit|free|net)\.cz|-ms\.net)|(s-?rapid|rapids)\.(cz|sk))|(e-stahuj|mediatack|premium-rapidshare|rapidshare-premium|qiuck)\.cz|kadzet\.com|stahuj-zdarma\.eu|strelci\.net|universal-share\.com)/stahuj/(?P<id>\w+)" + __version__ = "0.53" __description__ = """Share-rapid.com plugin - premium only""" - __author_name__ = ("MikyWoW", "zoidberg") - __author_mail__ = ("MikyWoW@seznam.cz", "zoidberg@mujmail.cz") + __author_name__ = ("MikyWoW", "zoidberg", "stickell") + __author_mail__ = ("MikyWoW@seznam.cz", "zoidberg@mujmail.cz", "l.stickell@yahoo.it") FILE_NAME_PATTERN = r'<h1[^>]*><span[^>]*>(?:<a[^>]*>)?(?P<N>[^<]+)' FILE_SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</strong></td>' @@ -59,7 +37,7 @@ class ShareRapidCom(SimpleHoster): ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahovánà je pÅÃstupné pouze pÅihlášenÃœm uÅŸivatelům' ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahovánà zdarma je moÅŸné jen pÅes náš' - FILE_URL_REPLACEMENTS = [(__pattern__, r'http://share-rapid.com/stahuj/\1')] + FILE_URL_REPLACEMENTS = [(__pattern__, r'http://share-rapid.com/stahuj/\g<id>')] def setup(self): self.chunkLimit = 1 @@ -69,35 +47,20 @@ class ShareRapidCom(SimpleHoster): if not self.account: self.fail("User not logged in") - self.info = checkFile(pyfile.url) - self.logDebug(self.info) - - pyfile.status = self.info['status'] - - if pyfile.status == 2: - pyfile.name = self.info['name'] - pyfile.size = self.info['size'] - elif pyfile.status == 1: - self.offline() - elif pyfile.status == 6: - self.tempOffline() - else: - self.fail("Unexpected file status") - - url = "http://share-rapid.com/stahuj/%s" % self.info['filepath'] try: - self.html = self.load(url, decode=True) + self.html = self.load(pyfile.url, decode=True) except BadHeader, e: self.account.relogin(self.user) self.retry(3, 0, str(e)) + self.getFileInfo() + found = re.search(self.DOWNLOAD_URL_PATTERN, self.html) - if found is not None: + if found: link = found.group(1) self.logDebug("Premium link: %s" % link) - self.check_data = {"size": pyfile.size} - self.download(link) + self.download(link, disposition=True) else: if re.search(self.ERR_LOGIN_PATTERN, self.html): self.relogin(self.user) diff --git a/pyload/plugins/hoster/SpeedLoadOrg.py b/pyload/plugins/hoster/SpeedLoadOrg.py index 17354864f..5687fae85 100644 --- a/pyload/plugins/hoster/SpeedLoadOrg.py +++ b/pyload/plugins/hoster/SpeedLoadOrg.py @@ -1,23 +1,15 @@ # -*- coding: utf-8 -*- -from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo +from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo -class SpeedLoadOrg(XFileSharingPro): +class SpeedLoadOrg(DeadHoster): __name__ = "SpeedLoadOrg" __type__ = "hoster" __pattern__ = r"http://(www\.)?speedload\.org/(?P<ID>\w+)" - __version__ = "1.01" + __version__ = "1.02" __description__ = """Speedload.org hoster plugin""" __author_name__ = ("stickell") __author_mail__ = ("l.stickell@yahoo.it") - FILE_NAME_PATTERN = r'Filename:</b></td><td nowrap>(?P<N>[^<]+)</td></tr>' - FILE_SIZE_PATTERN = r'Size:</b></td><td>[\w. ]+<small>\((?P<S>\d+) bytes\)</small>' - - HOSTER_NAME = "speedload.org" - - def handlePremium(self): - self.download(self.pyfile.url, post=self.getPostParameters()) - getInfo = create_getInfo(SpeedLoadOrg) diff --git a/pyload/plugins/hoster/TurbobitNet.py b/pyload/plugins/hoster/TurbobitNet.py index 5fe42bba7..d574d1fa7 100644 --- a/pyload/plugins/hoster/TurbobitNet.py +++ b/pyload/plugins/hoster/TurbobitNet.py @@ -35,8 +35,8 @@ from module.plugins.internal.CaptchaService import ReCaptcha class TurbobitNet(SimpleHoster): __name__ = "TurbobitNet" __type__ = "hoster" - __pattern__ = r"http://(?:\w*\.)?(turbobit.net|unextfiles.com)/(?:download/free/)?(?P<ID>\w+).*" - __version__ = "0.09" + __pattern__ = r"http://(?:\w*\.)?(turbobit.net|unextfiles.com)/(?!download/folder/)(?:download/free/)?(?P<ID>\w+).*" + __version__ = "0.10" __description__ = """Turbobit.net plugin""" __author_name__ = ("zoidberg") __author_mail__ = ("zoidberg@mujmail.cz") diff --git a/pyload/plugins/hoster/UploadedTo.py b/pyload/plugins/hoster/UploadedTo.py index 6ac3320c0..aee7f32b1 100644 --- a/pyload/plugins/hoster/UploadedTo.py +++ b/pyload/plugins/hoster/UploadedTo.py @@ -101,15 +101,13 @@ class UploadedTo(Hoster): def setup(self): self.html = None - self.multiDL = False - self.resumeDownload = False + self.multiDL = self.resumeDownload = False self.url = False self.chunkLimit = 1 # critical problems with more chunks if self.account: self.premium = self.account.getAccountInfo(self.user)["premium"] if self.premium: - self.multiDL = True - self.resumeDownload = True + self.multiDL = self.resumeDownload = True self.fileID = getID(self.pyfile.url) self.pyfile.url = "http://uploaded.net/file/%s" % self.fileID diff --git a/pyload/plugins/hoster/UptoboxCom.py b/pyload/plugins/hoster/UptoboxCom.py index e0d4ce7f4..fe05bf916 100644 --- a/pyload/plugins/hoster/UptoboxCom.py +++ b/pyload/plugins/hoster/UptoboxCom.py @@ -15,9 +15,5 @@ class UptoboxCom(XFileSharingPro): FILE_OFFLINE_PATTERN = r'<center>File Not Found</center>' HOSTER_NAME = "uptobox.com" - def setup(self): - self.resumeDownload = self.multiDL = self.premium - self.chunkLimit = 1 - getInfo = create_getInfo(UptoboxCom) diff --git a/pyload/plugins/hoster/X7To.py b/pyload/plugins/hoster/X7To.py index 24d1643f3..1b8850d9d 100644 --- a/pyload/plugins/hoster/X7To.py +++ b/pyload/plugins/hoster/X7To.py @@ -21,8 +21,7 @@ class X7To(Hoster): def init(self): if self.premium: - self.multiDL = False - self.resumeDownload = False + self.multiDL = self.resumeDownload = False self.chunkLimit = 1 else: self.multiDL = False diff --git a/pyload/plugins/hoster/XFileSharingPro.py b/pyload/plugins/hoster/XFileSharingPro.py index e37afc243..d6fb31307 100644 --- a/pyload/plugins/hoster/XFileSharingPro.py +++ b/pyload/plugins/hoster/XFileSharingPro.py @@ -24,6 +24,7 @@ from pycurl import FOLLOWLOCATION, LOW_SPEED_TIME from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, PluginParseError from module.plugins.internal.CaptchaService import ReCaptcha, SolveMedia from module.utils import html_unescape +from module.network.RequestFactory import getURL class XFileSharingPro(SimpleHoster): @@ -35,7 +36,7 @@ class XFileSharingPro(SimpleHoster): __name__ = "XFileSharingPro" __type__ = "hoster" __pattern__ = r"^unmatchable$" - __version__ = "0.21" + __version__ = "0.23" __description__ = """XFileSharingPro common hoster base""" __author_name__ = ("zoidberg", "stickell") __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it") @@ -73,18 +74,20 @@ class XFileSharingPro(SimpleHoster): else: self.fail("Only premium users can download from other hosters with %s" % self.HOSTER_NAME) else: + try: + # Due to a 0.4.9 core bug self.load would use cookies even if + # cookies=False. Workaround using getURL to avoid cookies. + # Can be reverted in 0.5 as the cookies bug has been fixed. + self.html = getURL(pyfile.url, decode=True) + self.file_info = self.getFileInfo() + except PluginParseError: + self.file_info = None + self.location = self.getDirectDownloadLink() - # self.load will fail because pyfile.url is a direct link to the download if self.location - # is set so it will be executed only if pyfile.url is not a direct link (location not set). - if not self.location: - try: - self.html = self.load(pyfile.url, cookies=False, decode=True) - self.file_info = self.getFileInfo() - except PluginParseError: - self.file_info = None - pyfile.name = html_unescape(unquote(urlparse( - self.location if self.location else pyfile.url).path.split("/")[-1])) + if not self.file_info: + pyfile.name = html_unescape(unquote(urlparse( + self.location if self.location else pyfile.url).path.split("/")[-1])) if self.location: self.startDownload(self.location) diff --git a/pyload/plugins/hoster/YoutubeCom.py b/pyload/plugins/hoster/YoutubeCom.py index 1db5fc0a4..316eebd4b 100644 --- a/pyload/plugins/hoster/YoutubeCom.py +++ b/pyload/plugins/hoster/YoutubeCom.py @@ -4,7 +4,6 @@ import re import subprocess import os -import os.path from urllib import unquote from module.utils import html_unescape @@ -37,7 +36,7 @@ class YoutubeCom(Hoster): __name__ = "YoutubeCom" __type__ = "hoster" __pattern__ = r"https?://(?:[^/]*?)youtube\.com/watch.*?[?&]v=.*" - __version__ = "0.34" + __version__ = "0.35" __config__ = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting", "hd"), ("fmt", "int", "FMT/ITAG Number (5-102, 0 for auto)", 0), (".mp4", "bool", "Allow .mp4", True), @@ -49,6 +48,9 @@ class YoutubeCom(Hoster): __author_name__ = ("spoob", "zoidberg") __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz") + # Invalid characters that must be removed from the file name + invalidChars = ':?><"|\\' + # name, width, height, quality ranking, 3D formats = {5: (".flv", 400, 240, 1, False), 6: (".flv", 640, 400, 4, False), @@ -137,6 +139,11 @@ class YoutubeCom(Hoster): file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv" file_name_pattern = '<meta name="title" content="(.+?)">' name = re.search(file_name_pattern, html).group(1).replace("/", "") + + # Cleaning invalid characters from the file name + for c in self.invalidChars: + name = name.replace(c, '_') + pyfile.name = html_unescape(name) time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url) diff --git a/pyload/plugins/hoster/ZippyshareCom.py b/pyload/plugins/hoster/ZippyshareCom.py index 3c7b68bb6..a3b1cf783 100644 --- a/pyload/plugins/hoster/ZippyshareCom.py +++ b/pyload/plugins/hoster/ZippyshareCom.py @@ -15,7 +15,7 @@ class ZippyshareCom(SimpleHoster): __name__ = "ZippyshareCom" __type__ = "hoster" __pattern__ = r"(?P<HOST>http://www\d{0,2}\.zippyshare.com)/v(?:/|iew.jsp.*key=)(?P<KEY>\d+)" - __version__ = "0.39" + __version__ = "0.41" __description__ = """Zippyshare.com Download Hoster""" __author_name__ = ("spoob", "zoidberg", "stickell") __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it") @@ -26,7 +26,7 @@ class ZippyshareCom(SimpleHoster): FILE_INFO_PATTERN = r'document\.getElementById\(\'dlbutton\'\)\.href = "[^;]*/(?P<N>[^"]+)";' FILE_OFFLINE_PATTERN = r'>File does not exist on this server</div>' - DOWNLOAD_URL_PATTERN = r"<script type=\"text/javascript\">([^<]*?)document\.getElementById\('dlbutton'\).href = ([^;]+);" + DOWNLOAD_URL_PATTERN = r"<script type=\"text/javascript\">([^<]*?)(document\.getElementById\('dlbutton'\).href = [^;]+;)" SEED_PATTERN = r'swfobject.embedSWF\("([^"]+)".*?seed: (\d+)' CAPTCHA_KEY_PATTERN = r'Recaptcha.create\("([^"]+)"' CAPTCHA_SHORTENCODE_PATTERN = r"shortencode: '([^']+)'" @@ -63,52 +63,29 @@ class ZippyshareCom(SimpleHoster): def get_file_url(self): """ returns the absolute downloadable filepath """ - url = multiply = modulo = None + url = None found = re.search(self.DOWNLOAD_URL_PATTERN, self.html, re.S) - if found: - #Method #1: JS eval + #Method #1: JS eval + if found and re.search(r'span id="omg" class="(\d*)"', self.html): + js = "\n".join(found.groups()) + d = re.search(r'span id="omg" class="(\d*)"', self.html).group(1) + regex = r"document.getElementById\('omg'\).getAttribute\('class'\)" + js = re.sub(regex, d, js) + regex = r"document.getElementById\(\\*'dlbutton\\*'\).href = " + js = re.sub(regex, '', js) + url = self.js.eval(js) + elif found and re.search(r"document.getElementById\(\\*'dlbutton\\*'\).omg", self.html): js = "\n".join(found.groups()) regex = r"document.getElementById\(\\*'dlbutton\\*'\).omg" omg = re.search(regex + r" = ([^;]+);", js).group(1) js = re.sub(regex + r" = ([^;]+);", '', js) js = re.sub(regex, omg, js) + js = re.sub(r"document.getElementById\(\\*'dlbutton\\*'\).href = ", '', js) url = self.js.eval(js) else: #Method #2: SWF eval - seed_search = re.search(self.SEED_PATTERN, self.html) - if seed_search: - swf_url, file_seed = seed_search.groups() - - swf_sts = self.getStorage("swf_sts") - swf_stamp = int(self.getStorage("swf_stamp") or 0) - swf_version = self.getStorage("version") - self.logDebug("SWF", swf_sts, swf_stamp, swf_version) - - if not swf_sts: - self.logDebug('Using default values') - multiply, modulo = self.LAST_KNOWN_VALUES - elif swf_sts == "1": - self.logDebug('Using stored values') - multiply = self.getStorage("multiply") - modulo = self.getStorage("modulo") - elif swf_sts == "2": - if swf_version < self.__version__: - self.logDebug('Reverting to default values') - self.setStorage("swf_sts", "") - self.setStorage("version", self.__version__) - multiply, modulo = self.LAST_KNOWN_VALUES - elif (swf_stamp + 3600000) < timestamp(): - swfdump = self.get_swfdump_path() - if swfdump: - multiply, modulo = self.get_swf_values(self.file_info['HOST'] + swf_url, swfdump) - else: - self.logWarning("Swfdump not found. Install swftools to bypass captcha.") - - if multiply and modulo: - self.logDebug("TIME = (%s * %s) %s" % (file_seed, multiply, modulo)) - url = "/download?key=%s&time=%d" % (self.file_info['KEY'], - (int(file_seed) * int(multiply)) % int(modulo)) + url = self.swf_eval() if not url: #Method #3: Captcha @@ -116,6 +93,45 @@ class ZippyshareCom(SimpleHoster): return self.file_info['HOST'] + url + def swf_eval(self): + multiply = modulo = None + seed_search = re.search(self.SEED_PATTERN, self.html) + if seed_search: + swf_url, file_seed = seed_search.groups() + + swf_sts = self.getStorage("swf_sts") + swf_stamp = int(self.getStorage("swf_stamp") or 0) + swf_version = self.getStorage("version") + self.logDebug("SWF", swf_sts, swf_stamp, swf_version) + + if not swf_sts: + self.logDebug('Using default values') + multiply, modulo = self.LAST_KNOWN_VALUES + elif swf_sts == "1": + self.logDebug('Using stored values') + multiply = self.getStorage("multiply") + modulo = self.getStorage("modulo") + elif swf_sts == "2": + if swf_version < self.__version__: + self.logDebug('Reverting to default values') + self.setStorage("swf_sts", "") + self.setStorage("version", self.__version__) + multiply, modulo = self.LAST_KNOWN_VALUES + elif (swf_stamp + 3600000) < timestamp(): + swfdump = self.get_swfdump_path() + if swfdump: + multiply, modulo = self.get_swf_values(self.file_info['HOST'] + swf_url, swfdump) + else: + self.logWarning("Swfdump not found. Install swftools to bypass captcha.") + + if multiply and modulo: + self.logDebug("TIME = (%s * %s) %s" % (file_seed, multiply, modulo)) + url = "/download?key=%s&time=%d" % (self.file_info['KEY'], + (int(file_seed) * int(multiply)) % int(modulo)) + return url + + return None + def get_swf_values(self, swf_url, swfdump): self.logDebug('Parsing values from %s' % swf_url) multiply = modulo = None diff --git a/pyload/plugins/internal/DeadCrypter.py b/pyload/plugins/internal/DeadCrypter.py new file mode 100644 index 000000000..805f781af --- /dev/null +++ b/pyload/plugins/internal/DeadCrypter.py @@ -0,0 +1,14 @@ +from module.plugins.Crypter import Crypter as _Crypter + + +class DeadCrypter(_Crypter): + __name__ = "DeadCrypter" + __type__ = "crypter" + __pattern__ = r"" + __version__ = "0.01" + __description__ = """Crypter is no longer available""" + __author_name__ = ("stickell") + __author_mail__ = ("l.stickell@yahoo.it") + + def setup(self): + self.fail("Crypter is no longer available") diff --git a/pyload/plugins/internal/SimpleCrypter.py b/pyload/plugins/internal/SimpleCrypter.py index f0fe0b764..e26bf6644 100644 --- a/pyload/plugins/internal/SimpleCrypter.py +++ b/pyload/plugins/internal/SimpleCrypter.py @@ -19,9 +19,8 @@ import re -from module.plugins.Crypter import Crypter -from module.utils import html_unescape - +from pyload.plugins.Crypter import Crypter, Package +from pyload.utils import html_unescape class SimpleCrypter(Crypter): __name__ = "SimpleCrypter" @@ -52,11 +51,10 @@ class SimpleCrypter(Crypter): must return the html of the page number 'page_n' """ - def decrypt(self, pyfile): - self.html = self.load(pyfile.url, decode=True) - - package_name, folder_name = self.getPackageNameAndFolder() + def decryptURL(self, url): + self.html = self.load(url, decode=True) + package_name = self.getPackageName() self.package_links = self.getLinks() if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'): @@ -65,10 +63,11 @@ class SimpleCrypter(Crypter): self.logDebug('Package has %d links' % len(self.package_links)) if self.package_links: - self.packages = [(package_name, self.package_links, folder_name)] + return Package(package_name, self.package_links) else: self.fail('Could not extract any links') + def getLinks(self): """ Returns the links extracted from self.html @@ -76,18 +75,15 @@ class SimpleCrypter(Crypter): """ return re.findall(self.LINK_PATTERN, self.html) - def getPackageNameAndFolder(self): + def getPackageName(self): if hasattr(self, 'TITLE_PATTERN'): m = re.search(self.TITLE_PATTERN, self.html) if m: - name = folder = html_unescape(m.group('title').strip()) - self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) - return name, folder - - name = self.pyfile.package().name - folder = self.pyfile.package().folder - self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) - return name, folder + name = html_unescape(m.group('title').strip()) + self.logDebug("Found name [%s] in package info" % (name)) + return name + + return None def handleMultiPages(self): pages = re.search(self.PAGES_PATTERN, self.html) diff --git a/pyload/plugins/internal/SimpleHoster.py b/pyload/plugins/internal/SimpleHoster.py index 7b1d7323a..745cbfd8f 100644 --- a/pyload/plugins/internal/SimpleHoster.py +++ b/pyload/plugins/internal/SimpleHoster.py @@ -146,7 +146,7 @@ class PluginParseError(Exception): class SimpleHoster(Hoster): __name__ = "SimpleHoster" - __version__ = "0.28" + __version__ = "0.29" __pattern__ = None __type__ = "hoster" __description__ = """Base hoster plugin""" @@ -173,13 +173,16 @@ class SimpleHoster(Hoster): self.file_info = {} def setup(self): - self.resumeDownload = self.multiDL = True if self.premium else False - if isinstance(self.SH_COOKIES, list): set_cookies(self.req.cj, self.SH_COOKIES) + self.resumeDownload = self.multiDL = self.premium + if isinstance(self.SH_COOKIES, list): + set_cookies(self.req.cj, self.SH_COOKIES) def process(self, pyfile): pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS) self.req.setOption("timeout", 120) - self.html = self.load(pyfile.url, decode = not self.SH_BROKEN_ENCODING, cookies = self.SH_COOKIES) + # Due to a 0.4.9 core bug self.load would keep previous cookies even if overridden by cookies parameter. + # Workaround using getURL. Can be reverted in 0.5 as the cookies bug has been fixed. + self.html = getURL(pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES) self.getFileInfo() if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()): self.handlePremium() diff --git a/pyload/plugins/network/CurlChunk.py b/pyload/plugins/network/CurlChunk.py index 871cf7f39..75be9ce6c 100644 --- a/pyload/plugins/network/CurlChunk.py +++ b/pyload/plugins/network/CurlChunk.py @@ -25,7 +25,7 @@ import codecs import pycurl from pyload.utils import remove_chars -from pyload.utils.fs import fs_encode +from pyload.utils.fs import fs_encode, fs_decode from CurlRequest import CurlRequest @@ -35,7 +35,7 @@ class WrongFormat(Exception): class ChunkInfo(): def __init__(self, name): - self.name = unicode(name) + self.name = fs_decode(name) self.size = 0 self.resume = False self.chunks = [] @@ -153,6 +153,8 @@ class CurlChunk(CurlRequest): self.sleep = 0.000 self.lastSize = 0 + # next to last size + self.nLastSize = 0 def __repr__(self): return "<CurlChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived) @@ -228,6 +230,8 @@ class CurlChunk(CurlRequest): self.BOMChecked = True size = len(buf) + self.nLastSize = self.lastSize + self.lastSize = size self.arrived += size @@ -235,7 +239,9 @@ class CurlChunk(CurlRequest): if self.p.bucket: sleep(self.p.bucket.consumed(size)) - else: + + # if the buffer sizes are stable no sleep will be made + elif size != self.lastSize or size != self.nLastSize: # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller # otherwise reduce sleep time percentile (values are based on tests) # So in general cpu time is saved without reducing bandwidth too much @@ -245,8 +251,6 @@ class CurlChunk(CurlRequest): else: self.sleep *= 0.7 - self.lastSize = size - sleep(self.sleep) if self.range and self.arrived > self.size: diff --git a/pyload/plugins/network/CurlRequest.py b/pyload/plugins/network/CurlRequest.py index 8d1f22450..717590ac5 100644 --- a/pyload/plugins/network/CurlRequest.py +++ b/pyload/plugins/network/CurlRequest.py @@ -187,7 +187,6 @@ class CurlRequest(Request): if "auth" in self.options: self.c.setopt(pycurl.USERPWD, str(self.options["auth"])) - def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False): """ load and returns a given page """ diff --git a/pyload/remote/json_converter.py b/pyload/remote/json_converter.py index a7a0645ce..b4e57c4a0 100644 --- a/pyload/remote/json_converter.py +++ b/pyload/remote/json_converter.py @@ -59,6 +59,17 @@ def dumps(*args, **kwargs): return json.dumps(*args, **kwargs) +def dump(*args, **kwargs): + if 'compact' in kwargs and kwargs['compact']: + kwargs['cls'] = BaseEncoderCompact + del kwargs['compact'] + else: + kwargs['cls'] = BaseEncoder + + kwargs['separators'] = separators + return json.dump(*args, **kwargs) + + def loads(*args, **kwargs): kwargs['object_hook'] = convert_obj return json.loads(*args, **kwargs)
\ No newline at end of file diff --git a/pyload/remote/pyload.thrift b/pyload/remote/pyload.thrift index 905be22b0..3d0f201e7 100644 --- a/pyload/remote/pyload.thrift +++ b/pyload/remote/pyload.thrift @@ -495,6 +495,9 @@ service Pyload { // returns own user data UserData getUserData(), + // works contextual, admin can change every password + bool setPassword(1: string username, 2: string old_password, 3: string new_password), + // all user, for admins only map<UserID, UserData> getAllUserData(), @@ -504,9 +507,6 @@ service Pyload { void updateUserData(1: UserData data), void removeUser(1: UserID uid), - // works contextual, admin can change every password - bool setPassword(1: string username, 2: string old_password, 3: string new_password), - /////////////////////// // Addon Methods /////////////////////// diff --git a/pyload/setup/Setup.py b/pyload/setup/Setup.py index 78afb7fcc..c61a389e2 100644 --- a/pyload/setup/Setup.py +++ b/pyload/setup/Setup.py @@ -30,17 +30,48 @@ from pyload.utils.fs import abspath, dirname, exists, join, makedirs from pyload.utils import get_console_encoding from pyload.web.ServerThread import WebServer +from system import get_system_info +from dependencies import deps class Setup(): """ pyLoads initial setup configuration assistant """ + @staticmethod + def check_system(): + return get_system_info() + + + @staticmethod + def check_deps(): + result = { + "core": [], + "opt": [] + } + + for d in deps: + avail, v = d.check() + check = { + "name": d.name, + "avail": avail, + "v": v + } + if d.optional: + result["opt"].append(check) + else: + result["core"].append(check) + + return result + + def __init__(self, path, config): self.path = path self.config = config self.stdin_encoding = get_console_encoding(sys.stdin.encoding) self.lang = None + self.db = None + # We will create a timestamp so that the setup will be completed in a specific interval self.timestamp = time() @@ -72,9 +103,13 @@ class Setup(): cli = self.ask("Use commandline for configuration instead?", self.no, bool=True) if cli: - self.start_cli() - else: - raw_input() + print "Not implemented yet!" + print "Use web configuration or config files" + + raw_input() + + return True + def start_cli(self): @@ -93,34 +128,8 @@ class Setup(): print _("When you are ready for system check, hit enter.") raw_input() - #self.get_page_next() - - - if len(avail) < 5: - print _("Features missing: ") - print - - if not self.check_module("Crypto"): - print _("no py-crypto available") - print _("You need this if you want to decrypt container files.") - print "" - - if not ssl: - print _("no SSL available") - print _("This is needed if you want to establish a secure connection to core or webinterface.") - print _("If you only want to access locally to pyLoad ssl is not useful.") - print "" - - if not captcha: - print _("no Captcha Recognition available") - print _("Only needed for some hosters and as freeuser.") - print "" - if not js: - print _("no JavaScript engine found") - print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino") - - print _("You can abort the setup now and fix some dependencies if you want.") + # TODO: new system check + deps con = self.ask(_("Continue with setup?"), self.yes, bool=True) @@ -151,12 +160,11 @@ class Setup(): if ssl: self.conf_ssl() + print "" + print _("Do you want to configure webinterface?") + web = self.ask(_("Configure webinterface?"), self.yes, bool=True) if web: - print "" - print _("Do you want to configure webinterface?") - web = self.ask(_("Configure webinterface?"), self.yes, bool=True) - if web: - self.conf_web() + self.conf_web() print "" print _("Setup finished successfully.") @@ -182,18 +190,11 @@ class Setup(): db.shutdown() print "" - print _("External clients (GUI, CLI or other) need remote access to work over the network.") - print _("However, if you only want to use the webinterface you may disable it to save ram.") - self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.yes, bool=True) - - print "" langs = self.config.getMetaData("general", "language") self.config["general"]["language"] = self.ask(_("Language"), "en", langs.type.split(";")) self.config["general"]["download_folder"] = self.ask(_("Download folder"), "Downloads") self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3") - #print _("You should disable checksum proofing, if you have low hardware requirements.") - #self.config["general"]["checksum"] = self.ask(_("Proof checksum?"), "y", bool=True) reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True) self.config["reconnect"]["activated"] = reconnect @@ -247,12 +248,8 @@ class Setup(): languages=[self.config["general"]["language"], "en"], fallback=True) translation.install(True) - from pyload.database import DatabaseBackend - - db = DatabaseBackend(None) - db.setup() + self.openDB() - noaction = True try: while True: print _("Select action") @@ -267,14 +264,12 @@ class Setup(): print "" username = self.ask(_("Username"), "User") password = self.ask("", "", password=True) - db.addUser(username, password) - noaction = False + self.db.addUser(username, password) elif action == "2": print "" print _("Users") print "-----" - users = db.getAllUserData() - noaction = False + users = self.db.getAllUserData() for user in users.itervalues(): print user.name print "-----" @@ -283,14 +278,35 @@ class Setup(): print "" username = self.ask(_("Username"), "") if username: - db.removeUserByName(username) - noaction = False + self.db.removeUserByName(username) elif action == "4": - db.syncSave() + self.db.syncSave() break finally: - if not noaction: - db.shutdown() + self.closeDB() + + def addUser(self, username, password): + self.openDB() + try: + self.db.addUser(username, password) + finally: + self.closeDB() + + def openDB(self): + from pyload.database import DatabaseBackend + + if self.db is None: + self.db = DatabaseBackend(None) + self.db.setup() + + def closeDB(self): + if self.db is not None: + self.db.syncSave() + self.db.shutdown() + + def save(self): + self.config.save() + self.closeDB() def conf_path(self, trans=False): if trans: diff --git a/pyload/setup/dependencies.py b/pyload/setup/dependencies.py index 53457de93..f7a0e4ae7 100644 --- a/pyload/setup/dependencies.py +++ b/pyload/setup/dependencies.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import inspect + # Provide gettext marker _ = lambda x: x @@ -50,7 +52,7 @@ class Python(Dependency): def getVersion(self): import sys - ".".join(str(v) for v in sys.version_info[:3]) + return ".".join(str(v) for v in sys.version_info[:3]) class JSON(Dependency): @@ -58,8 +60,7 @@ class JSON(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("json") or find_module("simplejson") class PyCurl(Dependency): @@ -67,8 +68,7 @@ class PyCurl(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("pycurl") class Sqlite(Dependency): @@ -76,9 +76,8 @@ class Sqlite(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("sqlite3") or find_module("pysqlite2") # TODO: ssl, crypto, image, tesseract, js -deps = [x for x in locals().itervalues() if issubclass(x, Dependency) and x is not Dependency]
\ No newline at end of file +deps = [Python, Sqlite, PyCurl, JSON]
\ No newline at end of file diff --git a/pyload/setup/system.py b/pyload/setup/system.py index 6e7039331..dab6d1d17 100644 --- a/pyload/setup/system.py +++ b/pyload/setup/system.py @@ -3,6 +3,8 @@ import sys import os +from new_collections import OrderedDict + # gettext decorator, translated only when needed _ = lambda x: x @@ -17,12 +19,12 @@ def get_system_info(): if info is None: import platform - info = { - _("Platform"): platform.platform(), - _("Version"): sys.version, - _("Path"): os.path.abspath(""), - _("Encoding"): sys.getdefaultencoding(), - _("FS-Encoding"): sys.getfilesystemencoding() - } + info = OrderedDict([ + (_("Platform"), platform.platform()), + (_("Version"), sys.version), + (_("Path"), os.path.abspath("")), + (_("Encoding"), sys.getdefaultencoding()), + (_("FS-Encoding"), sys.getfilesystemencoding()) + ]) return info
\ No newline at end of file diff --git a/pyload/threads/DecrypterThread.py b/pyload/threads/DecrypterThread.py index e8b889ac8..22a2d0037 100644 --- a/pyload/threads/DecrypterThread.py +++ b/pyload/threads/DecrypterThread.py @@ -3,6 +3,7 @@ from time import sleep +from pyload.Api import LinkStatus, DownloadStatus as DS from pyload.utils import uniqify, accumulate from pyload.plugins.Base import Abort, Retry from pyload.plugins.Crypter import Package @@ -34,7 +35,7 @@ class DecrypterThread(BaseThread): for p in packages: self.m.core.api.addPackage(p.name, p.getURLs(), pack.password) - def decrypt(self, plugin_map, password=None): + def decrypt(self, plugin_map, password=None, err=None): result = [] # TODO QUEUE_DECRYPT @@ -54,6 +55,11 @@ class DecrypterThread(BaseThread): plugin.logInfo(_("Decrypting aborted")) except Exception, e: plugin.logError(_("Decrypting failed"), e) + + # generate error linkStatus + if err: + plugin_result.extend(LinkStatus(url, url, -1, DS.Failed, name) for url in urls) + if self.core.debug: self.core.print_exc() self.writeDebugReport(plugin.__name__, plugin=plugin) @@ -75,7 +81,7 @@ class DecrypterThread(BaseThread): pack_names[p.name].urls.extend(p.urls) else: if not p.name: - urls.append(p) + urls.extend(p.links) else: pack_names[p.name] = p else: diff --git a/pyload/threads/InfoThread.py b/pyload/threads/InfoThread.py index 8aa5e2d24..f516d2cca 100644 --- a/pyload/threads/InfoThread.py +++ b/pyload/threads/InfoThread.py @@ -37,7 +37,7 @@ class InfoThread(DecrypterThread): if crypter: # decrypt them - links, packages = self.decrypt(crypter) + links, packages = self.decrypt(crypter, err=True) # push these as initial result and save package names self.updateResult(links) for pack in packages: diff --git a/pyload/threads/ThreadManager.py b/pyload/threads/ThreadManager.py index 07b0cd6e9..55cfcbfd2 100644 --- a/pyload/threads/ThreadManager.py +++ b/pyload/threads/ThreadManager.py @@ -94,13 +94,7 @@ class ThreadManager: oc = OnlineCheck(rid, user) self.infoResults[rid] = oc - # maps url to plugin - urls = [] - for links in data.itervalues(): - for link in links: - urls.append((link.url, link.plugin)) - - InfoThread(self, user, urls, oc=oc) + InfoThread(self, user, data, oc=oc) return rid diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py index 1655be857..577213dd1 100644 --- a/pyload/utils/__init__.py +++ b/pyload/utils/__init__.py @@ -193,7 +193,7 @@ def fixup(m): def has_method(obj, name): """ checks if 'name' was defined in obj, (false if it was inhereted) """ - return name in obj.__dict__ + return hasattr(obj, '__dict__') and name in obj.__dict__ def accumulate(it, inv_map=None): """ accumulate (key, value) data to {value : [keylist]} dictionary """ diff --git a/pyload/web/api_app.py b/pyload/web/api_app.py index 9370e671f..39747d5ea 100644 --- a/pyload/web/api_app.py +++ b/pyload/web/api_app.py @@ -13,6 +13,30 @@ from pyload.Api import ExceptionObject from pyload.remote.json_converter import loads, dumps, BaseEncoder from pyload.utils import remove_chars +# used for gzip compression +try: + import gzip + from cStringIO import StringIO +except ImportError: + gzip = None + StringIO = None + +# gzips response if supported +def json_response(obj): + accept = 'gzip' in request.headers.get('Accept-Encoding', '') + result = dumps(obj) + # don't compress small string + if gzip and accept and len(result) > 500: + response.headers['Vary'] = 'Accept-Encoding' + response.headers['Content-Encoding'] = 'gzip' + zbuf = StringIO() + zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf) + zfile.write(result) + zfile.close() + return zbuf.getvalue() + + return result + # returns http error def error(code, msg): @@ -78,7 +102,7 @@ def call_api(func, args=""): result = getattr(api, func)(*args, **kwargs) # null is invalid json response if result is None: result = True - return dumps(result) + return json_response(result) except ExceptionObject, e: return error(400, e.message) @@ -98,7 +122,7 @@ def login(): user = PYLOAD.checkAuth(username, password, request.environ.get('REMOTE_ADDR', None)) if not user: - return dumps(False) + return json_response(False) s = set_session(request, user) @@ -116,7 +140,7 @@ def login(): if request.params.get('user', None): return dumps(result) - return dumps(sid) + return json_response(sid) @route("/api/logout") @@ -127,4 +151,4 @@ def logout(): s = request.environ.get('beaker.session') s.delete() - return dumps(True) + return json_response(True) diff --git a/pyload/web/app/index.html b/pyload/web/app/index.html index bf75d40ed..98e1bf233 100644 --- a/pyload/web/app/index.html +++ b/pyload/web/app/index.html @@ -80,29 +80,52 @@ <a href="http://pyload.org/" target="_blank">The pyLoad Team</a><br> </div> </div> - <div class="span2"> - <h2 class="block-title">Powered by</h2> + <div class="span2 block"> + <h2 class="block-title"> + <a href="http://pyload.org" target="_blank"> + Community <i class="icon-comment"></i> + </a> + </h2> <hr> - Bootstrap <br> + <a href="http://pyload.org" target="_blank">Homepage</a> · + <a href="http://board.pyload.org" target="_blank">Board</a> · + <a href="http://pyload.org/chat" target="_blank">Chat</a> </div> - <div class="span2"> - <h2 class="block-title">pyLoad</h2> + <div class="span2 block"> + <h2 class="block-title"> + <a href="https://twitter.com/pyload" target="_blank"> + Follow us <i class="icon-twitter"></i> + </a> + </h2> <hr> - dsfdsf <br> + <a href="https://twitter.com/pyload" target="_blank">Twitter</a> · + <a href="http://www.youtube.com/user/pyloadTeam" target="_blank">Youtube</a> </div> - <div class="span2"> - <h2 class="block-title">Community</h2> + <div class="span2 block"> + <h2 class="block-title"> + <a href="https://github.com/pyload" target="_blank"> + Development <i class="icon-github"></i> + </a> + </h2> <hr> - asd <br> + <a href="https://github.com/pyload" target="_blank">Github</a> · + <a href="http://docs.pyload.org" target="_blank">Documentation</a> </div> - <div class="span2"> - <h2 class="block-title">Development</h2> + <div class="span2 block"> + <h2 class="block-title"> + <a href="http://pyload.org/donate" target="_blank"> + Donate <i class="icon-bitcoin"> </i> + </a> + </h2> <hr> - asd <br> + <a href="http://pyload.org/donate" target="_blank">PayPal</a> · + <a href="http://blockchain.info/address/1JvcfSKuzk3VENJm9XtqGp2DCTesgokkG2" target="_blank">Bitcoin</a> · + <a href="https://flattr.com/profile/pyload" target="_blank">Flattr</a> </div> + </div> </div> </footer> diff --git a/pyload/web/app/scripts/models/Setup.js b/pyload/web/app/scripts/models/Setup.js index 82a2978db..424edf452 100644 --- a/pyload/web/app/scripts/models/Setup.js +++ b/pyload/web/app/scripts/models/Setup.js @@ -4,10 +4,30 @@ define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], return Backbone.Model.extend({ + url: App.apiUrl('setup'), defaults: { lang: 'en', + system: null, + deps: null, user: null, password: null + }, + + fetch: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup'); + return Backbone.Model.prototype.fetch.call(this, options); + }, + + // will get a 409 on success + submit: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup_done'); + options.data = { + user: this.get('user'), + password: this.get('password') + }; + return Backbone.Model.prototype.fetch.call(this, options); } }); diff --git a/pyload/web/app/scripts/views/linkgrabber/packageView.js b/pyload/web/app/scripts/views/linkgrabber/packageView.js index 95c46e3cc..356d39b4b 100644 --- a/pyload/web/app/scripts/views/linkgrabber/packageView.js +++ b/pyload/web/app/scripts/views/linkgrabber/packageView.js @@ -39,9 +39,19 @@ define(['jquery', 'underscore', 'backbone', 'app', 'hbs!tpl/linkgrabber/package' return false; }, - renamePackage: function() { + renamePackage: function(e) { + e.stopPropagation(); + this.ui.name.addClass('edit'); this.ui.name.find('input').focus(); + + var self = this; + $(document).one('click', function() { + self.ui.name.removeClass('edit'); + self.ui.name.focus(); + }); + + return false; }, saveName: function(e) { diff --git a/pyload/web/app/scripts/views/setup/finishedView.js b/pyload/web/app/scripts/views/setup/finishedView.js new file mode 100644 index 000000000..9f0f8db19 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/finishedView.js @@ -0,0 +1,25 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/finished'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'Finished', + template: template, + + events: { + 'click .btn-blue': 'confirm' + }, + + ui: { + }, + + onRender: function() { + }, + + confirm: function() { + this.model.submit(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/setupView.js b/pyload/web/app/scripts/views/setup/setupView.js index 7636a0bc2..8ab6fba51 100644 --- a/pyload/web/app/scripts/views/setup/setupView.js +++ b/pyload/web/app/scripts/views/setup/setupView.js @@ -1,6 +1,6 @@ -define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setup/layout', 'hbs!tpl/setup/actionbar', - './welcomeView', './systemView'], - function($, Backbone, _, App, Setup, template, templateBar, welcomeView, systemView) { +define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setup/layout', 'hbs!tpl/setup/actionbar', 'hbs!tpl/setup/error', + './welcomeView', './systemView', './userView', './finishedView'], + function($, Backbone, _, App, Setup, template, templateBar, templateError, welcomeView, systemView, userView, finishedView) { 'use strict'; return Backbone.Marionette.ItemView.extend({ @@ -15,11 +15,14 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setu pages: [ welcomeView, - systemView + systemView, + userView, + finishedView ], page: 0, view: null, + error: null, initialize: function() { var self = this; @@ -52,37 +55,66 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setu }); this.listenTo(this.model, 'page:next', function() { - self.openPage(self.page++); + self.openPage(self.page + 1); }); this.listenTo(this.model, 'page:prev', function() { - self.openPage(self.page--); + self.openPage(self.page - 1); }); + + this.listenTo(this.model, 'error', this.onError); + this.model.fetch(); }, openPage: function(page) { console.log('Change page', page); // check if number is reasonable - if (!_.isNumber(page) || !_.isFinite(page)) + if (!_.isNumber(page) || !_.isFinite(page) || page < 0 || page >= this.pages.length) return; if (page === this.page) return; + // Render error directly + if (this.error) { + this.onRender(); + return; + } + this.page = page; - this.onRender(); + + var self = this; + this.ui.page.fadeOut({complete: function() { + self.onRender(); + }}); this.model.trigger('page:changed', page); }, + onError: function(model, xhr) { + console.log('Setup error', xhr); + this.error = xhr; + this.onRender(); + }, + onRender: function() { + // close old opened view if (this.view) this.view.close(); - // TODO: animation + // Render error if occurred + if (this.error) { + this.ui.page.html(templateError(this.error)); + return; + } + this.view = new this.pages[this.page]({model: this.model}); this.ui.page.empty(); - this.ui.page.append(this.view.render().$el); + + var el = this.view.render().el; + this.ui.page.append(el); + + this.ui.page.fadeIn(); } }); diff --git a/pyload/web/app/scripts/views/setup/systemView.js b/pyload/web/app/scripts/views/setup/systemView.js index 11e50213d..b4c0f7e12 100644 --- a/pyload/web/app/scripts/views/setup/systemView.js +++ b/pyload/web/app/scripts/views/setup/systemView.js @@ -8,12 +8,17 @@ define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/system'], template: template, events: { + 'click .btn-blue': 'nextPage' }, ui: { }, onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); } }); diff --git a/pyload/web/app/scripts/views/setup/userView.js b/pyload/web/app/scripts/views/setup/userView.js new file mode 100644 index 000000000..95eaa0dc2 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/userView.js @@ -0,0 +1,39 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/user'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'User', + template: template, + + events: { + 'click .btn-blue': 'submit' + }, + + ui: { + username: '#username', + password: '#password', + password2: '#password2' + }, + + onRender: function() { + }, + + submit: function() { + var pw = this.ui.password.val(); + var pw2 = this.ui.password2.val(); + + // TODO more checks and error messages + if (pw !== pw2) { + return; + } + + this.model.set('user', this.ui.username.val()); + this.model.set('password', pw); + + this.model.trigger('page:next'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/welcomeView.js b/pyload/web/app/scripts/views/setup/welcomeView.js index 4affc9075..a964e0d42 100644 --- a/pyload/web/app/scripts/views/setup/welcomeView.js +++ b/pyload/web/app/scripts/views/setup/welcomeView.js @@ -8,12 +8,17 @@ define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/welcome'], template: template, events: { + 'click .btn-blue': 'nextPage' }, ui: { }, onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); } }); diff --git a/pyload/web/app/styles/default/main.less b/pyload/web/app/styles/default/main.less index 6bf21e80b..6153b576e 100644 --- a/pyload/web/app/styles/default/main.less +++ b/pyload/web/app/styles/default/main.less @@ -13,10 +13,10 @@ @import "settings"; @import "accounts"; @import "admin"; +@import "setup"; @ResourcePath: "../.."; @DefaultFont: 'Abel', sans-serif; // Changed dimensions -@header-height: 70px; -@footer-height: 66px;
\ No newline at end of file +@header-height: 70px;;
\ No newline at end of file diff --git a/pyload/web/app/styles/default/setup.less b/pyload/web/app/styles/default/setup.less new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/web/app/styles/default/setup.less diff --git a/pyload/web/app/styles/default/style.less b/pyload/web/app/styles/default/style.less index da0e68991..ad60e5b59 100644 --- a/pyload/web/app/styles/default/style.less +++ b/pyload/web/app/styles/default/style.less @@ -284,15 +284,4 @@ header { // background-color: @greyDark; border-radius: 15px; -moz-border-radius: 15px; -webkit-border-radius: 15px; -} - -/* - Footer -*/ -footer .copyright { - background-size: 40px 40px; - background-position: 12px center; - height: 40px; - padding-left: 40px; - padding-top: 10px; -} +}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/error.html b/pyload/web/app/templates/default/setup/error.html new file mode 100644 index 000000000..37ce51283 --- /dev/null +++ b/pyload/web/app/templates/default/setup/error.html @@ -0,0 +1,14 @@ +{{#ifEq status 410}} + <h2 class="text-warning">{{ _ "Setup timed out" }}</h2> + <p>{{ _ "Setup was closed due to inactivity. Please restart it to continue configuration." }}</p> +{{else}} +{{#ifEq status 409}} + <h2 class="text-success">{{ _ "Setup finished" }}</h2> + <p>{{ _ "Setup was successful. You can restart pyLoad now." }}</p> +{{else}} + <h2 class="text-error"> + {{ _ "Setup failed" }} + </h2> + <p>{{ _ "Try to restart it or open a bug report." }}</p> +{{/ifEq}} +{{/ifEq}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/finished.html b/pyload/web/app/templates/default/setup/finished.html new file mode 100644 index 000000000..22a97649b --- /dev/null +++ b/pyload/web/app/templates/default/setup/finished.html @@ -0,0 +1,23 @@ +{{#if user}} + +<h2> + {{ _ "Nearly Done" }} +</h2> + +<p> + {{ _ "Please check your settings." }} +</p> + +<p> + <strong>Username:</strong> {{user}} +</p> + +<button class="btn btn-large btn-blue"> + {{ _ "Confirm" }} +</button> + +{{else}} + +<h2>{{ _ "Please add a user first." }}</h2> + +{{/if}} diff --git a/pyload/web/app/templates/default/setup/layout.html b/pyload/web/app/templates/default/setup/layout.html index 7b75e53b1..2e986173a 100644 --- a/pyload/web/app/templates/default/setup/layout.html +++ b/pyload/web/app/templates/default/setup/layout.html @@ -3,5 +3,8 @@ {{ _ "Setup" }} </h1> </div> -<div class="span8 setup-page"> +<div class="span8"> + <div class="hero-unit setup-page"> + + </div> </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/system.html b/pyload/web/app/templates/default/setup/system.html index 84a217b19..0c5023669 100644 --- a/pyload/web/app/templates/default/setup/system.html +++ b/pyload/web/app/templates/default/setup/system.html @@ -1,5 +1,56 @@ -<h1>{{ _ "System" }} </h1> +<h3>{{ _ "System" }} </h3> -<h2>{{_ "Dependencies" }}</h2> +<dl class="dl-horizontal"> + {{#each system}} + <dt>{{ @key }}</dt> + <dd>{{ this }}</dd> + {{/each}} +</dl> -<h2>{{ _ "Optional" }}</h2>
\ No newline at end of file +<h3>{{_ "Dependencies" }}</h3> +<dl class="dl-horizontal"> + {{#each deps.core}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + <i class="icon-ok"></i> + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + <i class="icon-remove"></i> + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<h4>{{ _ "Optional" }}</h4> +<dl class="dl-horizontal"> + {{#each deps.opt}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + {{ _ "available" }} + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + {{ _ "not available" }} + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<button class="btn btn-blue"> + {{ _ "Next" }} +</button>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/user.html b/pyload/web/app/templates/default/setup/user.html new file mode 100644 index 000000000..fe3f2de71 --- /dev/null +++ b/pyload/web/app/templates/default/setup/user.html @@ -0,0 +1,34 @@ +<form class="form-horizontal"> + <div class="control-group"> + <label class="control-label"> + Username + </label> + + <div class="controls"> + <input type="text" id="username" placeholder="User"> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password + </label> + + <div class="controls"> + <input type="password" id="password"> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password (again) + </label> + + <div class="controls"> + <input type="password" id="password2"> + </div> + </div> + <div class="control-group"> + <div class="controls"> + <a class="btn btn-blue">Submit</a> + </div> + </div> +</form>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/welcome.html b/pyload/web/app/templates/default/setup/welcome.html index f5c5af4d7..5a4f74d9f 100644 --- a/pyload/web/app/templates/default/setup/welcome.html +++ b/pyload/web/app/templates/default/setup/welcome.html @@ -1,16 +1,14 @@ -<div class="hero-unit"> - <h1>{{ _ "Welcome!" }}</h1> +<h1>{{ _ "Welcome!" }}</h1> - <p>{{ _ "pyLoad is running and ready for configuration." }}</p> +<p>{{ _ "pyLoad is running and ready for configuration." }}</p> - <p> - {{ _ "Select your language:" }} - <select> - <option>en</option> - </select> - </p> +<p> + {{ _ "Select your language:" }} + <select> + <option>en</option> + </select> +</p> - <button class="btn btn-large btn-blue"> - {{ _ "Start configuration" }} - </button> -</div>
\ No newline at end of file +<button class="btn btn-large btn-blue"> + {{ _ "Start configuration" }} +</button>
\ No newline at end of file diff --git a/pyload/web/bower.json b/pyload/web/bower.json index b0176a891..4da3634a0 100644 --- a/pyload/web/bower.json +++ b/pyload/web/bower.json @@ -11,7 +11,7 @@ "jquery.cookie": "~1.3.1", "jquery.animate-enhanced": "*", "flot": "~0.8.1", - "underscore": "~1.4.4", + "underscore": "~1.5.1", "backbone": "~1.0.0", "backbone.marionette": "~1.1.0", "handlebars.js": "1.0.0-rc.3", diff --git a/pyload/web/middlewares.py b/pyload/web/middlewares.py index 074681b8f..af355bf11 100644 --- a/pyload/web/middlewares.py +++ b/pyload/web/middlewares.py @@ -1,17 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# gzip is optional on some platform -try: - import gzip -except ImportError: - gzip = None - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - class StripPathMiddleware(object): def __init__(self, app): self.app = app @@ -31,105 +20,3 @@ class PrefixMiddleware(object): if path.startswith(self.prefix): e['PATH_INFO'] = path.replace(self.prefix, "", 1) return self.app(e, h) - -# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) -# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php - -# WSGI middleware -# Gzip-encodes the response. - -# TODO: not in use anymore, because of pre-gzipped resources -class GZipMiddleWare(object): - - def __init__(self, application, compress_level=6): - self.application = application - self.compress_level = int(compress_level) - - def __call__(self, environ, start_response): - if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''): - # nothing for us to do, so this middleware will - # be a no-op: - return self.application(environ, start_response) - response = GzipResponse(start_response, self.compress_level) - app_iter = self.application(environ, - response.gzip_start_response) - if app_iter is not None: - response.finish_response(app_iter) - - return response.write() - -def header_value(headers, key): - for header, value in headers: - if key.lower() == header.lower(): - return value - -def update_header(headers, key, value): - remove_header(headers, key) - headers.append((key, value)) - -def remove_header(headers, key): - for header, value in headers: - if key.lower() == header.lower(): - headers.remove((header, value)) - break - -class GzipResponse(object): - - def __init__(self, start_response, compress_level): - self.start_response = start_response - self.compress_level = compress_level - self.buffer = StringIO() - self.compressible = False - self.content_length = None - self.headers = () - - def gzip_start_response(self, status, headers, exc_info=None): - self.headers = headers - ct = header_value(headers,'content-type') - ce = header_value(headers,'content-encoding') - cl = header_value(headers, 'content-length') - - # don't compress on unknown size, it may be too huge - cl = int(cl) if cl else 0 - - if ce: - self.compressible = False - elif gzip is not None and ct and (ct.startswith('text/') or ct.startswith('application/')) \ - and 'zip' not in ct and 200 < cl < 1024*1024: - self.compressible = True - headers.append(('content-encoding', 'gzip')) - headers.append(('vary', 'Accept-Encoding')) - - remove_header(headers, 'content-length') - self.headers = headers - self.status = status - return self.buffer.write - - def write(self): - out = self.buffer - out.seek(0) - s = out.getvalue() - out.close() - return [s] - - def finish_response(self, app_iter): - if self.compressible: - output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, - fileobj=self.buffer) - else: - output = self.buffer - try: - for s in app_iter: - output.write(s) - if self.compressible: - output.close() - finally: - if hasattr(app_iter, 'close'): - try: - app_iter.close() - except : - pass - - content_length = self.buffer.tell() - update_header(self.headers, "Content-Length" , str(content_length)) - self.start_response(self.status, self.headers)
\ No newline at end of file diff --git a/pyload/web/pyload_app.py b/pyload/web/pyload_app.py index 1ec7cf4c9..1c89e2ada 100644 --- a/pyload/web/pyload_app.py +++ b/pyload/web/pyload_app.py @@ -70,7 +70,11 @@ def index(): # set variable depending on setup mode setup = 'false' if SETUP is None else 'true' ws = PYLOAD.getWSAddress() if PYLOAD else False - web = PYLOAD.getConfigValue('webinterface', 'port') if PYLOAD else False + web = None + if PYLOAD: + web = PYLOAD.getConfigValue('webinterface', 'port') + elif SETUP: + web = SETUP.config['webinterface']['port'] # Render variables into the html page if resp.status_code == 200: diff --git a/pyload/web/setup_app.py b/pyload/web/setup_app.py index 5163f9cc6..939fcb600 100644 --- a/pyload/web/setup_app.py +++ b/pyload/web/setup_app.py @@ -3,26 +3,71 @@ from time import time +from pyload.utils import json_dumps + from bottle import route, request, response, HTTPError, redirect from webinterface import PROJECT_DIR, SETUP from utils import add_json_header +# returns http error +def error(code, msg): + return HTTPError(code, json_dumps(msg), **dict(response.headers)) + + def setup_required(func): def _view(*args, **kwargs): + global timestamp + # setup needs to be running if SETUP is None: - redirect("/nopermission") + return error(404, "Not Found") + + # setup finished + if timestamp == 0: + return error(409, "Done") + + # setup timed out due to inactivity + if timestamp + TIMEOUT * 60 < time(): + return error(410, "Timeout") + + timestamp = time() return func(*args, **kwargs) + return _view # setup will close after inactivity TIMEOUT = 15 timestamp = time() + @route("/setup") @setup_required def setup(): - pass # TODO + add_json_header(response) + + return json_dumps({ + "system": SETUP.check_system(), + "deps": SETUP.check_deps() + }) + + +@route("/setup_done") +@setup_required +def setup_done(): + global timestamp + add_json_header(response) + + SETUP.addUser( + request.params['user'], + request.params['password'] + ) + + SETUP.save() + + # mark setup as finished + timestamp = 0 + + return error(409, "Done") diff --git a/pyload/web/utils.py b/pyload/web/utils.py index e94089185..7e8ee3f13 100644 --- a/pyload/web/utils.py +++ b/pyload/web/utils.py @@ -4,14 +4,21 @@ import re from bottle import request, HTTPError, redirect +try: + import zlib +except ImportError: + zlib = None + from webinterface import PYLOAD, SETUP + def add_json_header(r): r.headers.replace("Content-type", "application/json") r.headers.append("Cache-Control", "no-cache, must-revalidate") r.headers.append("Access-Control-Allow-Origin", request.get_header('Origin', '*')) r.headers.append("Access-Control-Allow-Credentials", "true") + def set_session(request, user): s = request.environ.get('beaker.session') s["uid"] = user.uid @@ -58,15 +65,14 @@ def is_mobile(): return True return False -def select_language(langs): +def select_language(langs): accept = request.headers.get('Accept-Language', '') # TODO return langs[0] - def login_required(perm=None): def _dec(func): def _view(*args, **kwargs): |