[a-zA-Z0-9$]+)\(', player_data)
try:
@@ -339,15 +335,15 @@ def _decrypt_signature(self, encrypted_sig):
#: Since Youtube just scrambles the order of the characters in the signature
#: and does not change any byte value, we can store just a transformation map as a cached function
decrypt_map = [ord(c) for c in decrypt_func(''.join(map(unichr, range(len(encrypted_sig)))))]
- cache_info['cache'][player_url] = {'decrypt_map': decrypt_map,
- 'time': time.time()}
+ cache_info['cache'][sig_cache_id] = {'decrypt_map': decrypt_map,
+ 'time': time.time()}
cache_dirty = True
decrypted_sig = decrypt_func(encrypted_sig)
except (JSInterpreterError, AssertionError), e:
self.log_error(_("Signature decode failed"), e)
- self.fail(e.message)
+ self.fail(e.args[0])
#: Remove old records from cache
for _k in list(cache_info['cache'].keys()):
@@ -419,7 +415,7 @@ def _handle_video(self):
else:
signature = video_streams[chosen_fmt][1]
- url += "&signature=" + signature
+ url += "&%s=%s" % (video_streams[chosen_fmt][3], signature)
if "&ratebypass=" not in url:
url += "&ratebypass=yes"
@@ -437,7 +433,7 @@ def _handle_video(self):
filename = os.path.join(self.pyload.config.get("general", "download_folder"),
self.pyfile.package().folder,
self.pyfile.name)
- self.log_info(_("Download skipped: %s due to %s") % (self.pyfile.name, e.message))
+ self.log_info(_("Download skipped: %s due to %s") % (self.pyfile.name, e.args[0]))
return filename, chosen_fmt
@@ -487,7 +483,7 @@ def _handle_audio(self, video_fmt):
else:
signature = audio_streams[chosen_fmt][1]
- url += "&signature=" + signature
+ url += "&%s=%s" % (audio_streams[chosen_fmt][3], signature)
if "&ratebypass=" not in url:
url += "&ratebypass=yes"
@@ -502,7 +498,7 @@ def _handle_audio(self, video_fmt):
filename = os.path.join(self.pyload.config.get("general", "download_folder"),
self.pyfile.package().folder,
self.pyfile.name)
- self.log_info(_("Download skipped: %s due to %s") % (self.pyfile.name, e.message))
+ self.log_info(_("Download skipped: %s due to %s") % (self.pyfile.name, e.args[0]))
return filename, chosen_fmt
@@ -514,32 +510,62 @@ def _format_srt_time(millisec):
h, m = divmod(m, 60)
return "%02d:%02d:%02d,%s" % (h, m, s, milli)
- i = 1
srt = ""
dom = parse_xml(timedtext)
body = dom.getElementsByTagName("body")[0]
paras = body.getElementsByTagName("p")
+ subtitles = []
for para in paras:
- srt += str(i) + "\n"
- srt += _format_srt_time(int(para.attributes['t'].value)) + ' --> ' + \
- _format_srt_time(int(para.attributes['t'].value) + int(para.attributes['d'].value)) + "\n"
- for child in para.childNodes:
- if child.nodeName == 'br':
- srt += "\n"
- elif child.nodeName == '#text':
- srt += unicode(child.data)
- srt += "\n\n"
- i += 1
+ try:
+ start_time = int(para.attributes['t'].value)
+ end_time = int(para.attributes['t'].value) + int(para.attributes['d'].value)
+ except KeyError:
+ continue
+
+ subtitle_text = ""
+ words = para.getElementsByTagName("s")
+ if words:
+ subtitle_text = "".join([unicode(word.firstChild.data) for word in words])
+
+ else:
+ for child in para.childNodes:
+ if child.nodeName == 'br':
+ subtitle_text += "\n"
+ elif child.nodeName == '#text':
+ subtitle_text += unicode(child.data)
+
+ if subtitle_text.strip():
+ subtitles.append({'start': start_time,
+ 'end': end_time,
+ 'text': subtitle_text})
+ else:
+ continue
+
+ for line_num in range(len(subtitles)):
+ start_time = subtitles[line_num]['start']
+ try:
+ end_time = min(subtitles[line_num]['end'], subtitles[line_num + 1]['start'])
+ except IndexError:
+ end_time = subtitles[line_num]['end']
+
+ subtitle_text = subtitles[line_num]['text']
+
+ subtitle_element = str(line_num + 1) + "\n" \
+ + _format_srt_time(start_time) + ' --> ' + _format_srt_time(end_time) + "\n" \
+ + subtitle_text + "\n\n"
+ srt += subtitle_element
return srt
srt_files =[]
try:
- subs = json.loads(self.player_config['args']['player_response'])['captions']['playerCaptionsTracklistRenderer']['captionTracks']
- subtitles_urls = dict([(_subtitle['languageCode'],
- urllib.unquote(_subtitle['baseUrl']).decode('unicode-escape') + "&fmt=3")
+ subs = self.player_response['captions']['playerCaptionsTracklistRenderer']['captionTracks']
+ subtitles_info = dict([(_subtitle['languageCode'],
+ (urllib.unquote(_subtitle['baseUrl']).decode('unicode-escape') + "&fmt=3",
+ _subtitle['vssId'].startswith("a."),
+ _subtitle['isTranslatable']))
for _subtitle in subs])
- self.log_debug("AVAILABLE SUBTITLES: %s" % subtitles_urls.keys() or "None")
+ self.log_debug("AVAILABLE SUBTITLES: %s" % subtitles_info.keys() or "None")
except KeyError:
self.log_debug("AVAILABLE SUBTITLES: None")
@@ -547,22 +573,40 @@ def _format_srt_time(millisec):
subs_dl = self.config.get('subs_dl')
if subs_dl != "off":
+ subs_translate = self.config.get('subs_translate').strip()
+ auto_subs = self.config.get('auto_subs')
+ subs_dl = "first_available" if subs_translate != "" else subs_dl
subs_dl_langs = [_x.strip() for _x in self.config.get('subs_dl_langs', "").split(',') if _x.strip()]
+
if subs_dl_langs:
# Download only listed subtitles (`subs_dl_langs` config gives the priority)
for _lang in subs_dl_langs:
- if _lang in subtitles_urls:
- srt_filename = os.path.join(self.pyload.config.get("general", "download_folder"),
- self.pyfile.package().folder,
- os.path.splitext(self.file_name)[0] + "." + _lang + ".srt")
+ if _lang in subtitles_info:
+ subtitle_code = _lang if subs_translate == "" else subs_translate
+
+ if auto_subs is False and subtitles_info[_lang][1] is True:
+ self.log_warning(_("Skipped machine generated subtitle: %s") % _lang)
+ continue
+
+ subtitle_url = subtitles_info[_lang][0]
+ if subs_translate:
+ if subtitles_info[_lang][2]: #: Translatable?
+ subtitle_url += "&tlang=%s" % subs_translate
+ else:
+ self.log_warning(_("Skipped non translatable subtitle: %s") % _lang)
+ continue #: No, try next one
+
+ srt_filename = fsjoin(self.pyload.config.get("general", "download_folder"),
+ self.pyfile.package().folder,
+ self.file_name + "." + subtitle_code + ".srt")
if self.pyload.config.get('download', 'skip_existing') and \
exists(srt_filename) and os.stat(srt_filename).st_size != 0:
self.log_info("Download skipped: %s due to File exists" % os.path.basename(srt_filename))
- srt_files.append((srt_filename, _lang))
+ srt_files.append((srt_filename, subtitle_code))
continue
- timed_text = self.load(subtitles_urls[_lang], decode=False)
+ timed_text = self.load(subtitle_url, decode=False)
srt = timedtext_to_srt(timed_text)
with open(srt_filename, "w") as f:
@@ -575,18 +619,32 @@ def _format_srt_time(millisec):
else:
# Download any available subtitle
- for _subtitle in subtitles_urls.items():
- srt_filename = os.path.join(self.pyload.config.get("general", "download_folder"),
- self.pyfile.package().folder,
- os.path.splitext(self.file_name)[0] + "." + _subtitle[0] + ".srt")
+ for _subtitle in subtitles_info.items():
+ if auto_subs is False and _subtitle[1][1] is True:
+ self.log_warning(_("Skipped machine generated subtitle: %s") % _subtitle[0])
+ continue
+
+ subtitle_code = _subtitle[0] if subs_translate == "" else subs_translate
+
+ subtitle_url = _subtitle[1][0]
+ if subs_translate:
+ if _subtitle[1][2]: #: Translatable?
+ subtitle_url += "&tlang=%s" % subs_translate
+ else:
+ self.log_warning(_("Skipped non translatable subtitle: %s") % _subtitle[0])
+ continue #: No, try next one
+
+ srt_filename = fsjoin(self.pyload.config.get("general", "download_folder"),
+ self.pyfile.package().folder,
+ os.path.splitext(self.file_name)[0] + "." + subtitle_code + ".srt")
if self.pyload.config.get('download', 'skip_existing') and \
exists(srt_filename) and os.stat(srt_filename).st_size != 0:
self.log_info("Download skipped: %s due to File exists" % os.path.basename(srt_filename))
- srt_files.append((srt_filename, _subtitle[0]))
+ srt_files.append((srt_filename, subtitle_code))
continue
- timed_text = self.load(_subtitle[1], decode=False)
+ timed_text = self.load(subtitle_url, decode=False)
srt = timedtext_to_srt(timed_text)
with open(srt_filename, "w") as f:
@@ -594,7 +652,7 @@ def _format_srt_time(millisec):
self.set_permissions(srt_filename)
self.log_debug("Saved subtitle: %s" % os.path.basename(srt_filename))
- srt_files.append((srt_filename, _lang))
+ srt_files.append((srt_filename, subtitle_code))
if subs_dl == "first_available":
break
@@ -626,8 +684,8 @@ def _postprocess(self, video_filename, audio_filename, subtitles_files):
self.ffmpeg.set_output_filename(final_filename)
self.pyfile.name = os.path.basename(final_filename)
- self.pyfile.size = os.path.getsize(video_filename) + \
- os.path.getsize(audio_filename) #: Just an estimate
+ self.pyfile.size = os.path.getsize(fs_encode(video_filename)) + \
+ os.path.getsize(fs_encode(audio_filename)) #: Just an estimate
if self.ffmpeg.run():
self.remove(video_filename, trash=False)
@@ -679,6 +737,7 @@ def _postprocess(self, video_filename, audio_filename, subtitles_files):
def setup(self):
self.resume_download = True
+ self.chunk_limit = -1
self.multiDL = True
try:
@@ -687,31 +746,58 @@ def setup(self):
pass
self.req.http = BIGHTTPRequest(
- cookies=CookieJar(None),
+ cookies=self.req.cj,
options=self.pyload.requestFactory.getOptions(),
- limit=2500000)
+ limit=5000000)
def process(self, pyfile):
pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
self.data = self.load(pyfile.url)
- if re.search(r'',
- self.data) or '"playabilityStatus":{"status":"ERROR"' in self.data:
- self.offline()
+ url, inputs = self.parse_html_form('action="https://consent.youtube.com/s"')
+ if url is not None:
+ self.data = self.load(url, post=inputs)
+
+ m = re.search(r'"playabilityStatus":{"status":"(\w+)",(:?"(?:reason":|messages":\[)"([^"]+))?', self.data)
+ if m is None:
+ self.log_warning(_("Playability status pattern not found"))
+
+ else:
+ if m.group(1) != "OK":
+ if m.group(2):
+ self.log_error(m.group(2))
+ self.offline()
if "We have been receiving a large volume of requests from your network." in self.data:
self.temp_offline()
m = re.search(r'ytplayer.config = ({.+?});', self.data)
+ if m is not None:
+ self.player_config = json.loads(m.group(1))
+ self.player_response = json.loads(self.player_config['args']['player_response'])
+
+ else:
+ m =re.search(r'ytInitialPlayerResponse = ({.+?});', self.data)
+ if m is not None:
+ self.player_config = json.loads(m.group(1))
+ self.player_response = self.player_config
+
+ else:
+ self.fail(_("Player config pattern not found"))
+
+ m = re.search(r'"jsUrl"\s*:\s*"(.+?)"', self.data) or re.search(r'"assets":.+?"js":\s*"(.+?)"', self.data)
if m is None:
- self.fail(_("Player config pattern not found"))
+ self.fail(_("Player URL pattern not found"))
+
+ self.player_url = self.fixurl(m.group(1))
- self.player_config = json.loads(m.group(1))
+ if not self.player_url.endswith(".js"):
+ self.fail(_("Unsupported player type %s") % self.player_url)
- self.ffmpeg = Ffmpeg(self.config.get('priority') ,self)
+ self.ffmpeg = Ffmpeg(self.config.get('priority'), self)
#: Set file name
- self.file_name = self.player_config['args']['title']
+ self.file_name = decode(self.player_response['videoDetails']['title'])
#: Check for start time
self.start_time = (0, 0)
@@ -721,27 +807,49 @@ def process(self, pyfile):
self.file_name += " (starting at %sm%ss)" % (self.start_time[0], self.start_time[1])
#: Cleaning invalid characters from the file name
- self.file_name = self.file_name.encode('ascii', 'replace')
- for c in self.invalid_chars:
- self.file_name = self.file_name.replace(c, '_')
+ self.file_name = safename(self.file_name)
#: Parse available streams
- streams_keys = ['url_encoded_fmt_stream_map']
- if 'adaptive_fmts' in self.player_config['args']:
- streams_keys.append('adaptive_fmts')
+ streams = []
+ for path in [('args', 'url_encoded_fmt_stream_map'),
+ ('args', 'adaptive_fmts')]:
+ item = try_get(self.player_config, *path)
+ if item is not None:
+ strms = [urlparse.parse_qs(_s) for _s in item.split(',')]
+ strms = [dict((k, v[0]) for k,v in _d.items()) for _d in strms]
+ streams.extend(strms)
+ streams.extend(try_get(self.player_response, 'streamingData', 'formats') or [])
+ streams.extend(try_get(self.player_response, 'streamingData', 'adaptiveFormats') or [])
self.streams = []
- for streams_key in streams_keys:
- streams = self.player_config['args'][streams_key]
- streams = [_s.split('&') for _s in streams.split(',')]
- streams = [dict((_x.split('=', 1)) for _x in _s) for _s in streams]
- streams = [(int(_s['itag']),
- urllib.unquote(_s['url']),
- _s.get('s', _s.get('sig', None)),
- True if 's' in _s else False)
- for _s in streams]
-
- self.streams += streams
+ for _s in streams:
+ itag = int(_s['itag'])
+ url_data = _s
+ url = _s.get('url', None)
+ if url is None:
+ cipher = _s.get('cipher', None)
+ if cipher is not None:
+ url_data = urlparse.parse_qs(cipher)
+ url_data = dict((k, v[0]) for k,v in url_data.items())
+ url = url_data.get('url')
+ if url is None:
+ continue
+
+ else:
+ cipher = _s.get('signatureCipher')
+ if cipher is not None:
+ url_data = urlparse.parse_qs(cipher)
+ url = try_get(url_data, 'url', 0)
+ if url is None:
+ continue
+
+ self.streams.append((itag,
+ url,
+ try_get(url_data, 's', 0) or url_data.get('s', url_data.get('sig', None)),
+ 's' in url_data,
+ try_get(url_data, 'sp', 0) or url_data.get('sp', "signature")))
+
+ self.streams = uniqify(self.streams)
self.log_debug("AVAILABLE STREAMS: %s" % [_s[0] for _s in self.streams])
@@ -761,8 +869,8 @@ def process(self, pyfile):
subtitles_files)
#: Everything is finished and final name can be set
- pyfile.name = os.path.basename(final_filename)
- pyfile.size = os.path.getsize(final_filename)
+ pyfile.name = os.path.basename(fs_encode(final_filename))
+ pyfile.size = os.path.getsize(fs_encode(final_filename))
self.last_download = final_filename
diff --git a/module/plugins/hoster/ZDF.py b/module/plugins/hoster/ZDF.py
index 5af2db3be1..07436df70d 100644
--- a/module/plugins/hoster/ZDF.py
+++ b/module/plugins/hoster/ZDF.py
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
+import json
+import os
import re
-import xml.etree.ElementTree as etree
+
+import pycurl
from ..internal.Hoster import Hoster
@@ -10,52 +13,52 @@
class ZDF(Hoster):
__name__ = "ZDF Mediathek"
__type__ = "hoster"
- __version__ = "0.89"
+ __version__ = "0.93"
__status__ = "testing"
- __pattern__ = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*'
- __config__ = [("activated", "bool", "Activated", True)]
+ __pattern__ = r"https://(?:www\.)?zdf\.de/(?P[/\w-]+)\.html"
+ __config__ = [("activated", "bool", "Activated", True),
+ ("use_premium", "bool", "Use premium account if available", True),
+ ("fallback", "bool", "Fallback to free download if premium fails", True),
+ ("chk_filesize", "bool", "Check file size", True),
+ ("max_wait", "int", "Reconnect if waiting time is greater than minutes", 10)]
- __description__ = """ZDF.de hoster plugin"""
+ __description__ = """ZDF.de downloader plugin"""
__license__ = "GPLv3"
__authors__ = []
- XML_API = "http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?id=%i"
-
- @staticmethod
- def video_key(video):
- return (
- int(video.findtext("videoBitrate", "0")),
- any(f.text == "progressive" for f in video.iter("facet")),
- )
-
- @staticmethod
- def video_valid(video):
- return video.findtext("url").startswith("http") and video.findtext("url").endswith(".mp4") and \
- video.findtext("facets/facet").startswith("progressive")
-
- @staticmethod
- def get_id(url):
- return int(re.search(r'\D*(\d{4,})\D*', url).group(1))
-
def process(self, pyfile):
- xml = etree.fromstring(
- self.load(
- self.XML_API %
- self.get_id(
- pyfile.url),
- decode=False))
-
- status = xml.findtext("./status/statuscode")
- if status != "ok":
- self.fail(_("Error retrieving manifest"))
-
- video = xml.find("video")
- title = video.findtext("information/title")
-
- pyfile.name = title.encode('ascii', errors='replace')
-
- target_url = sorted((v for v in video.iter("formitaet") if self.video_valid(v)),
- key=self.video_key)[-1].findtext("url")
-
- self.download(target_url)
+ self.data = self.load(pyfile.url)
+ try:
+ api_token = re.search(
+ r'window\.zdfsite\.player\.apiToken = "([\d\w]+)";', self.data
+ ).group(1)
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["Api-Auth: Bearer " + api_token])
+ id = re.match(self.__pattern__, pyfile.url).group("ID")
+
+ filename = json.loads(
+ self.load(
+ "https://api.zdf.de/content/documents/zdf/" + id + ".json",
+ get={"profile": "player-3"},
+ )
+ )
+ stream_list = filename["mainVideoContent"]["http://zdf.de/rels/target"][
+ "streams"
+ ]["default"]["extId"]
+
+ streams = json.loads(
+ self.load(
+ "https://api.zdf.de/tmd/2/ngplayer_2_4/vod/ptmd/mediathek/"
+ + stream_list
+ )
+ )
+ download_name = streams["priorityList"][0]["formitaeten"][0]["qualities"][
+ 0
+ ]["audio"]["tracks"][0]["uri"]
+
+ self.pyfile.name = os.path.basename(id) + os.path.splitext(download_name)[1]
+ self.download(download_name)
+
+ except Exception as exc:
+ self.log_error(exc)
diff --git a/module/plugins/hoster/ZShareNet.py b/module/plugins/hoster/ZShareNet.py
deleted file mode 100644
index fccdd08959..0000000000
--- a/module/plugins/hoster/ZShareNet.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from ..internal.DeadHoster import DeadHoster
-
-
-class ZShareNet(DeadHoster):
- __name__ = "ZShareNet"
- __type__ = "hoster"
- __version__ = "0.26"
- __status__ = "stable"
-
- __pattern__ = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
- __config__ = [] # @TODO: Remove in 0.4.10
-
- __description__ = """ZShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("espes", None),
- ("Cptn Sandwich", None)]
diff --git a/module/plugins/hoster/ZahikiNet.py b/module/plugins/hoster/ZahikiNet.py
deleted file mode 100644
index 950b918f4c..0000000000
--- a/module/plugins/hoster/ZahikiNet.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from ..internal.DeadHoster import DeadHoster
-
-
-class ZahikiNet(DeadHoster):
- __name__ = "ZahikiNet"
- __type__ = "hoster"
- __version__ = "0.07"
- __status__ = "testing"
-
- __pattern__ = r'https?://(?:www\.)?zahiki\.net/\w+/.+'
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Zahiki.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/module/plugins/hoster/ZbigzCom.py b/module/plugins/hoster/ZbigzCom.py
index 5c8e295e10..1c5d0eb792 100644
--- a/module/plugins/hoster/ZbigzCom.py
+++ b/module/plugins/hoster/ZbigzCom.py
@@ -1,147 +1,181 @@
# -*- coding: utf-8 -*-
-import random
-import re
+import os
import time
+import urllib
import urlparse
from ..internal.Hoster import Hoster
-from ..internal.misc import json
+from ..internal.misc import json, safejoin
+
+try:
+ from module.network.HTTPRequest import FormFile
+except ImportError:
+ pass
+
class ZbigzCom(Hoster):
__name__ = "ZbigzCom"
__type__ = "hoster"
- __version__ = "0.03"
+ __version__ = "0.05"
__status__ = "testing"
- __pattern__ = r'https?://.+\.torrent|magnet:\?.+'
+ __pattern__ = r'^unmatchable$'
__config__ = [("activated", "bool", "Activated", False)]
__description__ = """Zbigz.com hoster plugin"""
__license__ = "GPLv3"
__authors__ = [("GammaC0de", "nitzo2001[AT}yahoo[DOT]com")]
- def jquery_call(self, url, file_id, call_id, **kwargs):
- current_millis = int(time.time() * 1000)
- json_callback = "jQuery" + call_id + "_" + str(current_millis)
-
- urlp = urlparse.urlparse(url)
- get_params = kwargs.copy()
- get_params.update(urlparse.parse_qs(urlp.query))
-
- get_params['hash'] = file_id
- get_params['jsoncallback'] = json_callback
- get_params['_'] = current_millis
+ API_URL = "https://api.zbigz.com/v1/"
- jquery_data = self.load(
- urlp.scheme +
- "://" +
- urlp.netloc +
- urlp.path,
- get=get_params)
+ def load_json(self, url, **kwargs):
+ json_data = self.load(url, **kwargs)
+ return json.loads(json_data)
- m = re.search("%s\((.+?)\);" % json_callback, jquery_data)
-
- return json.loads(m.group(1)) if m else None
+ def api_call(self, method, **kwargs):
+ return self.load_json(self.API_URL + method, **kwargs)
def sleep(self, sec):
- for _i in range(sec):
+ for _ in range(sec):
if self.pyfile.abort:
break
time.sleep(1)
- def process(self, pyfile):
- self.data = self.load("http://m.zbigz.com/myfiles",
- post={'url': pyfile.url})
+ def exit_error(self, msg):
+ if self.tmp_file:
+ os.remove(self.tmp_file)
- if "Error. Only premium members are able to download" in self.data:
- self.fail(_("File can be downloaded by premium users only"))
+ self.fail(msg)
- m = re.search(r'&hash=(\w+)"', self.data)
- if m is None:
- self.fail("Hash not found")
+ def send_request_to_server(self):
+ """ Send torrent/magnet to the server """
- file_id = m.group(1)
- call_id = "".join(random.choice("0123456789") for _x in range(20))
+ if self.pyfile.url.startswith("magnet:"):
+ #: magnet URL, send it to the server
+ api_data = self.api_call(
+ "torrent/add",
+ post={'url': self.pyfile.url},
+ multipart=True
+ )
- self.pyfile.setCustomStatus("torrent")
- self.pyfile.setProgress(0)
+ else:
+ #: torrent URL
+ if self.pyfile.url.startswith("http"):
+ #: remote URL, download the torrent to tmp directory
+ torrent_content = self.load(self.pyfile.url, decode=False)
+ torrent_filename = safejoin(self.pyload.tempdir, "tmp_{}.torrent".format(self.pyfile.package().name))
+ with open(torrent_filename, "wb") as f:
+ f.write(torrent_content)
- json_data = self.jquery_call(
- "http://m.zbigz.com/core/info.php", file_id, call_id)
- if json_data is None:
- self.fail("Unexpected jQuery response")
+ else:
+ #: URL is a local torrent file (uploaded container)
+ torrent_filename = urllib.url2pathname(self.pyfile.url[7:]) #: trim the starting `file://`
+ if not os.path.exists(torrent_filename):
+ self.fail(_("Torrent file does not exist"))
- if 'faultString' in json_data:
- self.fail(json_data['faultString'])
+ self.tmp_file = torrent_filename
- pyfile.name = json_data['info']['name'] + \
- (".zip" if len(json_data['files']) > 1 else "")
- pyfile.size = json_data['info']['size']
+ #: Check if the torrent file path is inside pyLoad's temp directory
+ if os.path.abspath(torrent_filename).startswith(self.pyload.tempdir + os.sep):
+ #: send the torrent content to the server
+ api_data = self.api_call(
+ "torrent/add",
+ post={'file': FormFile(torrent_filename, mimetype="application/octet-stream")},
+ multipart=True
+ )
- while True:
- json_data = self.jquery_call(
- "http://m.zbigz.com/core/info.php", file_id, call_id)
- if json_data is None:
- self.fail("Unexpected jQuery response")
+ else:
+ self.fail(_("Illegal URL")) #: We don't allow files outside pyLoad's temp directory
- if 'faultString' in json_data:
- self.fail(json_data['faultString'])
+ if api_data['error']:
+ self.fail(api_data['message'])
- progress = int(json_data['info']['progress'])
- pyfile.setProgress(progress)
+ api_data = self.api_call("storage/list")
+ if api_data['error']:
+ self.fail(api_data['error_msg'])
- if json_data['info']['state'] != "downloading" or progress == 100:
- break
+ torrent_id = api_data["0"]["hash"]
+ server = api_data["0"]["server"]
- self.sleep(5)
+ if self.tmp_file:
+ os.remove(self.tmp_file)
+ self.tmp_file = None
- pyfile.setProgress(100)
+ return torrent_id, server
- if len(json_data['files']) == 1:
- download_url = "http://m.zbigz.com/file/%s/0" % file_id
+ def wait_for_server_dl(self, torrent_id, server):
+ """ Show progress while the server does the download """
- else:
- self.data = self.load("http://m.zbigz.com/file/%s/-1" % file_id)
+ self.pyfile.setCustomStatus("torrent")
+ self.pyfile.setProgress(0)
- m = re.search(
- r'\'(http://\w+.zbigz.com/core/zipstate.php\?hash=%s&did=(\w+)).+?\'' %
- file_id, self.data)
- if m is None:
- self.fail("Zip state URL not found")
+ while True:
+ api_data = self.load_json(
+ "https://%s/gate/status" % server,
+ get={"hash": torrent_id}
+ )
- zip_status_url = m.group(1)
- download_id = m.group(2)
+ if api_data["error"] == 404 and api_data["result"] == "not found +init":
+ pass
- m = re.search(
- r'\'(http://\w+.zbigz.com/z/%s/.+?)\'' %
- download_id, self.data)
- if m is None:
- self.fail("Zip download URL not found")
+ elif api_data["error"]:
+ self.exit_error(api_data["result"])
- download_url = m.group(1)
+ if api_data.get("has_metadata", False):
+ self.pyfile.name = api_data["name"]
+ self.pyfile.size = api_data["size"]
+ break
- self.pyfile.setCustomStatus("zip")
- self.pyfile.setProgress(0)
+ self.sleep(5)
- while True:
- json_data = self.jquery_call(zip_status_url, file_id, call_id)
- if json_data is None:
- self.fail("Unexpected jQuery response")
+ while True:
+ api_data = self.load_json(
+ "https://%s/gate/status" % server,
+ get={"hash": torrent_id}
+ )
+ if api_data["error"]:
+ self.exit_error(api_data["result"])
+
+ progress = api_data["progress"]
+ self.pyfile.setProgress(progress)
+ if progress >= 100:
+ break
- if 'faultString' in json_data:
- self.fail(json_data['faultString'])
+ self.sleep(5)
- progress = int(json_data['proc'])
+ self.pyfile.setProgress(100)
- self.pyfile.setProgress(progress)
+ def download_from_server(self, torrent_id, server):
+ api_data = self.api_call("storage/download/%s" % torrent_id)
+ if api_data['error']:
+ self.fail(api_data['error_msg'])
- if progress == 100:
+ if "state" in api_data:
+ zip_status_url = urlparse.urljoin("https://", api_data["state"])
+ self.pyfile.setCustomStatus("zipping")
+ self.pyfile.setProgress(0)
+
+ while True:
+ zip_status = self.load_json(zip_status_url)
+ progress = zip_status["proc"]
+ self.pyfile.setProgress(progress)
+ if progress >= 100:
break
- self.sleep(5)
+ self.sleep(2)
+ self.pyfile.setProgress(100)
+
+ download_url = api_data["link"]
self.download(download_url)
- self.load("http://m.zbigz.com/delete.php?hash=%s" % file_id)
+ def process(self, pyfile):
+ self.tmp_file = None
+ torrent_id, server = self.send_request_to_server()
+ self.wait_for_server_dl(torrent_id, server)
+ self.download_from_server(torrent_id, server)
+
+ if self.tmp_file:
+ os.remove(self.tmp_file)
diff --git a/module/plugins/hoster/ZippyshareCom.py b/module/plugins/hoster/ZippyshareCom.py
deleted file mode 100644
index 4476a4aa34..0000000000
--- a/module/plugins/hoster/ZippyshareCom.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-import BeautifulSoup
-
-from ..captcha.ReCaptcha import ReCaptcha
-from ..internal.SimpleHoster import SimpleHoster
-
-
-class ZippyshareCom(SimpleHoster):
- __name__ = "ZippyshareCom"
- __type__ = "hoster"
- __version__ = "0.98"
- __status__ = "testing"
-
- __pattern__ = r'https?://(?Pwww\d{0,3}\.zippyshare\.com)/(?:[vd]/|view\.jsp.*key=)(?P[\w^_]+)'
- __config__ = [("activated", "bool", "Activated", True),
- ("use_premium", "bool", "Use premium account if available", True),
- ("fallback", "bool", "Fallback to free download if premium fails", True),
- ("chk_filesize", "bool", "Check file size", True),
- ("max_wait", "int", "Reconnect if waiting time is greater than minutes", 10)]
-
- __description__ = """Zippyshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("sebdelsol", "seb.morin@gmail.com"),
- ("GammaC0de", "nitzo2001[AT]yahoo[DOT]com")]
-
- COOKIES = [("zippyshare.com", "ziplocale", "en")]
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://\g/v/\g/file.html')]
-
- NAME_PATTERN = r'(?:Zippyshare.com - |"/)(?P[^/]+)(?:|";)'
- SIZE_PATTERN = r'>Size:.+?">(?P[\d.,]+) (?P[\w^_]+)'
- OFFLINE_PATTERN = r'does not exist (anymore )?on this server<'
- TEMP_OFFLINE_PATTERN = r'^unmatchable$'
-
- LINK_PATTERN = r"document.location = '(.+?)'"
-
- def setup(self):
- self.chunk_limit = -1
- self.multiDL = True
- self.resume_download = True
-
- def handle_free(self, pyfile):
- self.captcha = ReCaptcha(pyfile)
- captcha_key = self.captcha.detect_key()
-
- if captcha_key:
- try:
- self.link = re.search(self.LINK_PATTERN, self.data)
- self.captcha.challenge()
-
- except Exception, e:
- self.error(e)
-
- else:
- self.link = self.fixurl(self.get_link())
- if ".com/pd/" in self.link:
- self.load(self.link)
- self.link = self.link.replace(".com/pd/", ".com/d/")
-
- if self.link and pyfile.name == "file.html":
- pyfile.name = urllib.unquote(self.link.split('/')[-1])
-
- def get_link(self):
- #: Get all the scripts inside the html body
- soup = BeautifulSoup.BeautifulSoup(self.data)
- scripts = [
- s.getText() for s in soup.body.findAll(
- 'script',
- type='text/javascript') if "('dlbutton').href =" in s.getText()]
-
- #: Emulate a document in JS
- inits = ['''
- var document = {}
- document.getElementById = function(x) {
- if (!this.hasOwnProperty(x)) {
- this[x] = {getAttribute : function(x) { return this[x] } }
- }
- return this[x]
- }
- ''']
-
- #: inits is meant to be populated with the initialization of all the DOM elements found in the scripts
- eltRE = r'getElementById\([\'"](.+?)[\'"]\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?'
- for m in re.findall(eltRE, ' '.join(scripts)):
- JSid, JSattr = m[0], m[3]
- values = filter(None, (elt.get(JSattr, None)
- for elt in soup.findAll(id=JSid)))
- if values:
- inits.append('document.getElementById("%s")["%s"] = "%s"' % (
- JSid, JSattr, values[-1]))
-
- #: Add try/catch in JS to handle deliberate errors
- scripts = ['\n'.join(('try{', script, '} catch(err){}'))
- for script in scripts]
-
- #: Get the file's url by evaluating all the scripts
- scripts = inits + scripts + ['document.dlbutton.href']
-
- return self.js.eval('\n'.join(scripts))
diff --git a/module/plugins/internal/Account.py b/module/plugins/internal/Account.py
index 19ec2f8116..de87ee5e4c 100644
--- a/module/plugins/internal/Account.py
+++ b/module/plugins/internal/Account.py
@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
-import random
+import copy
import threading
import time
-from .misc import (Periodical, compare_time, decode, isiterable, lock,
- parse_size)
+from .misc import Periodical, compare_time, decode, isiterable, lock, parse_size
from .Plugin import Plugin, Skip
class Account(Plugin):
__name__ = "Account"
__type__ = "account"
- __version__ = "0.84"
+ __version__ = "0.91"
__status__ = "stable"
__description__ = """Base account plugin"""
@@ -61,8 +60,7 @@ def logged(self):
def premium(self):
return bool(self.get_data('premium'))
- def _log(self, level, plugintype, pluginname, messages):
- log = getattr(self.pyload.log, level)
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
msg = u" | ".join(decode(a).strip() for a in messages if a)
#: Hide any user/password
@@ -76,14 +74,11 @@ def _log(self, level, plugintype, pluginname, messages):
except Exception:
pass
- log("%(plugintype)s %(pluginname)s: %(msg)s" %
- {'plugintype': plugintype.upper(),
- 'pluginname': pluginname,
- 'msg': msg})
+ return Plugin._log(self, level, plugintype, pluginname, (msg, ), tbframe=tbframe)
def setup(self):
"""
- Setup for enviroment and other things, called before logging (possibly more than one time)
+ Setup for environment and other things, called before logging (possibly more than one time)
"""
pass
@@ -97,16 +92,18 @@ def signin(self, user, password, data):
pass
def login(self):
- if not self.req:
+ self.clean()
+ self.sync()
+
+ self.info["login"]["stats"][0] += 1
+ if self.info["login"]["stats"][0] == 1:
self.log_info(_("Login user `%s`...") % self.user)
else:
self.log_info(_("Relogin user `%s`...") % self.user)
- self.clean()
self.req = self.pyload.requestFactory.getRequest(
self.classname, self.user)
- self.sync()
self.setup()
timestamp = time.time()
@@ -140,6 +137,15 @@ def login(self):
return bool(self.info['login']['valid'])
+
+ def logout(self):
+ """
+ Invalidate the account timestamp so relogin will be forced next time.
+ """
+ self.sync()
+ self.info["login"]["timestamp"] = 0
+ self.syncback()
+
#@TODO: Recheck in 0.4.10
def syncback(self):
"""
@@ -166,7 +172,7 @@ def sync(self, reverse=False):
d = {'login': {}, 'data': {}}
for k, v in u.items():
- if k in ('password', 'timestamp', 'valid'):
+ if k in ('password', 'timestamp', "stats", 'valid'):
d['login'][k] = v
else:
d['data'][k] = v
@@ -192,8 +198,6 @@ def get_info(self, refresh=True):
Retrieve account infos for an user, do **not** overwrite this method!
just use it to retrieve infos in hoster plugins. see `grab_info`
- :param user: username
- :param relogin: reloads cached account information
:return: dictionary with information
"""
if not self.logged:
@@ -249,7 +253,8 @@ def grab_info(self, user, password, data):
and retrieving account information for user
:param user:
- :param req: `Request` instance
+ :param password:
+ :param data:
:return:
"""
pass
@@ -303,6 +308,7 @@ def add(self, user, password=None, options={}):
'password': password or "",
'plugin': self.pyload.accountManager.getAccountPlugin(self.classname),
'premium': None,
+ "stats": [0, 0], #: login_count, chosen_time
'timestamp': 0,
'trafficleft': None,
'type': self.__name__,
@@ -339,21 +345,30 @@ def updateAccounts(self, user, password=None, options={}):
def removeAccount(self, user):
self.log_info(_("Removing user `%s`...") % user)
self.accounts.pop(user, None)
+ # self.pyload.requestFactory.remove_cookie_jar(self.classname, user)
+ self.pyload.requestFactory.cookiejars.pop((self.classname, user), None)
if user is self.user:
self.choose()
@lock
def select(self):
+ def hide(secret):
+ hidden = secret[:3] + "*******"
+ return hidden
+
free_accounts = {}
premium_accounts = {}
for user in self.accounts:
- info = self.accounts[user]['plugin'].get_info()
- data = info['data']
+ if not self.accounts[user]["plugin"].choose(user):
+ continue
+ info = self.accounts[user]["plugin"].get_info()
if not info['login']['valid']:
continue
+ data = info["data"]
+
if data['options'].get('time'):
time_data = ""
try:
@@ -365,41 +380,37 @@ def select(self):
except Exception:
self.log_warning(_("Invalid time format `%s` for account `%s`, use 1:22-3:44")
- % (user, time_data))
+ % (hide(user), time_data))
if data['trafficleft'] == 0:
self.log_warning(
_("Not using account `%s` because the account has no traffic left") %
- user)
+ hide(user))
continue
if time.time() > data['validuntil'] > 0:
self.log_warning(
_("Not using account `%s` because the account has expired") %
- user)
+ hide(user))
continue
if data['premium']:
- premium_accounts[user] = info
+ premium_accounts[user] = copy.copy(info)
else:
- free_accounts[user] = info
+ free_accounts[user] = copy.copy(info)
account_list = (premium_accounts or free_accounts).items()
if not account_list:
return None, None
- validuntil_list = [(user, info) for user, info in account_list
- if info['data']['validuntil']]
+ #: Choose the oldest used account
+ chosen_account = sorted(account_list, key=lambda x: x[1]["login"]["stats"][1])[0]
+ self.accounts[chosen_account[0]]["stats"][1] = time.time()
- if not validuntil_list:
- # @TODO: Random account?! Rewrite in 0.4.10
- return random.choice(account_list)
-
- return sorted(validuntil_list,
- key=lambda a: a[1]['data']['validuntil'],
- reverse=True)[0]
+ self.log_debug("Using account %s" % (hide(chosen_account[0])))
+ return chosen_account
@lock
def choose(self, user=None):
@@ -415,32 +426,30 @@ def choose(self, user=None):
user, _("User does not exists"))
return False
- if self.req and user == self.user:
- return True
-
- self.user = user
- self.info.clear()
- self.clean()
+ else:
+ if self.req and user == self.user:
+ return True
if user is None:
return False
else:
+ self.user = user
+ self.info.clear()
+ self.req.close()
+ self.req = self.pyload.requestFactory.getRequest(self.classname, self.user)
+
if not self.logged:
self.relogin()
- else:
- self.req = self.pyload.requestFactory.getRequest(
- self.classname, self.user)
return True
###########################################################################
- def parse_traffic(self, size, unit=None): # @NOTE: Returns kilobytes only in 0.4.9
+ def parse_traffic(self, size, unit=None): #: returns bytes
self.log_debug("Size: %s" % size,
"Unit: %s" % (unit or "N/D"))
- # @TODO: Remove `/ 1024` in 0.4.10
- return parse_size(size, unit or "byte") / 1024
+ return parse_size(size, unit)
def fail_login(self, msg=_("Login handshake has failed")):
return self.fail(msg)
diff --git a/module/plugins/internal/Addon.py b/module/plugins/internal/Addon.py
index f7228a48ac..08cad44064 100644
--- a/module/plugins/internal/Addon.py
+++ b/module/plugins/internal/Addon.py
@@ -9,7 +9,7 @@
class Addon(Plugin):
__name__ = "Addon"
__type__ = "hook" # @TODO: Change to `addon` in 0.4.10
- __version__ = "0.55"
+ __version__ = "0.56"
__status__ = "stable"
__threaded__ = [] # @TODO: Remove in 0.4.10
@@ -46,9 +46,9 @@ def activated(self):
return self.config.get('activated')
#@TODO: Remove in 0.4.10
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
plugintype = "addon" if plugintype == "hook" else plugintype
- return Plugin._log(self, level, plugintype, pluginname, messages)
+ return Plugin._log(self, level, plugintype, pluginname, messages, tbframe=tbframe)
#@TODO: Remove in 0.4.10
def _init_events(self):
diff --git a/module/plugins/internal/Base.py b/module/plugins/internal/Base.py
index 7159674df4..8f240d4077 100644
--- a/module/plugins/internal/Base.py
+++ b/module/plugins/internal/Base.py
@@ -6,8 +6,8 @@
import urlparse
from .Captcha import Captcha
-from .misc import (decode, encode, fixurl, format_size, format_time,
- parse_html_form, parse_name, replace_patterns)
+from .misc import (
+ decode, encode, fixurl, format_exc, format_size, format_time, parse_html_form, parse_name, replace_patterns)
from .Plugin import Abort, Fail, Plugin, Reconnect, Retry, Skip
@@ -26,7 +26,7 @@ def parse_fileInfo(klass, url="", html=""):
class Base(Plugin):
__name__ = "Base"
__type__ = "base"
- __version__ = "0.34"
+ __version__ = "0.41"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -39,18 +39,17 @@ class Base(Plugin):
URL_REPLACEMENTS = []
- @classmethod
- def get_info(cls, url="", html=""):
+ def get_info(self, url="", html=""):
url = fixurl(url, unquote=True)
info = {'name': parse_name(url),
'hash': {},
'pattern': {},
'size': 0,
'status': 7 if url else 8,
- 'url': replace_patterns(url, cls.URL_REPLACEMENTS)}
+ 'url': replace_patterns(url, self.URL_REPLACEMENTS)}
try:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict()
+ info['pattern'] = re.match(self.__pattern__, url).groupdict()
except Exception:
pass
@@ -98,23 +97,24 @@ def __init__(self, pyfile):
self.init_base()
self.init()
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
log = getattr(self.pyload.log, level)
msg = u" | ".join(decode(a).strip() for a in messages if a)
#: Hide any user/password
try:
- msg = msg.replace(
- self.account.user, self.account.user[:3] + "*******")
+ msg = msg.replace(self.account.user, self.account.user[:3] + "*******")
except Exception:
pass
try:
- msg = msg.replace(
- self.account.info['login']['password'], "**********")
+ msg = msg.replace(self.account.info['login']['password'], "**********")
except Exception:
pass
+ if tbframe:
+ msg += "\n" + format_exc(tbframe)
+
log("%(plugintype)s %(pluginname)s[%(id)s]: %(msg)s" %
{'plugintype': plugintype.upper(),
'pluginname': pluginname,
@@ -152,8 +152,7 @@ def _setup(self):
pass
if self.account:
- self.req = self.pyload.requestFactory.getRequest(
- self.classname, self.account.user)
+ self.req = self.pyload.requestFactory.getRequest(self.classname, self.account.user)
# @NOTE: Avoid one unnecessary get_info call by `self.account.premium` here
self.premium = self.account.info['data']['premium']
else:
@@ -169,8 +168,7 @@ def _setup(self):
def load_account(self):
if not self.account:
- self.account = self.pyload.accountManager.getAccountPlugin(
- self.classname)
+ self.account = self.pyload.accountManager.getAccountPlugin(self.classname)
if not self.account:
self.account = False
@@ -202,9 +200,7 @@ def _update_size(self):
size = self.pyfile.size
if size:
- self.log_info(
- _("Link size: %s (%s bytes)") %
- (format_size(size), size))
+ self.log_info(_("Link size: %s (%s bytes)") % (format_size(size), size))
else:
self.log_info(_("Link size: N/D"))
@@ -223,7 +219,7 @@ def grab_info(self):
self.log_info(_("Grabbing link info..."))
old_info = dict(self.info)
- new_info = self.get_info(self.pyfile.url, self.data)
+ new_info = self.get_info(replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS), self.data)
self.info.update(new_info)
@@ -334,17 +330,17 @@ def wait(self, seconds=None, reconnect=None):
if seconds is not None:
self.set_wait(seconds)
- if reconnect is None:
- reconnect = (seconds > self.config.get('max_wait', 10) * 60)
-
- self.set_reconnect(reconnect)
-
wait_time = self.pyfile.waitUntil - time.time()
if wait_time < 1:
self.log_warning(_("Invalid wait time interval"))
return
+ if reconnect is None:
+ reconnect = (wait_time > self.config.get('max_wait', 10) * 60)
+
+ self.set_reconnect(reconnect)
+
self.waiting = True
status = self.pyfile.status # @NOTE: Recheck in 0.4.10
@@ -396,14 +392,12 @@ def fail(self, msg=""):
if msg:
self.pyfile.error = msg
else:
- msg = self.pyfile.error or self.info.get(
- 'error') or self.pyfile.getStatusName()
+ msg = self.pyfile.error or self.info.get('error') or self.pyfile.getStatusName()
- raise Fail(encode(msg)) # @TODO: Remove `encode` in 0.4.10
+ raise Fail(decode(msg)) # @TODO: Remove `encode` in 0.4.10
def error(self, msg="", type=_("Parse")):
- type = _("%s error") % type.strip(
- ).capitalize() if type else _("Unknown")
+ type = _("%s error") % type.strip().capitalize() if type else _("Unknown")
msg = _("%(type)s: %(msg)s | Plugin may be out of date"
% {'type': type, 'msg': msg or self.pyfile.error})
@@ -434,8 +428,7 @@ def temp_offline(self, msg=""):
def restart(self, msg="", premium=True):
if not msg:
- msg = _("Restart plugin") if premium else _(
- "Fallback to free processing")
+ msg = _("Restart plugin") if premium else _("Fallback to free processing")
if not premium:
if self.premium:
@@ -443,8 +436,6 @@ def restart(self, msg="", premium=True):
else:
self.fail("%s | %s" % (msg, _("Url was already processed as free")))
- self.req.clearCookies()
-
raise Retry(encode(msg)) # @TODO: Remove `encode` in 0.4.10
def retry(self, attemps=5, wait=1, msg="", msgfail=_("Max retries reached")):
@@ -478,16 +469,15 @@ def retry_captcha(self, attemps=10, wait=1, msg="", msgfail=_("Max captcha retri
self.captcha.invalid(msg)
self.retry(attemps, wait, msg=_("Retry Captcha"), msgfail=msgfail)
- def fixurl(self, url, baseurl=None, unquote=True):
- url = fixurl(url, unquote=True)
- baseurl = fixurl(baseurl or self.pyfile.url, unquote=True)
+ def fixurl(self, url, baseurl=None):
+ baseurl = baseurl or self.pyfile.url
if not urlparse.urlparse(url).scheme:
url_p = urlparse.urlparse(baseurl)
baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
url = urlparse.urljoin(baseurl, url)
- return fixurl(url, unquote)
+ return url
def load(self, *args, **kwargs):
self.check_status()
diff --git a/module/plugins/internal/Captcha.py b/module/plugins/internal/Captcha.py
index a979c06963..61fb05b48f 100644
--- a/module/plugins/internal/Captcha.py
+++ b/module/plugins/internal/Captcha.py
@@ -2,16 +2,18 @@
from __future__ import with_statement
+import base64
import os
import time
from .Plugin import Plugin
+from .misc import fsjoin
class Captcha(Plugin):
__name__ = "Captcha"
__type__ = "captcha"
- __version__ = "0.55"
+ __version__ = "0.62"
__status__ = "stable"
__description__ = """Base anti-captcha plugin"""
@@ -26,10 +28,9 @@ def __init__(self, pyfile):
self.init()
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
messages = (self.__name__,) + messages
- return self.pyfile.plugin._log(
- level, plugintype, self.pyfile.plugin.__name__, messages)
+ return self.pyfile.plugin._log(level, plugintype, self.pyfile.plugin.__name__, messages, tbframe=tbframe)
def recognize(self, image):
"""
@@ -69,15 +70,15 @@ def decrypt_image(self, img, input_type='jpg',
result = None
time_ref = ("%.2f" % time.time())[-6:].replace(".", "")
- with open(os.path.join("tmp", "captcha_image_%s_%s.%s" % (self.pyfile.plugin.__name__, time_ref, input_type)), "wb") as img_f:
+ img_path = fsjoin("tmp", "captcha_image_%s_%s.%s" % (self.pyfile.plugin.__name__, time_ref, input_type))
+ with open(img_path, "wb") as img_f:
img_f.write(img)
if ocr:
self.log_info(_("Using OCR to decrypt captcha..."))
if isinstance(ocr, basestring):
- _OCR = self.pyload.pluginManager.loadClass(
- "captcha", ocr) #: Rename `captcha` to `ocr` in 0.4.10
+ _OCR = self.pyload.pluginManager.loadClass("captcha", ocr) #: Rename `captcha` to `ocr` in 0.4.10
result = _OCR(self.pyfile).recognize(img_f.name)
else:
result = self.recognize(img_f.name)
@@ -87,15 +88,18 @@ def decrypt_image(self, img, input_type='jpg',
if not result:
captchaManager = self.pyload.captchaManager
+ timeout = max(timeout, 50)
try:
- self.task = captchaManager.newTask(
- img, input_type, img_f.name, output_type)
+ params = {'src': "data:image/%s;base64,%s" % (input_type, base64.standard_b64encode(img)),
+ 'file': img_f.name,
+ 'captcha_plugin': self.__name__,
+ 'plugin': self.pyfile.plugin.__name__,
+ "url": self.pyfile.url}
+ self.task = captchaManager.newTask(input_type, params, output_type)
- captchaManager.handleCaptcha(self.task)
+ captchaManager.handleCaptcha(self.task, timeout)
- # @TODO: Move to `CaptchaManager` in 0.4.10
- self.task.setWaiting(max(timeout, 50))
while self.task.isWaiting():
self.pyfile.plugin.check_status()
time.sleep(1)
@@ -107,8 +111,7 @@ def decrypt_image(self, img, input_type='jpg',
if self.task.error:
if not self.task.handler and not self.pyload.isClientConnected():
- self.log_warning(
- _("No Client connected for captcha decrypting"))
+ self.log_warning(_("No Client connected for captcha decrypting"))
self.fail(_("No Client connected for captcha decrypting"))
else:
self.pyfile.plugin.retry_captcha(msg=self.task.error)
@@ -117,19 +120,89 @@ def decrypt_image(self, img, input_type='jpg',
self.log_info(_("Captcha result: `%s`") % (result,))
else:
- self.pyfile.plugin.retry_captcha(
- msg=_("No captcha result obtained in appropriate timing"))
+ self.pyfile.plugin.retry_captcha(msg=_("No captcha result obtained in appropriate timing (%ss)") % timeout)
- if not self.pyload.debug:
- self.remove(img_f.name, trash=False)
+ self.remove(img_f.name, trash=False)
return result
- def invalid(self):
+ def decrypt_interactive(self, params={}, timeout=120):
+ captchaManager = self.pyload.captchaManager
+ timeout = max(timeout, 50)
+
+ try:
+ params.update({'captcha_plugin': self.__name__,
+ 'plugin': self.pyfile.plugin.__name__,
+ "url": self.pyfile.url})
+ self.task = captchaManager.newTask("interactive", params, "interactive")
+
+ captchaManager.handleCaptcha(self.task, timeout)
+
+ while self.task.isWaiting():
+ self.pyfile.plugin.check_status()
+ time.sleep(1)
+
+ finally:
+ captchaManager.removeTask(self.task)
+
+ result = self.task.result
+
+ if self.task.error:
+ if not self.task.handler and not self.pyload.isClientConnected():
+ self.log_warning(_("No Client connected for captcha decrypting"))
+ self.fail(_("No Client connected for captcha decrypting"))
+ else:
+ self.pyfile.plugin.retry_captcha(msg=self.task.error)
+
+ elif self.task.result:
+ self.log_info(_("Captcha result: `%s`") % (result,))
+
+ else:
+ self.pyfile.plugin.retry_captcha(msg=_("No captcha result obtained in appropriate timing (%ss)") % timeout)
+
+ return result
+
+ def decrypt_invisible(self, params={}, timeout=120):
+ captchaManager = self.pyload.captchaManager
+ timeout = max(timeout, 50)
+
+ try:
+ params.update({'captcha_plugin': self.__name__,
+ 'plugin': self.pyfile.plugin.__name__,
+ "url": self.pyfile.url})
+ self.task = captchaManager.newTask("invisible", params, "invisible")
+
+ captchaManager.handleCaptcha(self.task, timeout)
+
+ while self.task.isWaiting():
+ self.pyfile.plugin.check_status()
+ time.sleep(1)
+
+ finally:
+ captchaManager.removeTask(self.task)
+
+ result = self.task.result
+
+ if self.task.error:
+ if not self.task.handler and not self.pyload.isClientConnected():
+ self.log_warning(_("No Client connected for captcha decrypting"))
+ self.fail(_("No Client connected for captcha decrypting"))
+ else:
+ self.pyfile.plugin.retry_captcha(msg=self.task.error)
+
+ elif self.task.result:
+ self.log_info(_("Captcha result: `%s`") % (result,))
+
+ else:
+ self.pyfile.plugin.retry_captcha(msg=_("No captcha result obtained in appropriate timing (%ss)") % timeout)
+
+ return result
+
+ def invalid(self, msg=""):
if not self.task:
return
- self.log_warning(_("Invalid captcha"), self.task.result)
+ self.log_warning(_("Invalid captcha"), msg, self.task.result)
self.task.invalid()
self.task = None
diff --git a/module/plugins/internal/Container.py b/module/plugins/internal/Container.py
index 42f391e05a..14cf7c5c17 100644
--- a/module/plugins/internal/Container.py
+++ b/module/plugins/internal/Container.py
@@ -6,13 +6,13 @@
import urlparse
from .Crypter import Crypter
-from .misc import encode, exists
+from .misc import encode, exists, fs_encode, safejoin
class Container(Crypter):
__name__ = "Container"
__type__ = "container"
- __version__ = "0.14"
+ __version__ = "0.15"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -58,14 +58,11 @@ def _make_tmpfile(self):
content = self.load(self.pyfile.url)
self.pyfile.name = "tmp_" + self.pyfile.name
- self.pyfile.url = os.path.join(
- self.pyload.config.get(
- 'general',
- 'download_folder'),
- self.pyfile.name)
+ self.pyfile.url = safejoin(self.pyload.config.get('general', 'download_folder'),
+ self.pyfile.name)
try:
- with open(self.pyfile.url, "wb") as f:
+ with open(fs_encode(self.pyfile.url), "wb") as f:
f.write(encode(content))
except IOError, e:
diff --git a/module/plugins/internal/Crypter.py b/module/plugins/internal/Crypter.py
index a1b12d4568..411c0f8bc3 100644
--- a/module/plugins/internal/Crypter.py
+++ b/module/plugins/internal/Crypter.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
from .Base import Base
-from .misc import parse_name, safename
+from .misc import decode, parse_name, safepath
class Crypter(Base):
__name__ = "Crypter"
__type__ = "crypter"
- __version__ = "0.20"
+ __version__ = "0.22"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -49,15 +49,15 @@ def _generate_packages(self):
"""
Generate new packages from self.links
"""
+ links = map(self.fixurl, self.links)
name = self.info['pattern'].get("N")
if name is None:
- links = map(self.fixurl, self.links)
pdict = self.pyload.api.generatePackages(links)
packages = [(_name, _links, parse_name(_name))
for _name, _links in pdict.items()]
else:
- packages = [(name, self.links, parse_name(name))]
+ packages = [(name, links, parse_name(name))]
self.packages.extend(packages)
@@ -72,12 +72,12 @@ def _create_packages(self):
folder_per_package = self.config.get('folder_per_package', "Default")
if folder_per_package == "Default":
- folder_per_package = self.pyload.config.get(
- 'general', 'folder_per_package')
+ folder_per_package = self.pyload.config.get('general', 'folder_per_package')
else:
folder_per_package = folder_per_package == "Yes"
for name, links, folder in self.packages:
+ name = decode(name)
self.log_info(_("Create package: %s") % name,
_("%d links") % len(links))
@@ -87,12 +87,10 @@ def _create_packages(self):
pid = self.pyload.api.addPackage(name, links, pack_queue)
if pack_password:
- self.pyload.api.setPackageData(
- pid, {'password': pack_password})
+ self.pyload.api.setPackageData(pid, {'password': pack_password})
#: Workaround to do not break API addPackage method
- set_folder = lambda x: self.pyload.api.setPackageData(
- pid, {'folder': safename(x or "")})
+ set_folder = lambda x: self.pyload.api.setPackageData(pid, {'folder': safepath(x or "")})
if not folder_per_package:
folder = pack_folder
diff --git a/module/plugins/internal/DeadCrypter.py b/module/plugins/internal/DeadCrypter.py
index fcb9ceaad2..df4cfaaa8c 100644
--- a/module/plugins/internal/DeadCrypter.py
+++ b/module/plugins/internal/DeadCrypter.py
@@ -6,7 +6,7 @@
class DeadCrypter(Crypter):
__name__ = "DeadCrypter"
__type__ = "crypter"
- __version__ = "0.14"
+ __version__ = "0.15"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -16,9 +16,8 @@ class DeadCrypter(Crypter):
__license__ = "GPLv3"
__authors__ = [("stickell", "l.stickell@yahoo.it")]
- @classmethod
- def get_info(cls, *args, **kwargs):
- info = super(DeadCrypter, cls).get_info(*args, **kwargs)
+ def get_info(self, *args, **kwargs):
+ info = super(DeadCrypter, self).get_info(*args, **kwargs)
info['status'] = 1
return info
diff --git a/module/plugins/internal/DeadHoster.py b/module/plugins/internal/DeadHoster.py
index 7aad23806b..a45eba35da 100644
--- a/module/plugins/internal/DeadHoster.py
+++ b/module/plugins/internal/DeadHoster.py
@@ -6,7 +6,7 @@
class DeadHoster(Hoster):
__name__ = "DeadHoster"
__type__ = "hoster"
- __version__ = "0.24"
+ __version__ = "0.25"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -16,9 +16,8 @@ class DeadHoster(Hoster):
__license__ = "GPLv3"
__authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
- @classmethod
- def get_info(cls, *args, **kwargs):
- info = super(DeadHoster, cls).get_info(*args, **kwargs)
+ def get_info(self, *args, **kwargs):
+ info = super(DeadHoster, self).get_info(*args, **kwargs)
info['status'] = 1
return info
diff --git a/module/plugins/internal/Extractor.py b/module/plugins/internal/Extractor.py
index 473e5f7b39..22eb2846c7 100644
--- a/module/plugins/internal/Extractor.py
+++ b/module/plugins/internal/Extractor.py
@@ -3,7 +3,7 @@
import os
import re
-from .misc import encode
+from .misc import fs_encode
from .Plugin import Plugin
@@ -22,7 +22,7 @@ class PasswordError(Exception):
class Extractor(Plugin):
__name__ = "Extractor"
__type__ = "extractor"
- __version__ = "0.48"
+ __version__ = "0.50"
__status__ = "stable"
__description__ = """Base extractor plugin"""
@@ -51,7 +51,7 @@ def archivetype(cls, filename):
return ext
elif isinstance(ext, tuple):
- if re.search("\." + ext[1] + "$", name):
+ if re.search(r"\." + ext[1] + "$", name):
return ext[0]
return None
@@ -133,11 +133,11 @@ def __init__(self, pyfile, filename, out,
@property
def target(self):
- return encode(self.filename)
+ return fs_encode(self.filename)
@property
def dest(self):
- return encode(self.out)
+ return fs_encode(self.out)
def verify(self, password=None):
"""
diff --git a/module/plugins/internal/HjSplit.py b/module/plugins/internal/HjSplit.py
new file mode 100644
index 0000000000..b61cb9392d
--- /dev/null
+++ b/module/plugins/internal/HjSplit.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+
+from .Extractor import ArchiveError, Extractor
+from .misc import exists, fsjoin
+
+
+class HjSplit(Extractor):
+ __name__ = "HjSplit"
+ __type__ = "extractor"
+ __version__ = "0.02"
+ __status__ = "testing"
+
+ __description__ = """HJSPLIT extractor plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("GammaC0de", "nitzo2001[AT]yahoo[DOT]com")]
+
+ VERSION = __version__
+
+ EXTENSIONS = [("001", r'(? traffic
# def check_size(self, file_size, size_tolerance=1024, delete=False):
@@ -506,11 +502,12 @@ def check_duplicates(self):
else:
# Same file exists but it does not belongs to our pack, add a trailing counter
- m = re.match(r'(.+?)(?: \((\d+)\))?(\..+)?$', self.pyfile.name)
+ name, ext = os.path.splitext(self.pyfile.name)
+ m = re.match(r'(.+?)(?:\((\d+)\))?$', name)
dl_n = int(m.group(2) or "0")
while True:
- name = "%s (%s)%s" % (m.group(1), dl_n + 1, m.group(3) or "")
+ name = "%s (%s)%s" % (m.group(1), dl_n + 1, ext)
dl_file = fsjoin(dl_folder, pack_folder, name)
if not exists(dl_file):
break
diff --git a/module/plugins/internal/MultiAccount.py b/module/plugins/internal/MultiAccount.py
index 73df004a6b..46db375735 100644
--- a/module/plugins/internal/MultiAccount.py
+++ b/module/plugins/internal/MultiAccount.py
@@ -10,7 +10,7 @@
class MultiAccount(Account):
__name__ = "MultiAccount"
__type__ = "account"
- __version__ = "0.22"
+ __version__ = "0.24"
__status__ = "testing"
__config__ = [("activated", "bool", "Activated", True),
@@ -23,7 +23,8 @@ class MultiAccount(Account):
__authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
("GammaC0de", "nitzo2001[AT]yahoo[DOT]com")]
- DOMAIN_REPLACEMENTS = [(r'180upload\.com', "hundredeightyupload.com"),
+ DOMAIN_REPLACEMENTS = [(r"ddl\.to", "ddownload.com"),
+ (r'180upload\.com', "hundredeightyupload.com"),
(r'bayfiles\.net', "bayfiles.com"),
(r'cloudnator\.com', "shragle.com"),
(r'dfiles\.eu', "depositfiles.com"),
@@ -73,8 +74,7 @@ def init_plugin(self):
self.pyload.hookManager.addEvent("plugin_updated", self.plugins_updated)
- interval = self.config.get('mh_interval', 12) * 60 * 60
- self.periodical.start(interval, threaded=True, delay=2)
+ self.periodical.start(3, threaded=True)
else:
self.log_warning(_("Multi-hoster feature will be deactivated due missing plugin reference"))
@@ -253,7 +253,14 @@ def unload_plugin(self, plugin):
def reactivate(self, refresh=False):
reloading = self.info['data'].get('hosters') is not None
- if not self.info['login']['valid']:
+ if self.info['login']['valid'] is None:
+ return
+
+ else:
+ interval = self.config.get('mh_interval', 12) * 60 * 60
+ self.periodical.set_interval(interval)
+
+ if self.info['login']['valid'] is False:
self.fail_count += 1
if self.fail_count < 3:
if reloading:
diff --git a/module/plugins/internal/MultiCrypter.py b/module/plugins/internal/MultiCrypter.py
index 6a84d546d4..c8cbd2f1a2 100644
--- a/module/plugins/internal/MultiCrypter.py
+++ b/module/plugins/internal/MultiCrypter.py
@@ -6,7 +6,7 @@
class MultiCrypter(SimpleCrypter):
__name__ = "MultiCrypter"
__type__ = "hoster"
- __version__ = "0.10"
+ __version__ = "0.11"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -22,6 +22,6 @@ def init(self):
self.PLUGIN_NAME = self.pyload.pluginManager.crypterPlugins.get(self.classname)[
'name']
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
messages = (self.PLUGIN_NAME,) + messages
- return SimpleCrypter._log(self, level, plugintype, pluginname, messages)
+ return SimpleCrypter._log(self, level, plugintype, pluginname, messages, tbframe=tbframe)
diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py
index d94fe62728..2047e127f9 100644
--- a/module/plugins/internal/MultiHoster.py
+++ b/module/plugins/internal/MultiHoster.py
@@ -2,6 +2,8 @@
import re
+from .Base import Base
+from .misc import replace_patterns
from .Plugin import Fail
from .SimpleHoster import SimpleHoster
@@ -9,7 +11,7 @@
class MultiHoster(SimpleHoster):
__name__ = "MultiHoster"
__type__ = "hoster"
- __version__ = "0.67"
+ __version__ = "0.72"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -29,13 +31,17 @@ class MultiHoster(SimpleHoster):
TEMP_OFFLINE_PATTERN = r'^unmatchable$'
LEECH_HOSTER = False
+ DIRECT_LINK = None
+
+ def get_info(self, url="", html=""):
+ return super(SimpleHoster, self).get_info(url, html)
def init(self):
self.PLUGIN_NAME = self.pyload.pluginManager.hosterPlugins.get(self.classname)['name']
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
messages = (self.PLUGIN_NAME,) + messages
- return SimpleHoster._log(self, level, plugintype, pluginname, messages)
+ return SimpleHoster._log(self, level, plugintype, pluginname, messages, tbframe=tbframe)
def setup(self):
self.no_fallback = True
@@ -43,18 +49,19 @@ def setup(self):
self.multiDL = bool(self.account)
self.resume_download = self.premium
- #@TODO: Recheck in 0.4.10
- def setup_base(self):
- klass = self.pyload.pluginManager.loadClass("hoster", self.classname)
- self.get_info = klass.get_info
-
- SimpleHoster.setup_base(self)
+ def _preload(self):
+ pass
def _prepare(self):
SimpleHoster._prepare(self)
+ if self.pyfile.pluginname != self.__name__:
+ overwritten_plugin = self.pyload.pluginManager.loadClass("hoster", self.pyfile.pluginname)
+ if overwritten_plugin is not None:
+ self.pyfile.url = replace_patterns(self.pyfile.url, overwritten_plugin.URL_REPLACEMENTS)
+
if self.DIRECT_LINK is None:
- self.direct_dl = self.__pattern__ != r'^unmatchable$' and re.match(self.__pattern__, self.pyfile.url)
+ self.direct_dl = self.__pattern__ != r'^unmatchable$' and re.match(self.__pattern__, self.pyfile.url) is not None
else:
self.direct_dl = self.DIRECT_LINK
diff --git a/module/plugins/internal/Notifier.py b/module/plugins/internal/Notifier.py
index 21c077e42b..4bae78bfe7 100644
--- a/module/plugins/internal/Notifier.py
+++ b/module/plugins/internal/Notifier.py
@@ -3,13 +3,13 @@
import time
from .Addon import Addon
-from .misc import Expose, encode, isiterable
+from .misc import Expose, decode, isiterable
class Notifier(Addon):
__name__ = "Notifier"
__type__ = "hook"
- __version__ = "0.11"
+ __version__ = "0.13"
__status__ = "testing"
__config__ = [("activated", "bool", "Activated", False),
@@ -17,6 +17,8 @@ class Notifier(Addon):
("reconnection", "bool", "Notify reconnection request", False),
("downloadfinished", "bool", "Notify download finished", True),
("downloadfailed", "bool", "Notify download failed", True),
+ ("alldownloadsfinished", "bool", "Notify all downloads finished", True),
+ ("alldownloadsprocessed", "bool", "Notify all downloads processed", True),
("packagefinished", "bool", "Notify package finished", True),
("packagefailed", "bool", "Notify package failed", True),
("update", "bool", "Notify pyLoad update", False),
@@ -101,10 +103,12 @@ def download_failed(self, pyfile):
self.notify(_("Download failed"), pyfile.name)
def all_downloads_processed(self):
- self.notify(_("All downloads processed"))
+ if self.config.get('alldownloadsprocessed', True):
+ self.notify(_("All downloads processed"))
def all_downloads_finished(self):
- self.notify(_("All downloads finished"))
+ if self.config.get('alldownloadsfinished', True):
+ self.notify(_("All downloads finished"))
@Expose
def notify(self, event, msg=None, key=None):
@@ -113,9 +117,9 @@ def notify(self, event, msg=None, key=None):
return
if isiterable(msg):
- msg = " | ".join(encode(a).strip() for a in msg if a)
+ msg = " | ".join(decode(a).strip() for a in msg if a)
else:
- msg = encode(msg)
+ msg = decode(msg)
if self.pyload.isClientConnected() and not self.config.get('ignoreclient', False):
return
diff --git a/module/plugins/internal/OCR.py b/module/plugins/internal/OCR.py
index 4423d9de1c..8fa4615b6b 100644
--- a/module/plugins/internal/OCR.py
+++ b/module/plugins/internal/OCR.py
@@ -5,7 +5,7 @@
import os
import subprocess
-from .misc import encode, fsjoin
+from .misc import Popen, fs_encode, fsjoin
from .Plugin import Plugin
try:
@@ -14,28 +14,25 @@
except ImportError:
import Image
-# import tempfile
-
class OCR(Plugin):
__name__ = "OCR"
__type__ = "ocr"
- __version__ = "0.26"
+ __version__ = "0.28"
__status__ = "stable"
__description__ = """OCR base plugin"""
__license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
+ __authors__ = [("pyLoad Team", "admin@pyload.net")]
def __init__(self, pyfile):
self._init(pyfile.m.core)
self.pyfile = pyfile
self.init()
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
messages = (self.__name__,) + messages
- return self.pyfile.plugin._log(
- level, plugintype, self.pyfile.plugin.__name__, messages)
+ return self.pyfile.plugin._log(level, plugintype, self.pyfile.plugin.__name__, messages, tbframe=tbframe)
def load_image(self, image):
self.img = Image.open(image)
@@ -58,44 +55,31 @@ def call_cmd(self, command, *args, **kwargs):
call = (command,) + args
self.log_debug("EXECUTE " + " ".join(call))
- call = map(encode, call)
- popen = subprocess.Popen(
+ call = map(fs_encode, call)
+ p = Popen(
call,
bufsize=-1,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
- popen.wait()
+ p.wait()
- output = popen.stdout.read() + " | " + popen.stderr.read()
+ output = p.stdout.read() + " | " + p.stderr.read()
- popen.stdout.close()
- popen.stderr.close()
+ p.stdout.close()
+ p.stderr.close()
- self.log_debug(
- "Tesseract ReturnCode %d" %
- popen.returncode,
- "Output: %s" %
- output)
+ self.log_debug("Tesseract ReturnCode %d" % p.returncode,
+ "Output: %s" % output)
def run_tesser(self, subset=False, digits=True,
lowercase=True, uppercase=True, pagesegmode=None):
# tmpTif = tempfile.NamedTemporaryFile(suffix=".tif")
try:
- tmpTif = open(
- fsjoin(
- "tmp",
- "tmpTif_%s.tif" %
- self.classname),
- "wb")
+ tmpTif = open(fsjoin("tmp", "tmpTif_%s.tif" % self.classname), "wb")
tmpTif.close()
# tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
- tmpTxt = open(
- fsjoin(
- "tmp",
- "tmpTxt_%s.txt" %
- self.classname),
- "wb")
+ tmpTxt = open(fsjoin("tmp", "tmpTxt_%s.txt" % self.classname), "wb")
tmpTxt.close()
except IOError, e:
@@ -110,13 +94,8 @@ def run_tesser(self, subset=False, digits=True,
else:
command = "tesseract"
- args = [
- os.path.abspath(
- tmpTif.name),
- os.path.abspath(
- tmpTxt.name).replace(
- ".txt",
- "")]
+ args = [os.path.abspath(tmpTif.name),
+ os.path.abspath(tmpTxt.name).replace(".txt", "")]
if pagesegmode:
args.extend(["-psm", str(pagesegmode)])
diff --git a/module/plugins/internal/Plugin.py b/module/plugins/internal/Plugin.py
index 66f3be6aa3..877dd57f5c 100644
--- a/module/plugins/internal/Plugin.py
+++ b/module/plugins/internal/Plugin.py
@@ -7,13 +7,13 @@
import pycurl
from module.network.RequestFactory import getRequest as get_request
-
from module.plugins.Plugin import SkipDownload as Skip
# @TODO: Remove in 0.4.10
from module.plugins.Plugin import Abort, Fail, Reconnect, Retry
-from .misc import (DB, Config, decode, encode, exists, fixurl, format_exc,
- fsjoin, html_unescape, parse_html_header, remove,
- set_cookies)
+
+from .misc import (
+ DB, Config, decode, encode, exists, fixurl, format_exc, fs_encode, fsjoin, html_unescape, parse_html_header, remove,
+ set_cookies)
if os.name != "nt":
import grp
@@ -27,7 +27,7 @@
class Plugin(object):
__name__ = "Plugin"
__type__ = "plugin"
- __version__ = "0.74"
+ __version__ = "0.79"
__status__ = "stable"
__config__ = [] #: [("name", "type", "desc", "default")]
@@ -71,38 +71,71 @@ def init(self):
"""
pass
- def _log(self, level, plugintype, pluginname, messages):
+ def _log(self, level, plugintype, pluginname, messages, tbframe=None):
log = getattr(self.pyload.log, level)
msg = u" | ".join(decode(a).strip() for a in messages if a)
+ if tbframe:
+ msg += "\n" + format_exc(tbframe)
+
log("%(plugintype)s %(pluginname)s: %(msg)s" %
{'plugintype': plugintype.upper(),
'pluginname': pluginname,
'msg': msg})
def log_debug(self, *args, **kwargs):
- self._log("debug", self.__type__, self.__name__, args)
- if self.pyload.debug and kwargs.get('trace'):
- self._print_exc()
+ trace = self.pyload.debug and kwargs.get('trace', False)
+ if trace:
+ try:
+ frame = inspect.currentframe()
+ self._log("debug", self.__type__, self.__name__, args, tbframe=frame)
+ finally:
+ del frame
+ else:
+ self._log("debug", self.__type__, self.__name__, args, tbframe=None)
def log_info(self, *args, **kwargs):
- self._log("info", self.__type__, self.__name__, args)
- if self.pyload.debug and kwargs.get('trace'):
- self._print_exc()
+ trace = self.pyload.debug and kwargs.get('trace', False)
+ if trace:
+ try:
+ frame = inspect.currentframe()
+ self._log("info", self.__type__, self.__name__, args, tbframe=frame)
+ finally:
+ del frame
+ else:
+ self._log("info", self.__type__, self.__name__, args, tbframe=False)
def log_warning(self, *args, **kwargs):
- self._log("warning", self.__type__, self.__name__, args)
- if self.pyload.debug and kwargs.get('trace'):
- self._print_exc()
+ trace = self.pyload.debug and kwargs.get('trace', False)
+ if trace:
+ try:
+ frame = inspect.currentframe()
+ self._log("warning", self.__type__, self.__name__, args, tbframe=frame)
+ finally:
+ del frame
+ else:
+ self._log("warning", self.__type__, self.__name__, args, tbframe=False)
def log_error(self, *args, **kwargs):
- self._log("error", self.__type__, self.__name__, args)
- if self.pyload.debug and kwargs.get('trace', True):
- self._print_exc()
+ trace = self.pyload.debug and kwargs.get('trace', True)
+ if trace:
+ try:
+ frame = inspect.currentframe()
+ self._log("error", self.__type__, self.__name__, args, tbframe=frame)
+ finally:
+ del frame
+ else:
+ self._log("error", self.__type__, self.__name__, args, tbframe=False)
def log_critical(self, *args, **kwargs):
- self._log("critical", self.__type__, self.__name__, args)
- if kwargs.get('trace', True):
- self._print_exc()
+ trace = kwargs.get('trace', True)
+ if trace:
+ try:
+ frame = inspect.currentframe()
+ self._log("critical", self.__type__, self.__name__, args, tbframe=frame)
+ finally:
+ del frame
+ else:
+ self._log("critical", self.__type__, self.__name__, args, tbframe=False)
def _print_exc(self):
frame = inspect.currentframe()
@@ -126,7 +159,7 @@ def remove(self, path, trash=False): # @TODO: Change to `trash=True` in 0.4.10
return True
def set_permissions(self, path):
- path = encode(path)
+ path = fs_encode(path)
if not exists(path):
return
@@ -155,7 +188,7 @@ def fail(self, msg):
"""
Fail and give msg
"""
- raise Fail(encode(msg)) # @TODO: Remove `encode` in 0.4.10
+ raise Fail(decode(msg)) # @TODO: Remove `decode` in 0.4.10
def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=True,
multipart=False, redirect=True, req=None):
@@ -189,7 +222,7 @@ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False,
if isinstance(cookies, list):
set_cookies(req.cj, cookies)
- http_req = self.req.http if hasattr(self.req, "http") else self.req
+ http_req = req.http if hasattr(req, "http") else req
#@TODO: Move to network in 0.4.10
if not redirect:
@@ -212,7 +245,7 @@ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False,
bool(cookies),
just_header,
multipart,
- decode is True) # @TODO: Fix network multipart in 0.4.10
+ decode is True)
#@TODO: Move to network in 0.4.10
if not redirect:
@@ -272,7 +305,7 @@ def upload(self, path, url, get={}, ref=True, cookies=True, just_header=False, d
*["%s=%s" % (key, value) for key, value in locals().items()
if key not in ("self", "url", "_[1]")])
- with open(path, 'rb') as f:
+ with open(fs_encode(path), 'rb') as f:
url = fixurl(url, unquote=True) #: Recheck in 0.4.10
if req is False:
@@ -402,10 +435,10 @@ def clean(self):
Remove references
"""
try:
- self.req.clearCookies()
+ # self.req.clearCookies()
self.req.close()
- except Exception:
+ except AttributeError:
pass
else:
diff --git a/module/plugins/internal/SevenZip.py b/module/plugins/internal/SevenZip.py
index 64c17850cc..bcf8feb147 100644
--- a/module/plugins/internal/SevenZip.py
+++ b/module/plugins/internal/SevenZip.py
@@ -5,20 +5,21 @@
import string
import subprocess
-from .misc import encode, fsjoin, renice
from .Extractor import ArchiveError, CRCError, Extractor, PasswordError
+from .misc import Popen, fs_encode, fsjoin, renice
class SevenZip(Extractor):
__name__ = "SevenZip"
__type__ = "extractor"
- __version__ = "0.27"
+ __version__ = "0.39"
__status__ = "testing"
__description__ = """7-Zip extractor plugin"""
__license__ = "GPLv3"
__authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("Michael Nowak", None)]
+ ("Michael Nowak", None),
+ ("GammaC0de", "nitzo2001[AT]yahoo[DOT]com")]
CMD = "7z"
EXTENSIONS = [('7z', "7z(?:\.\d{3})?"), "xz", "gz", "gzip", "tgz", "bz2", "bzip2", "tbz2",
@@ -27,9 +28,11 @@ class SevenZip(Extractor):
"iso", "msi", "doc", "xls", "ppt", "dmg", "xar", "hfs", "exe",
"ntfs", "fat", "vhd", "mbr", "squashfs", "cramfs", "scap"]
- _RE_PART = re.compile(r'\.7z\.\d{3}|\.(part|r)\d+(\.rar|\.rev)?(\.bad)?', re.I)
- _RE_FILES = re.compile(r'([\d\-]+)\s+([\d\:]+)\s+([RHSA\.]+)\s+(\d+)\s+(\d+)\s+(.+)')
- _RE_BADPWD = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I)
+ _RE_PART = re.compile(r'\.7z\.\d{3}|\.(part|r)\d+(\.rar|\.rev)?(\.bad)?|\.rar$', re.I)
+ _RE_FILES = re.compile(r'([\d\-]+)\s+([\d:]+)\s+([RHSA.]+)\s+(\d+)\s+(?:(\d+)\s+)?(.+)')
+ _RE_ENCRYPTED_HEADER = re.compile(r'encrypted archive')
+ _RE_ENCRYPTED_FILES = re.compile(r'Encrypted\s+=\s+\+')
+ _RE_BADPWD = re.compile(r"Wrong password", re.I)
_RE_BADCRC = re.compile(r'CRC Failed|Can not open file', re.I)
_RE_VERSION = re.compile(r'7-Zip\s(?:\(\w+\)\s)?(?:\[(?:32|64)\]\s)?(\d+\.\d+)', re.I)
@@ -39,9 +42,9 @@ def find(cls):
if os.name == "nt":
cls.CMD = os.path.join(pypath, "7z.exe")
- p = subprocess.Popen([cls.CMD],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ p = Popen([cls.CMD],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
out, err = (_r.strip() if _r else "" for _r in p.communicate())
except OSError:
@@ -58,22 +61,52 @@ def find(cls):
def ismultipart(cls, filename):
return True if cls._RE_PART.search(filename) else False
+ def init(self):
+ self.smallest = None
+ self.archive_encryption = None
+
def verify(self, password=None):
- #: 7z can't distinguish crc and pw error in test
- p = self.call_cmd("l", "-slt", self.filename)
- out, err = (_r.strip() if _r else "" for _r in p.communicate())
+ #: First we check if the header (file list) is protected
+ #: if the header is protected, we cen verify the password very fast without hassle
+ #: otherwise, we find the smallest file in the archive and then try to extract it
+
+ encrypted_header, encrypted_files = self._check_archive_encryption()
+ if encrypted_header:
+ p = self.call_cmd("l", "-slt", self.filename, password=password)
+ out, err = (_r.strip() if _r else "" for _r in p.communicate())
- if self._RE_BADPWD.search(out):
- raise PasswordError
+ if err:
+ if self._RE_ENCRYPTED_HEADER.search(err):
+ raise PasswordError
- elif self._RE_BADPWD.search(err):
- raise PasswordError
+ else:
+ raise ArchiveError(err)
- elif self._RE_BADCRC.search(out):
- raise CRCError(_("Header protected"))
+ elif encrypted_files:
+ #: search for smallest file and try to extract it to verify password
+ smallest = self._find_smallest_file(password=password)[0]
+ if smallest is None:
+ raise ArchiveError("Cannot find smallest file")
- elif self._RE_BADCRC.search(err):
- raise CRCError(err)
+ try:
+ extracted = os.path.join(self.dest, smallest if self.fullpath else os.path.basename(smallest))
+ try:
+ os.remove(extracted)
+ except OSError:
+ pass
+ self.extract(password=password, file=smallest)
+
+ #: Extraction was successful so exclude the file from further extraction
+ if smallest not in self.excludefiles:
+ self.excludefiles.append(smallest)
+
+ except (PasswordError, CRCError, ArchiveError), ex:
+ try:
+ os.remove(extracted)
+ except OSError:
+ pass
+
+ raise ex
def progress(self, process):
s = ""
@@ -83,7 +116,7 @@ def progress(self, process):
if not c:
break
#: Reading a percentage sign -> set progress and restart
- if c == "%":
+ if c == "%" and s:
self.pyfile.setProgress(int(s))
s = ""
#: Not reading a digit -> therefore restart
@@ -93,14 +126,14 @@ def progress(self, process):
else:
s += c
-
- def extract(self, password=None):
+ def extract(self, password=None, file=None):
command = "x" if self.fullpath else "e"
p = self.call_cmd(
command,
'-o' + self.dest,
self.filename,
+ file,
password=password)
#: Communicate and retrieve stderr
@@ -124,9 +157,10 @@ def chunks(self):
files = []
dir, name = os.path.split(self.filename)
- #: eventually Multipart Files
- files.extend(fsjoin(dir, os.path.basename(file)) for file in filter(self.ismultipart, os.listdir(dir))
- if self._RE_PART.sub("", name) == self._RE_PART.sub("", file))
+ #: eventually multi-part files
+ files.extend(fsjoin(dir, os.path.basename(_f))
+ for _f in filter(self.ismultipart, os.listdir(dir))
+ if self._RE_PART.sub("", name) == self._RE_PART.sub("", _f))
#: Actually extracted file
if self.filename not in files:
@@ -135,32 +169,18 @@ def chunks(self):
return files
def list(self, password=None):
- command = "l" if self.fullpath else "l"
-
- p = self.call_cmd(command, self.filename, password=password)
- out, err = (_r.strip() if _r else "" for _r in p.communicate())
-
- if any([_e in err for _e in ("Can not open", "cannot find the file")]):
- raise ArchiveError(_("Cannot open file"))
-
- if p.returncode > 1:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- files = set()
- for groups in self._RE_FILES.findall(out):
- f = groups[-1].strip()
- files.add(fsjoin(self.dest, f))
-
- self.files = list(files)
+ if not self.files:
+ self._find_smallest_file(password=password)
return self.files
def call_cmd(self, command, *xargs, **kwargs):
args = []
- #: Progress output
if self.VERSION and float(self.VERSION) >= 15.08:
- args.append("-bsp1")
+ #: Disable all output except progress and errors
+ args.append("-bso0")
+ args.append("-bsp1")
#: Overwrite flag
if self.overwrite:
@@ -186,12 +206,11 @@ def call_cmd(self, command, *xargs, **kwargs):
else:
args.append("-p-")
- #@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
- call = [self.CMD, command] + args + list(xargs)
+ call = [self.CMD, command] + args + [arg for arg in xargs if arg]
self.log_debug("EXECUTE " + " ".join(call))
- call = map(encode, call)
- p = subprocess.Popen(
+ call = map(fs_encode, call)
+ p = Popen(
call,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -199,3 +218,45 @@ def call_cmd(self, command, *xargs, **kwargs):
renice(p.pid, self.priority)
return p
+
+ def _check_archive_encryption(self):
+ if self.archive_encryption is None:
+ p = self.call_cmd("l", "-slt", self.filename)
+ out, err = (r.strip() if r else "" for r in p.communicate())
+
+ encrypted_header = self._RE_ENCRYPTED_HEADER.search(err) is not None
+ encrypted_files = self._RE_ENCRYPTED_FILES.search(out) is not None
+
+ self.archive_encryption = (encrypted_header, encrypted_files)
+
+ return self.archive_encryption
+
+ def _find_smallest_file(self, password=None):
+ if not self.smallest:
+ p = self.call_cmd("l", self.filename, password=password)
+ out, err = (_r.strip() if _r else "" for _r in p.communicate())
+
+ if any(e in err for e in ("Can not open", "cannot find the file")):
+ raise ArchiveError(_("Cannot open file"))
+
+ if p.returncode > 1:
+ raise ArchiveError(_("Process return code: %s") % p.returncode)
+
+ smallest = (None, 0)
+ files = set()
+ for groups in self._RE_FILES.findall(out):
+ s = int(groups[3])
+ f = groups[-1].strip()
+
+ if smallest[1] == 0 or smallest[1] > s > 0:
+ smallest = (f, s)
+
+ if not self.fullpath:
+ f = os.path.basename(f)
+ f = os.path.join(self.dest, f)
+ files.add(f)
+
+ self.smallest = smallest
+ self.files = list(files)
+
+ return self.smallest
diff --git a/module/plugins/internal/SimpleCrypter.py b/module/plugins/internal/SimpleCrypter.py
index 0552bba96b..2292f251f2 100644
--- a/module/plugins/internal/SimpleCrypter.py
+++ b/module/plugins/internal/SimpleCrypter.py
@@ -3,16 +3,15 @@
import re
from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL as get_url
from .Crypter import Crypter
-from .misc import parse_name, parse_time, replace_patterns
+from .misc import parse_name, parse_time, replace_patterns, search_pattern
class SimpleCrypter(Crypter):
__name__ = "SimpleCrypter"
__type__ = "crypter"
- __version__ = "0.93"
+ __version__ = "0.98"
__status__ = "testing"
__pattern__ = r'^unmatchable$'
@@ -72,8 +71,8 @@ def load_page(self, page_n):
PAGES_PATTERN = None
NAME_PATTERN = None
- OFFLINE_PATTERN = r'[^\w](404\s|[Ii]nvalid|[Oo]ffline|[Dd]elet|[Rr]emov|([Nn]o(t|thing)?|sn\'t) (found|(longer )?(available|exist)))'
- TEMP_OFFLINE_PATTERN = r'[^\w](503\s|[Mm]aint(e|ai)nance|[Tt]emp([.-]|orarily)|[Mm]irror)'
+ OFFLINE_PATTERN = r'[^\w](?:404\s|[Nn]ot [Ff]ound|[Ff]ile (?:was|has been)?\s*(?:removed|deleted)|[Ff]ile (?:does not exist|could not be found|no longer available))'
+ TEMP_OFFLINE_PATTERN = r'[^\w](?:503\s|[Ss]erver (?:is (?:in|under) )?[Mm]aint(?:e|ai)nance|[Tt]emp(?:[.-]|orarily )(?:[Oo]ffline|[Uu]available)|[Uu]se (?:[Aa] )?[Mm]irror)'
WAIT_PATTERN = None
PREMIUM_ONLY_PATTERN = None
@@ -81,15 +80,13 @@ def load_page(self, page_n):
SIZE_LIMIT_PATTERN = None
ERROR_PATTERN = None
- @classmethod
- def api_info(cls, url):
+ def api_info(self, url):
return {}
- @classmethod
- def get_info(cls, url="", html=""):
- info = super(SimpleCrypter, cls).get_info(url)
+ def get_info(self, url="", html=""):
+ info = super(SimpleCrypter, self).get_info(url)
- info.update(cls.api_info(url))
+ info.update(self.api_info(url))
if not html and info['status'] != 2:
if not url:
@@ -98,8 +95,7 @@ def get_info(cls, url="", html=""):
elif info['status'] in (3, 7):
try:
- html = get_url(
- url, cookies=cls.COOKIES, decode=cls.TEXT_ENCODING)
+ html = self.load(url, cookies=self.COOKIES, decode=self.TEXT_ENCODING)
except BadHeader, e:
info['error'] = "%d: %s" % (e.code, e.content)
@@ -108,23 +104,20 @@ def get_info(cls, url="", html=""):
pass
if html:
- if cls.OFFLINE_PATTERN and re.search(
- cls.OFFLINE_PATTERN, html) is not None:
+ if search_pattern(self.OFFLINE_PATTERN, html) is not None:
info['status'] = 1
- elif cls.TEMP_OFFLINE_PATTERN and re.search(cls.TEMP_OFFLINE_PATTERN, html) is not None:
+ elif search_pattern(self.TEMP_OFFLINE_PATTERN, html) is not None:
info['status'] = 6
- elif cls.NAME_PATTERN:
- m = re.search(cls.NAME_PATTERN, html)
+ elif self.NAME_PATTERN:
+ m = search_pattern(self.NAME_PATTERN, html)
if m is not None:
info['status'] = 2
info['pattern'].update(m.groupdict())
if 'N' in info['pattern']:
- name = replace_patterns(
- info['pattern']['N'],
- cls.NAME_REPLACEMENTS)
+ name = replace_patterns(info['pattern']['N'], self.NAME_REPLACEMENTS)
info['name'] = parse_name(name)
return info
@@ -134,8 +127,7 @@ def setup_base(self):
account_name = self.classname.rsplit("Folder", 1)[0]
if self.account:
- self.req = self.pyload.requestFactory.getRequest(
- account_name, self.account.user)
+ self.req = self.pyload.requestFactory.getRequest(account_name, self.account.user)
# @NOTE: Don't call get_info here to reduce overhead
self.premium = self.account.info['data']['premium']
else:
@@ -192,8 +184,7 @@ def _prepare(self):
else:
self.direct_dl = self.DIRECT_LINK
- self.pyfile.url = replace_patterns(
- self.pyfile.url, self.URL_REPLACEMENTS)
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
def decrypt(self, pyfile):
self._prepare()
@@ -261,7 +252,7 @@ def load_page(self, number):
def handle_pages(self, pyfile):
try:
- pages = int(re.search(self.PAGES_PATTERN, self.data).group(1))
+ pages = int(search_pattern(self.PAGES_PATTERN, self.data).group(1))
except Exception:
pages = 1
@@ -273,28 +264,27 @@ def handle_pages(self, pyfile):
self.links = links
- def check_errors(self):
+ def check_errors(self, data=None):
self.log_info(_("Checking for link errors..."))
- if not self.data:
+ data = data or self.data
+
+ if not data:
self.log_warning(_("No data to check"))
return
- if self.IP_BLOCKED_PATTERN and re.search(
- self.IP_BLOCKED_PATTERN, self.data):
- self.fail(
- _("Connection from your current IP address is not allowed"))
+ if search_pattern(self.IP_BLOCKED_PATTERN, data):
+ self.fail(_("Connection from your current IP address is not allowed"))
elif not self.premium:
- if self.PREMIUM_ONLY_PATTERN and re.search(
- self.PREMIUM_ONLY_PATTERN, self.data):
+ if search_pattern(self.PREMIUM_ONLY_PATTERN, data):
self.fail(_("Link can be decrypted by premium users only"))
- elif self.SIZE_LIMIT_PATTERN and re.search(self.SIZE_LIMIT_PATTERN, self.data):
+ elif search_pattern(self.SIZE_LIMIT_PATTERN, data):
self.fail(_("Link list too large for free decrypt"))
if self.ERROR_PATTERN:
- m = re.search(self.ERROR_PATTERN, self.data)
+ m = search_pattern(self.ERROR_PATTERN, data)
if m is not None:
try:
errmsg = m.group(1)
@@ -308,22 +298,19 @@ def check_errors(self):
self.info['error'] = errmsg
self.log_warning(errmsg)
- if re.search(self.TEMP_OFFLINE_PATTERN, errmsg):
+ if search_pattern(self.TEMP_OFFLINE_PATTERN, errmsg):
self.temp_offline()
- elif re.search(self.OFFLINE_PATTERN, errmsg):
+ elif search_pattern(self.OFFLINE_PATTERN, errmsg):
self.offline()
elif re.search(r'limit|wait|slot', errmsg, re.I):
wait_time = parse_time(errmsg)
- self.wait(
- wait_time, reconnect=wait_time > self.config.get(
- 'max_wait', 10) * 60)
+ self.wait(wait_time, reconnect=wait_time > self.config.get('max_wait', 10) * 60)
self.restart(_("Download limit exceeded"))
elif re.search(r'country|ip|region|nation', errmsg, re.I):
- self.fail(
- _("Connection from your current IP address is not allowed"))
+ self.fail(_("Connection from your current IP address is not allowed"))
elif re.search(r'captcha|code', errmsg, re.I):
self.retry_captcha()
@@ -337,8 +324,7 @@ def check_errors(self):
elif re.search(r'up to|size', errmsg, re.I):
self.fail(_("Link list too large for free decrypt"))
- elif re.search(r'404|sorry|offline|delet|remov|(no(t|thing)?|sn\'t) (found|(longer )?(available|exist))',
- errmsg, re.I):
+ elif re.search(r'404|sorry|offline|delet|remov|(no(t|thing)?|sn\'t) (found|(longer )?(available|exist))', errmsg, re.I):
self.offline()
elif re.search(r'filename', errmsg, re.I):
@@ -351,8 +337,8 @@ def check_errors(self):
self.wait(60, reconnect=True)
self.restart(errmsg)
- elif self.WAIT_PATTERN:
- m = re.search(self.WAIT_PATTERN, self.data)
+ else:
+ m = search_pattern(self.WAIT_PATTERN, data)
if m is not None:
try:
waitmsg = m.group(1).strip()
@@ -361,11 +347,7 @@ def check_errors(self):
waitmsg = m.group(0).strip()
wait_time = parse_time(waitmsg)
- self.wait(
- wait_time,
- reconnect=wait_time > self.config.get(
- 'max_wait',
- 10) * 60)
+ self.wait(wait_time,reconnect=wait_time > self.config.get('max_wait', 10) * 60)
self.log_info(_("No errors found"))
self.info.pop('error', None)
diff --git a/module/plugins/internal/SimpleHoster.py b/module/plugins/internal/SimpleHoster.py
index af236975ea..78896a3244 100644
--- a/module/plugins/internal/SimpleHoster.py
+++ b/module/plugins/internal/SimpleHoster.py
@@ -5,16 +5,15 @@
import re
from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL as get_url
from .Hoster import Hoster
-from .misc import encode, parse_name, parse_size, parse_time, replace_patterns
+from .misc import fs_encode, parse_name, parse_size, parse_time, replace_patterns, search_pattern
class SimpleHoster(Hoster):
__name__ = "SimpleHoster"
__type__ = "hoster"
- __version__ = "2.27"
+ __version__ = "2.42"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -114,8 +113,8 @@ class SimpleHoster(Hoster):
NAME_PATTERN = None
SIZE_PATTERN = None
HASHSUM_PATTERN = r'[^\w](?P(CRC|crc)(-?32)?|(MD|md)-?5|(SHA|sha)-?(1|224|256|384|512)).*(:|=|>)[ ]*(?P(?:[a-z0-9]|[A-Z0-9]){8,})'
- OFFLINE_PATTERN = r'[^\w](404\s|[Ii]nvalid|[Oo]ffline|[Dd]elet|[Rr]emov|([Nn]o(t|thing)?|sn\'t) (found|(longer )?(available|exist)))'
- TEMP_OFFLINE_PATTERN = r'[^\w](503\s|[Mm]aint(e|ai)nance|[Tt]emp([.-]|orarily)|[Mm]irror)'
+ OFFLINE_PATTERN = r'[^\w](?:404\s|[Nn]ot [Ff]ound|[Ff]ile (?:was|has been)?\s*(?:removed|deleted)|[Ff]ile (?:does not exist|could not be found|no longer available))'
+ TEMP_OFFLINE_PATTERN = r'[^\w](?:503\s|[Ss]erver (?:is (?:in|under) )?[Mm]aint(?:e|ai)nance|[Tt]emp(?:[.-]|orarily )(?:[Oo]ffline|[Uu]available)|[Uu]se (?:[Aa] )?[Mm]irror)'
WAIT_PATTERN = None
PREMIUM_ONLY_PATTERN = None
@@ -126,18 +125,15 @@ class SimpleHoster(Hoster):
ERROR_PATTERN = None
FILE_ERRORS = [('Html error', r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'),
- ('Request error',
- r'([Aa]n error occured while processing your request)'),
+ ('Request error', r'([Aa]n error occured while processing your request)'),
('Html file', r'\A\s*', " ", errmsg.strip())
+ finally:
+ errmsg = re.sub(r'<.*?>', " ", errmsg.strip())
- self.info['error'] = errmsg
- self.log_warning(errmsg)
+ self.info['error'] = errmsg
+ self.log_warning(errmsg)
- wait_time = parse_time(errmsg)
- self.wait(
- wait_time,
- reconnect=wait_time > self.config.get(
- 'max_wait',
- 10) * 60)
- self.restart(_("Download limit exceeded"))
+ wait_time = parse_time(errmsg)
+ self.wait(wait_time, reconnect=wait_time > self.config.get('max_wait', 10) * 60)
+ self.restart(_("Download limit exceeded"))
- if self.HAPPY_HOUR_PATTERN and re.search(
- self.HAPPY_HOUR_PATTERN, self.data):
+ if search_pattern(self.HAPPY_HOUR_PATTERN, data):
self.multiDL = True
if self.ERROR_PATTERN:
- m = re.search(self.ERROR_PATTERN, self.data)
+ m = search_pattern(self.ERROR_PATTERN, data)
if m is not None:
try:
errmsg = m.group(1).strip()
@@ -390,22 +371,19 @@ def check_errors(self):
self.info['error'] = errmsg
self.log_warning(errmsg)
- if re.search(self.TEMP_OFFLINE_PATTERN, errmsg):
+ if search_pattern(self.TEMP_OFFLINE_PATTERN, errmsg):
self.temp_offline()
- elif re.search(self.OFFLINE_PATTERN, errmsg):
+ elif search_pattern(self.OFFLINE_PATTERN, errmsg):
self.offline()
elif re.search(r'limit|wait|slot', errmsg, re.I):
wait_time = parse_time(errmsg)
- self.wait(
- wait_time, reconnect=wait_time > self.config.get(
- 'max_wait', 10) * 60)
+ self.wait(wait_time, reconnect=wait_time > self.config.get('max_wait', 10) * 60)
self.restart(_("Download limit exceeded"))
elif re.search(r'country|ip|region|nation', errmsg, re.I):
- self.fail(
- _("Connection from your current IP address is not allowed"))
+ self.fail(_("Connection from your current IP address is not allowed"))
elif re.search(r'captcha|code', errmsg, re.I):
self.retry_captcha()
@@ -419,23 +397,21 @@ def check_errors(self):
elif re.search(r'up to|size', errmsg, re.I):
self.fail(_("File too large for free download"))
- elif re.search(r'404|sorry|offline|delet|remov|(no(t|thing)?|sn\'t) (found|(longer )?(available|exist))',
- errmsg, re.I):
+ elif re.search(r'404|sorry|offline|delet|remov|(no(t|thing)?|sn\'t) (found|(longer )?(available|exist))', errmsg, re.I):
self.offline()
elif re.search(r'filename', errmsg, re.I):
self.fail(_("Invalid url"))
elif re.search(r'premium', errmsg, re.I):
- self.fail(
- _("File can be downloaded by premium users only"))
+ self.fail(_("File can be downloaded by premium users only"))
else:
self.wait(60, reconnect=True)
self.restart(errmsg)
elif self.WAIT_PATTERN:
- m = re.search(self.WAIT_PATTERN, self.data)
+ m = search_pattern(self.WAIT_PATTERN, data)
if m is not None:
try:
waitmsg = m.group(1).strip()
@@ -444,11 +420,7 @@ def check_errors(self):
waitmsg = m.group(0).strip()
wait_time = parse_time(waitmsg)
- self.wait(
- wait_time,
- reconnect=wait_time > self.config.get(
- 'max_wait',
- 10) * 60)
+ self.wait(wait_time, reconnect=wait_time > self.config.get('max_wait', 10) * 60)
self.log_info(_("No errors found"))
self.info.pop('error', None)
@@ -459,8 +431,18 @@ def get_fileInfo(self):
self.grab_info()
return self.info
+ def grab_info(self):
+ if self.info.get("status", 7) != 2:
+ self.pyfile.name = parse_name(self.pyfile.url)
+
def handle_direct(self, pyfile):
- self.link = pyfile.url if self.isresource(pyfile.url) else None
+ link = self.isresource(pyfile.url)
+ if link:
+ pyfile.name = parse_name(link)
+ self.link = pyfile.url
+
+ else:
+ self.link = None
def handle_multi(self, pyfile): #: Multi-hoster handler
pass
@@ -469,7 +451,7 @@ def handle_free(self, pyfile):
if not self.LINK_FREE_PATTERN:
self.fail(_("Free download not implemented"))
- m = re.search(self.LINK_FREE_PATTERN, self.data)
+ m = search_pattern(self.LINK_FREE_PATTERN, self.data)
if m is None:
self.error(_("Free download link not found"))
else:
@@ -480,7 +462,7 @@ def handle_premium(self, pyfile):
self.log_warning(_("Premium download not implemented"))
self.restart(premium=False)
- m = re.search(self.LINK_PREMIUM_PATTERN, self.data)
+ m = search_pattern(self.LINK_PREMIUM_PATTERN, self.data)
if m is None:
self.error(_("Premium download link not found"))
else:
diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py
index ed6252c223..70eb992da3 100644
--- a/module/plugins/internal/UnRar.py
+++ b/module/plugins/internal/UnRar.py
@@ -6,20 +6,21 @@
import subprocess
from .Extractor import ArchiveError, CRCError, Extractor, PasswordError
-from .misc import decode, encode, fsjoin, renice
+from .misc import Popen, decode, fs_encode, fsjoin, renice
class UnRar(Extractor):
__name__ = "UnRar"
__type__ = "extractor"
- __version__ = "1.38"
+ __version__ = "1.48"
__status__ = "testing"
- __config__ = [("ignore_warnings", "bool", "Ignore unrar warnings", False)]
+ __config__ = [("ignore_warnings", "bool", "Ignore unrar warnings", False),
+ ("ignore_file_attributes", "bool", "Ignore File Attributes", False)]
__description__ = """RAR extractor plugin"""
__license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
+ __authors__ = [("RaNaN", "RaNaN@pyload.net"),
("Walter Purcaro", "vuolter@gmail.com"),
("Immenz", "immenz@gmx.net"),
("GammaCode", "nitzo2001[AT]yahoo[DOT]com")]
@@ -28,14 +29,13 @@ class UnRar(Extractor):
EXTENSIONS = ["rar", "cab", "arj", "lzh", "tar", "gz", "ace", "uue",
"bz2", "jar", "iso", "xz", "z"]
- _RE_PART = re.compile(r'\.(part|r)\d+(\.rar|\.rev)?(\.bad)?', re.I)
+ _RE_PART = re.compile(r'\.(part|r)\d+(\.rar|\.rev)?(\.bad)?|\.rar$', re.I)
_RE_FIXNAME = re.compile(r'Building (.+)')
- _RE_FILES = re.compile(
- r'^(.)(\s*[\w\-.]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8,}\s+[\d\:]{5}',
- re.I | re.M)
+ _RE_FILES_V4 = re.compile(r'^([* ])(.+?)\s+(\d+)\s+(\d+)\s+(\d+%|-->|<--)\s+([\d-]+)\s+([\d:]+)\s*([ACHIRS.rw\-]+)\s+([0-9A-F]{8})\s+(\w+)\s+([\d.]+)', re.M)
+ _RE_FILES_V5 = re.compile(r'^([* ])\s*([ACHIRS.rw\-]+)\s+(\d+)(?:\s+\d+)?(?:\s+(?:\d+%|-->|<--))?\s+([\d-]+)\s+([\d:]+)(?:\s+[0-9A-F]{8})?\s+(.+)', re.M)
+ _RE_ENCRYPTED_HEADER = re.compile(r'\s0 files')
_RE_BADPWD = re.compile(r'password', re.I)
- _RE_BADCRC = re.compile(
- r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I)
+ _RE_BADCRC = re.compile(r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I)
_RE_VERSION = re.compile(r'(?:UN)?RAR\s(\d+\.\d+)', re.I)
@classmethod
@@ -46,11 +46,10 @@ def find(cls):
else:
cls.CMD = "rar"
- p = subprocess.Popen([cls.CMD],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ p = Popen([cls.CMD],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
out, err = (_r.strip() if _r else "" for _r in p.communicate())
- # cls.__name__ = "RAR"
cls.REPAIR = True
except OSError:
@@ -60,9 +59,9 @@ def find(cls):
else:
cls.CMD = "unrar"
- p = subprocess.Popen([cls.CMD],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ p = Popen([cls.CMD],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
out, err = (_r.strip() if _r else "" for _r in p.communicate())
except OSError:
@@ -71,6 +70,7 @@ def find(cls):
m = cls._RE_VERSION.search(out)
if m is not None:
cls.VERSION = m.group(1)
+ cls._RE_FILES = cls._RE_FILES_V4 if float(cls.VERSION) < 5 else cls._RE_FILES_V5
return True
@@ -78,20 +78,47 @@ def find(cls):
def ismultipart(cls, filename):
return True if cls._RE_PART.search(filename) else False
+ def init(self):
+ self.smallest = None
+ self.archive_encryption = None
+
def verify(self, password=None):
- p = self.call_cmd("l", "-v", self.filename, password=password)
- out, err = (_r.strip() if _r else "" for _r in p.communicate())
+ #: First we check if the header (file list) is protected
+ #: if the header is protected, we cen verify the password very fast without hassle
+ #: otherwise, we find the smallest file in the archive and then try to extract it
+ encrypted_header, encrypted_files = self._check_archive_encryption()
+ if encrypted_header:
+ p = self.call_cmd("l", "-v", self.filename, password=password)
+ out, err = (_r.strip() if _r else "" for _r in p.communicate())
- if self._RE_BADPWD.search(err):
- raise PasswordError
+ if self._RE_ENCRYPTED_HEADER.search(out):
+ raise PasswordError
- if self._RE_BADCRC.search(err):
- raise CRCError(err)
+ elif encrypted_files:
+ #: search for smallest file and try to extract it to verify password
+ smallest = self._find_smallest_file(password=password)[0]
+ if smallest is None:
+ raise ArchiveError("Cannot find smallest file")
- #: Output only used to check if passworded files are present
- for attr in self._RE_FILES.findall(out):
- if attr[0].startswith("*"):
- raise PasswordError
+ try:
+ extracted = os.path.join(self.dest, smallest if self.fullpath else os.path.basename(smallest))
+ try:
+ os.remove(extracted)
+ except OSError:
+ pass
+ self.extract(password=password, file=smallest)
+
+ #: Extraction was successful so exclude the file from further extraction
+ if smallest not in self.excludefiles:
+ self.excludefiles.append(smallest)
+
+ except (PasswordError, CRCError, ArchiveError) as ex:
+ try:
+ os.remove(extracted)
+ except OSError:
+ pass
+
+ raise ex
def repair(self):
p = self.call_cmd("rc", self.filename)
@@ -126,7 +153,7 @@ def progress(self, process):
if not c:
break
#: Reading a percentage sign -> set progress and restart
- if c == "%":
+ if c == "%" and s:
self.pyfile.setProgress(int(s))
s = ""
#: Not reading a digit -> therefore restart
@@ -136,10 +163,10 @@ def progress(self, process):
else:
s += c
- def extract(self, password=None):
+ def extract(self, password=None, file=None):
command = "x" if self.fullpath else "e"
- p = self.call_cmd(command, self.filename, self.dest, password=password)
+ p = self.call_cmd(command, self.filename, file, self.dest, password=password)
#: Communicate and retrieve stderr
self.progress(p)
@@ -158,7 +185,7 @@ def extract(self, password=None):
else: #: Raise error if anything is on stderr
raise ArchiveError(err)
- if p.returncode:
+ if p.returncode and p.returncode != 10: #: RARX_NOFILES
raise ArchiveError(_("Process return code: %d") % p.returncode)
return self.list(password)
@@ -167,9 +194,10 @@ def chunks(self):
files = []
dir, name = os.path.split(self.filename)
- #: eventually Multipart Files
- files.extend(fsjoin(dir, os.path.basename(file)) for file in filter(self.ismultipart, os.listdir(dir))
- if self._RE_PART.sub("", name) == self._RE_PART.sub("", file))
+ #: eventually multi-part files
+ files.extend(fsjoin(dir, os.path.basename(_f))
+ for _f in filter(self.ismultipart, os.listdir(dir))
+ if self._RE_PART.sub("", name) == self._RE_PART.sub("", _f))
#: Actually extracted file
if self.filename not in files:
@@ -178,41 +206,9 @@ def chunks(self):
return files
def list(self, password=None):
- command = "vb" if self.fullpath else "lb"
+ if not self.files:
+ self._find_smallest_file(password=password)
- p = self.call_cmd(command, "-v", self.filename, password=password)
- out, err = (_r.strip() if _r else "" for _r in p.communicate())
-
- if "Cannot open" in err:
- raise ArchiveError(_("Cannot open file"))
-
- if err: #: Only log error at this point
- self.log_error(err)
-
- result = set()
- if not self.fullpath and self.VERSION.startswith('5'):
- #@NOTE: Unrar 5 always list full path
- for f in decode(out).splitlines():
- f = fsjoin(self.dest, os.path.basename(f.strip()))
- if os.path.isfile(f):
- result.add(fsjoin(self.dest, os.path.basename(f)))
- else:
- if self.fullpath:
- for f in decode(out).splitlines():
- # Unrar fails to list all directories for some archives
- f = f.strip()
- while f:
- fabs = fsjoin(self.dest, f)
- if fabs not in result:
- result.add(fabs)
- f = os.path.dirname(f)
- else:
- break
- else:
- for f in decode(out).splitlines():
- result.add(fsjoin(self.dest, f.strip()))
-
- self.files = list(result)
return self.files
def call_cmd(self, command, *xargs, **kwargs):
@@ -231,6 +227,9 @@ def call_cmd(self, command, *xargs, **kwargs):
#: Assume yes on all queries
args.append("-y")
+ #: Disable comments show
+ args.append("-c-")
+
#: Set a password
password = kwargs.get('password')
@@ -242,12 +241,15 @@ def call_cmd(self, command, *xargs, **kwargs):
if self.keepbroken:
args.append("-kb")
- #@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
- call = [self.CMD, command] + args + list(xargs)
+ if self.config.get("ignore_file_attributes", False):
+ args.append("-ai")
+
+ # @NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
+ call = [self.CMD, command] + args + [arg for arg in xargs if arg]
self.log_debug("EXECUTE " + " ".join(call))
- call = map(encode, call)
- p = subprocess.Popen(
+ call = map(fs_encode, call)
+ p = Popen(
call,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -255,3 +257,46 @@ def call_cmd(self, command, *xargs, **kwargs):
renice(p.pid, self.priority)
return p
+
+ def _check_archive_encryption(self):
+ if self.archive_encryption is None:
+ p = self.call_cmd("l", "-v", self.filename)
+ out, err = (_r.strip() if _r else "" for _r in p.communicate())
+ encrypted_header = self._RE_ENCRYPTED_HEADER.search(out) is not None
+ encrypted_files = any(m.group(1) == "*" for m in self._RE_FILES.finditer(decode(out)))
+
+ self.archive_encryption = (encrypted_header, encrypted_files)
+
+ return self.archive_encryption
+
+ def _find_smallest_file(self, password=None):
+ if not self.smallest:
+ command = "v" if self.fullpath else "l"
+ p = self.call_cmd(command, "-v", self.filename, password=password)
+ out, err = (_r.strip() if _r else "" for _r in p.communicate())
+
+ if "Cannot open" in err:
+ raise ArchiveError(_("Cannot open file"))
+
+ if err: #: Only log error at this point
+ self.log_error(err)
+
+ smallest = (None, 0)
+ files = set()
+ f_grp = 5 if float(self.VERSION) >= 5 else 1
+ for groups in self._RE_FILES.findall(decode(out)):
+ s = int(groups[2])
+ f = groups[f_grp].strip()
+
+ if smallest[1] == 0 or smallest[1] > s > 0:
+ smallest = (f, s)
+
+ if not self.fullpath:
+ f = os.path.basename(f)
+ f = os.path.join(self.dest, f)
+ files.add(f)
+
+ self.smallest = smallest
+ self.files = list(files)
+
+ return self.smallest
diff --git a/module/plugins/internal/UnTar.py b/module/plugins/internal/UnTar.py
index 4be462d7f0..240fe31e27 100644
--- a/module/plugins/internal/UnTar.py
+++ b/module/plugins/internal/UnTar.py
@@ -2,17 +2,34 @@
from __future__ import with_statement
+import os
import sys
import tarfile
from .Extractor import ArchiveError, CRCError, Extractor
-from .misc import encode
+from .misc import fs_encode, fsjoin
+
+
+# Fix for tarfile CVE-2007-4559
+def _safe_extractall(tar, path=".", members=None):
+ def _is_within_directory(directory, target):
+ abs_directory = os.path.abspath(directory)
+ abs_target = os.path.abspath(target)
+ prefix = os.path.commonprefix([abs_directory, abs_target])
+ return prefix == abs_directory
+
+ for member in tar.getmembers():
+ member_path = os.path.join(path, member.name)
+ if not _is_within_directory(path, member_path):
+ raise ArchiveError("Attempted Path Traversal in Tar File (CVE-2007-4559)")
+
+ tar.extractall(path, members)
class UnTar(Extractor):
__name__ = "UnTar"
__type__ = "extractor"
- __version__ = "0.04"
+ __version__ = "0.07"
__status__ = "stable"
__description__ = """TAR extractor plugin"""
@@ -23,11 +40,19 @@ class UnTar(Extractor):
sys.version_info[1],
sys.version_info[2])
+ @classmethod
+ def archivetype(cls, filename):
+ return "tar" if cls.isarchive(filename) else None
+
@classmethod
def isarchive(cls, filename):
+ fsname = fs_encode(filename)
try:
- return tarfile.is_tarfile(encode(filename))
- except:
+ if tarfile.is_tarfile(fsname):
+ with tarfile.open(fsname) as tf:
+ return len(tf.getmembers()) > 0
+ return False
+ except IOError:
return False
@classmethod
@@ -36,7 +61,7 @@ def find(cls):
def list(self, password=None):
with tarfile.open(self.filename) as t:
- self.files = t.getnames()
+ self.files = [fsjoin(self.dest, _f) for _f in t.getnames()]
return self.files
def verify(self, password=None):
@@ -57,7 +82,7 @@ def extract(self, password=None):
try:
with tarfile.open(self.filename, errorlevel=2) as t:
- t.extractall(self.dest)
+ _safe_extractall(t, self.dest)
self.files = t.getnames()
return self.files
diff --git a/module/plugins/internal/UnZip.py b/module/plugins/internal/UnZip.py
index abb98d5ab0..6e2e097210 100644
--- a/module/plugins/internal/UnZip.py
+++ b/module/plugins/internal/UnZip.py
@@ -2,17 +2,18 @@
from __future__ import with_statement
+import os
import sys
import zipfile
from .Extractor import ArchiveError, CRCError, Extractor, PasswordError
-from .misc import encode
+from .misc import fs_encode, fsjoin
class UnZip(Extractor):
__name__ = "UnZip"
__type__ = "extractor"
- __version__ = "1.25"
+ __version__ = "1.27"
__status__ = "stable"
__description__ = """ZIP extractor plugin"""
@@ -25,11 +26,26 @@ class UnZip(Extractor):
@classmethod
def archivetype(cls, filename):
- return "zip" if cls.isarchive(filename) else None
+ try:
+ return "zip" if cls.isarchive(filename) else None
+
+ except IOError:
+ return None
@classmethod
def isarchive(cls, filename):
- return zipfile.is_zipfile(encode(filename))
+ #: zipfile only checks for 'End of archive' so we have to check ourselves for 'start of archive'
+ try:
+ with open(fs_encode(filename), "rb") as f:
+ data = f.read(4)
+ if data != "PK\003\004":
+ return False
+
+ else:
+ return zipfile.is_zipfile(f)
+
+ except IOError:
+ return False
@classmethod
def find(cls):
@@ -38,7 +54,7 @@ def find(cls):
def list(self, password=None):
with zipfile.ZipFile(self.filename, 'r') as z:
z.setpassword(password)
- self.files = z.namelist()
+ self.files = [fs_encode(self.dest, _f) for _f in z.namelist() if not _f[-1] != os.path.sep]
return self.files
def verify(self, password=None):
diff --git a/module/plugins/internal/XFSAccount.py b/module/plugins/internal/XFSAccount.py
index 6d56796bce..a85b7fc9e1 100644
--- a/module/plugins/internal/XFSAccount.py
+++ b/module/plugins/internal/XFSAccount.py
@@ -4,14 +4,14 @@
import time
import urlparse
-from .misc import parse_html_form, parse_time, set_cookie
+from .misc import parse_html_form, parse_time, set_cookie, search_pattern
from .Account import Account
class XFSAccount(Account):
__name__ = "XFSAccount"
__type__ = "account"
- __version__ = "0.59"
+ __version__ = "0.66"
__status__ = "stable"
__config__ = [("activated", "bool", "Activated", True),
@@ -33,7 +33,8 @@ class XFSAccount(Account):
PREMIUM_PATTERN = r'\(Premium only\)'
- VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})'
+ VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expires?:.*?(\d{1,2} [\w^_]+ \d{4})'
+ VALID_UNTIL_FORMAT = r"%d %B %Y"
TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?\s*(?P[\d.,]+|[Uu]nlimited)\s*(?:(?P[\w^_]+)\s*)?'
TRAFFIC_LEFT_UNIT = "MB" #: Used only if no group was found
@@ -82,15 +83,15 @@ def grab_info(self, user, password, data):
get={'op': "my_account"},
cookies=self.COOKIES)
- premium = True if re.search(self.PREMIUM_PATTERN, self.data) else False
+ premium = True if search_pattern(self.PREMIUM_PATTERN, self.data) else False
- m = re.search(self.VALID_UNTIL_PATTERN, self.data)
+ m = search_pattern(self.VALID_UNTIL_PATTERN, self.data)
if m is not None:
expiredate = m.group(1).strip()
self.log_debug("Expire date: " + expiredate)
try:
- validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y"))
+ validuntil = time.mktime(time.strptime(expiredate, self.VALID_UNTIL_FORMAT))
except Exception, e:
self.log_error(e)
@@ -107,7 +108,7 @@ def grab_info(self, user, password, data):
else:
self.log_debug("VALID UNTIL PATTERN not found")
- m = re.search(self.TRAFFIC_LEFT_PATTERN, self.data)
+ m = search_pattern(self.TRAFFIC_LEFT_PATTERN, self.data)
if m is not None:
try:
traffic = m.groupdict()
@@ -126,19 +127,17 @@ def grab_info(self, user, password, data):
unit = self.TRAFFIC_LEFT_UNIT
else:
- unit = ""
+ unit = None
- trafficleft = self.parse_traffic(size + unit)
+ trafficleft = max(0, self.parse_traffic(size, unit))
except Exception, e:
self.log_error(e)
else:
self.log_debug("TRAFFIC LEFT PATTERN not found")
- leech = [
- m.groupdict() for m in re.finditer(
- self.LEECH_TRAFFIC_PATTERN,
- self.data)]
+ leech = [m.groupdict()
+ for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, self.data)]
if leech:
leechtraffic = 0
try:
@@ -173,7 +172,7 @@ def grab_info(self, user, password, data):
def signin(self, user, password, data):
self.data = self.load(self.LOGIN_URL, cookies=self.COOKIES)
- if re.search(self.LOGIN_SKIP_PATTERN, self.data):
+ if search_pattern(self.LOGIN_SKIP_PATTERN, self.data):
self.skip_login()
action, inputs = parse_html_form('name="FL"', self.data)
@@ -185,7 +184,7 @@ def signin(self, user, password, data):
'password': password})
if action:
- url = urlparse.urljoin("http://", action)
+ url = urlparse.urljoin(self.LOGIN_URL, action)
else:
url = self.LOGIN_URL
@@ -193,14 +192,16 @@ def signin(self, user, password, data):
self.check_errors()
- def check_errors(self):
+ def check_errors(self, data=None):
self.log_info(_("Checking for link errors..."))
- if not self.data:
+ data = data or self.data
+
+ if not data:
self.log_warning(_("No data to check"))
return
- m = re.search(self.LOGIN_BAN_PATTERN, self.data)
+ m = search_pattern(self.LOGIN_BAN_PATTERN, data)
if m is not None:
try:
errmsg = m.group(1)
@@ -217,7 +218,7 @@ def check_errors(self):
self.fail_login(errmsg)
- m = re.search(self.LOGIN_FAIL_PATTERN, self.data)
+ m = search_pattern(self.LOGIN_FAIL_PATTERN, data)
if m is not None:
try:
errmsg = m.group(1)
diff --git a/module/plugins/internal/XFSHoster.py b/module/plugins/internal/XFSHoster.py
index 3facbfc883..f5ab727e8a 100644
--- a/module/plugins/internal/XFSHoster.py
+++ b/module/plugins/internal/XFSHoster.py
@@ -1,19 +1,21 @@
# -*- coding: utf-8 -*-
-import operator
import random
import re
+import urlparse
+from ..captcha.HCaptcha import HCaptcha
from ..captcha.ReCaptcha import ReCaptcha
from ..captcha.SolveMedia import SolveMedia
-from .misc import html_unescape, parse_time, seconds_to_midnight, set_cookie
+from ..captcha.Turnstile import Turnstile
+from .misc import html_unescape, parse_time, search_pattern, seconds_to_midnight, set_cookie
from .SimpleHoster import SimpleHoster
class XFSHoster(SimpleHoster):
__name__ = "XFSHoster"
__type__ = "hoster"
- __version__ = "0.81"
+ __version__ = "0.91"
__status__ = "stable"
__pattern__ = r'^unmatchable$'
@@ -27,12 +29,14 @@ class XFSHoster(SimpleHoster):
__license__ = "GPLv3"
__authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("GammaC0de", "nitzo2001[AT]yahoo[DOT]com")]
PLUGIN_DOMAIN = None
+ PLUGIN_URL = None
DIRECT_LINK = None
- # @NOTE: hould be set to `False` by default for safe, but I am lazy...
+ # @NOTE: should be set to `False` by default for safe, but I am lazy...
LEECH_HOSTER = True
NAME_PATTERN = r'(Filename[ ]*:[ ]*(