summaryrefslogtreecommitdiffstats
path: root/pyload
diff options
context:
space:
mode:
Diffstat (limited to 'pyload')
-rw-r--r--pyload/Api.py27
-rw-r--r--pyload/Core.py6
-rw-r--r--pyload/api/DownloadPreparingApi.py4
-rw-r--r--pyload/api/UserApi.py41
-rw-r--r--pyload/api/__init__.py2
-rw-r--r--pyload/network/Bucket.py29
-rw-r--r--pyload/plugins/Crypter.py57
-rw-r--r--pyload/plugins/accounts/DdlstorageCom.py44
-rw-r--r--pyload/plugins/accounts/FilebeerInfo.py57
-rw-r--r--pyload/plugins/accounts/FilecloudIo.py31
-rw-r--r--pyload/plugins/accounts/SpeedLoadOrg.py13
-rwxr-xr-xpyload/plugins/addons/Captcha9kw.py9
-rw-r--r--pyload/plugins/addons/Checksum.py2
-rw-r--r--pyload/plugins/crypter/FilebeerInfoFolder.py28
-rw-r--r--pyload/plugins/crypter/FiletramCom.py31
-rw-r--r--pyload/plugins/crypter/LofCc.py4
-rw-r--r--pyload/plugins/crypter/NCryptIn.py73
-rw-r--r--pyload/plugins/crypter/SpeedLoadOrgFolder.py9
-rw-r--r--pyload/plugins/crypter/TurbobitNetFolder.py60
-rw-r--r--pyload/plugins/crypter/XupPl.py18
-rw-r--r--pyload/plugins/hoster/CzshareCom.py2
-rw-r--r--pyload/plugins/hoster/DailymotionCom.py3
-rw-r--r--pyload/plugins/hoster/DdlstorageCom.py73
-rw-r--r--pyload/plugins/hoster/DlFreeFr.py2
-rw-r--r--pyload/plugins/hoster/FilecloudIo.py17
-rw-r--r--pyload/plugins/hoster/FileserveCom.py2
-rw-r--r--pyload/plugins/hoster/FileshareInUa.py3
-rw-r--r--pyload/plugins/hoster/FreevideoCz.py6
-rw-r--r--pyload/plugins/hoster/GamefrontCom.py6
-rw-r--r--pyload/plugins/hoster/GooIm.py55
-rw-r--r--pyload/plugins/hoster/HotfileCom.py3
-rw-r--r--pyload/plugins/hoster/IfolderRu.py4
-rw-r--r--pyload/plugins/hoster/JumbofilesCom.py3
-rw-r--r--pyload/plugins/hoster/Keep2shareCC.py89
-rw-r--r--pyload/plugins/hoster/MediafireCom.py6
-rw-r--r--pyload/plugins/hoster/MegaNz.py13
-rw-r--r--pyload/plugins/hoster/MegasharesCom.py2
-rw-r--r--pyload/plugins/hoster/NetloadIn.py3
-rw-r--r--pyload/plugins/hoster/NowDownloadEu.py3
-rw-r--r--pyload/plugins/hoster/OneFichierCom.py5
-rw-r--r--pyload/plugins/hoster/Premium4Me.py3
-rw-r--r--pyload/plugins/hoster/PutlockerCom.py3
-rw-r--r--pyload/plugins/hoster/RapidgatorNet.py3
-rw-r--r--pyload/plugins/hoster/RgHostNet.py28
-rw-r--r--pyload/plugins/hoster/Share76Com.py4
-rw-r--r--pyload/plugins/hoster/ShareRapidCom.py77
-rw-r--r--pyload/plugins/hoster/SpeedLoadOrg.py14
-rw-r--r--pyload/plugins/hoster/TurbobitNet.py4
-rw-r--r--pyload/plugins/hoster/UploadedTo.py6
-rw-r--r--pyload/plugins/hoster/UptoboxCom.py4
-rw-r--r--pyload/plugins/hoster/X7To.py3
-rw-r--r--pyload/plugins/hoster/XFileSharingPro.py25
-rw-r--r--pyload/plugins/hoster/YoutubeCom.py11
-rw-r--r--pyload/plugins/hoster/ZippyshareCom.py92
-rw-r--r--pyload/plugins/internal/DeadCrypter.py14
-rw-r--r--pyload/plugins/internal/SimpleCrypter.py30
-rw-r--r--pyload/plugins/internal/SimpleHoster.py11
-rw-r--r--pyload/plugins/network/CurlChunk.py14
-rw-r--r--pyload/plugins/network/CurlRequest.py1
-rw-r--r--pyload/remote/json_converter.py11
-rw-r--r--pyload/remote/pyload.thrift6
-rw-r--r--pyload/setup/Setup.py128
-rw-r--r--pyload/setup/dependencies.py15
-rw-r--r--pyload/setup/system.py16
-rw-r--r--pyload/threads/DecrypterThread.py10
-rw-r--r--pyload/threads/InfoThread.py2
-rw-r--r--pyload/threads/ThreadManager.py8
-rw-r--r--pyload/utils/__init__.py2
-rw-r--r--pyload/web/api_app.py32
-rw-r--r--pyload/web/app/index.html47
-rw-r--r--pyload/web/app/scripts/models/Setup.js20
-rw-r--r--pyload/web/app/scripts/views/linkgrabber/packageView.js12
-rw-r--r--pyload/web/app/scripts/views/setup/finishedView.js25
-rw-r--r--pyload/web/app/scripts/views/setup/setupView.js52
-rw-r--r--pyload/web/app/scripts/views/setup/systemView.js5
-rw-r--r--pyload/web/app/scripts/views/setup/userView.js39
-rw-r--r--pyload/web/app/scripts/views/setup/welcomeView.js5
-rw-r--r--pyload/web/app/styles/default/main.less4
-rw-r--r--pyload/web/app/styles/default/setup.less0
-rw-r--r--pyload/web/app/styles/default/style.less13
-rw-r--r--pyload/web/app/templates/default/setup/error.html14
-rw-r--r--pyload/web/app/templates/default/setup/finished.html23
-rw-r--r--pyload/web/app/templates/default/setup/layout.html5
-rw-r--r--pyload/web/app/templates/default/setup/system.html57
-rw-r--r--pyload/web/app/templates/default/setup/user.html34
-rw-r--r--pyload/web/app/templates/default/setup/welcome.html24
-rw-r--r--pyload/web/bower.json2
-rw-r--r--pyload/web/middlewares.py113
-rw-r--r--pyload/web/pyload_app.py6
-rw-r--r--pyload/web/setup_app.py49
-rw-r--r--pyload/web/utils.py10
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&eacute;l&eacute;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>.*?)&nbsp;&nbsp;<'
@@ -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 &nbsp;<i class="icon-comment"></i>
+ </a>
+ </h2>
<hr>
- Bootstrap <br>
+ <a href="http://pyload.org" target="_blank">Homepage</a>&nbsp;&middot;
+ <a href="http://board.pyload.org" target="_blank">Board</a>&nbsp;&middot;
+ <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 &nbsp;<i class="icon-twitter"></i>
+ </a>
+ </h2>
<hr>
- dsfdsf <br>
+ <a href="https://twitter.com/pyload" target="_blank">Twitter</a>&nbsp;&middot;
+ <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 &nbsp;<i class="icon-github"></i>
+ </a>
+ </h2>
<hr>
- asd <br>
+ <a href="https://github.com/pyload" target="_blank">Github</a>&nbsp;&middot;
+ <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 &nbsp;<i class="icon-bitcoin">&nbsp;</i>
+ </a>
+ </h2>
<hr>
- asd <br>
+ <a href="http://pyload.org/donate" target="_blank">PayPal</a>&nbsp;&middot;
+ <a href="http://blockchain.info/address/1JvcfSKuzk3VENJm9XtqGp2DCTesgokkG2" target="_blank">Bitcoin</a>&nbsp;&middot;
+ <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):