forked from pyload/pyload
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBase.py
More file actions
357 lines (288 loc) · 11.6 KB
/
Base.py
File metadata and controls
357 lines (288 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# -*- coding: utf-8 -*-
###############################################################################
# Copyright(c) 2008-2013 pyLoad Team
# http://www.pyload.org
#
# This file is part of pyLoad.
# pyLoad is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Subjected to the terms and conditions in LICENSE
#
# @author: RaNaN
###############################################################################
import sys
from time import time, sleep
from random import randint
from pyload.utils import decode
from pyload.utils.fs import exists, makedirs, join, remove
# TODO
# more attributes if needed
# get rid of catpcha & container plugins ?! (move to crypter & internals)
# adapt old plugins as needed
class Fail(Exception):
""" raised when failed """
class Retry(Exception):
""" raised when start again from beginning """
class Abort(Exception):
""" raised when aborted """
class Base(object):
"""
The Base plugin class with all shared methods and every possible attribute for plugin definition.
"""
#: Version as string or number
__version__ = "0.1"
# Type of the plugin, will be inherited and should not be set!
__type__ = ""
#: Regexp pattern which will be matched for download/crypter plugins
__pattern__ = r""
#: Internal addon plugin which is always loaded
__internal__ = False
#: When True this addon can be enabled by every user
__user_context__ = False
#: Config definition: list of (name, type, label, default_value) or
#: (name, label, desc, Input(...))
__config__ = tuple()
#: Short description, one liner
__description__ = ""
#: More detailed text
__explanation__ = """"""
#: List of needed modules
__dependencies__ = tuple()
#: Used to assign a category for addon plugins
__category__ = ""
#: Tags to categorize the plugin, see documentation for further info
__tags__ = tuple()
#: Base64 encoded .png icon, should be 32x32, please don't use sizes above ~2KB, for bigger icons use url.
__icon__ = ""
#: Alternative, link to png icon
__icon_url__ = ""
#: Domain name of the service
__domain__ = ""
#: Url with general information/support/discussion
__url__ = ""
#: Url to term of content, user is accepting these when using the plugin
__toc_url__ = ""
#: Url to service (to buy premium) for accounts
__ref_url__ = ""
__author__ = tuple()
__author_mail__ = tuple()
def __init__(self, core, owner=None):
self.__name__ = self.__class__.__name__
#: Core instance
self.core = core
#: logging instance
self.log = core.log
#: core config
self.config = core.config
#: :class:`EventManager`
self.evm = core.eventManager
#: :class:`InteractionManager`
self.im = core.interactionManager
if owner is not None:
#: :class:`Api`, user api when user is set
self.api = self.core.api.withUserContext(owner)
if not self.api:
raise Exception("Plugin running with invalid user")
#: :class:`User`, user related to this plugin
self.owner = self.api.user
else:
self.api = self.core.api
self.owner = None
#: last interaction task
self.task = None
#: js engine, see `JsEngine`
self.js = self.core.js
def __getitem__(self, item):
""" Retrieves meta data attribute """
return getattr(self, "__%s__" % item)
def logInfo(self, *args, **kwargs):
""" Print args to log at specific level
:param args: Arbitrary object which should be logged
:param kwargs: sep=(how to separate arguments), default = " | "
"""
self._log("info", *args, **kwargs)
def logWarning(self, *args, **kwargs):
self._log("warning", *args, **kwargs)
def logError(self, *args, **kwargs):
self._log("error", *args, **kwargs)
def logDebug(self, *args, **kwargs):
self._log("debug", *args, **kwargs)
def _log(self, level, *args, **kwargs):
if "sep" in kwargs:
sep = "%s" % kwargs["sep"]
else:
sep = " | "
strings = []
for obj in args:
if type(obj) == unicode:
strings.append(obj)
elif type(obj) == str:
strings.append(decode(obj))
else:
strings.append(str(obj))
getattr(self.log, level)("%s: %s" % (self.__name__, sep.join(strings)))
def getName(self):
""" Name of the plugin class """
return self.__name__
@property
def pattern(self):
""" Gives the compiled pattern of the plugin """
return self.core.pluginManager.getPlugin(self.__type__, self.__name__).re
def setConfig(self, option, value):
""" Set config value for current plugin """
self.core.config.set(self.__name__, option, value)
def getConf(self, option):
""" see `getConfig` """
return self.getConfig(option)
def getConfig(self, option):
""" Returns config value for current plugin """
return self.core.config.get(self.__name__, option)
def setStorage(self, key, value):
""" Saves a value persistently to the database """
self.core.db.setStorage(self.__name__, key, value)
def store(self, key, value):
""" same as `setStorage` """
self.core.db.setStorage(self.__name__, key, value)
def getStorage(self, key=None, default=None):
""" Retrieves saved value or dict of all saved entries if key is None """
if key is not None:
return self.core.db.getStorage(self.__name__, key) or default
return self.core.db.getStorage(self.__name__, key)
def retrieve(self, *args, **kwargs):
""" same as `getStorage` """
return self.getStorage(*args, **kwargs)
def delStorage(self, key):
""" Delete entry in db """
self.core.db.delStorage(self.__name__, key)
def shell(self):
""" open ipython shell """
if self.core.debug:
from IPython import embed
#noinspection PyUnresolvedReferences
sys.stdout = sys._stdout
embed()
def abort(self):
""" Check if plugin is in an abort state, is overwritten by subtypes"""
return False
def checkAbort(self):
""" Will be overwritten to determine if control flow should be aborted """
if self.abort(): raise Abort()
def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
"""Load content at url and returns it
:param url: url as string
:param get: GET as dict
:param post: POST as dict, list or string
:param ref: Set HTTP_REFERER header
:param cookies: use saved cookies
:param just_header: if True only the header will be retrieved and returned as dict
:param decode: Whether to decode the output according to http header, should be True in most cases
:return: Loaded content
"""
if not hasattr(self, "req"): raise Exception("Plugin type does not have Request attribute.")
self.checkAbort()
res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode)
if self.core.debug:
from inspect import currentframe
frame = currentframe()
if not exists(join("tmp", self.__name__)):
makedirs(join("tmp", self.__name__))
f = open(
join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
, "wb")
del frame # delete the frame or it wont be cleaned
try:
tmp = res.encode("utf8")
except:
tmp = res
f.write(tmp)
f.close()
if just_header:
#parse header
header = {"code": self.req.code}
for line in res.splitlines():
line = line.strip()
if not line or ":" not in line: continue
key, none, value = line.partition(":")
key = key.lower().strip()
value = value.strip()
if key in header:
if type(header[key]) == list:
header[key].append(value)
else:
header[key] = [header[key], value]
else:
header[key] = value
res = header
return res
def invalidTask(self):
if self.task:
self.task.invalid()
def invalidCaptcha(self):
self.logDebug("Deprecated method .invalidCaptcha, use .invalidTask")
self.invalidTask()
def correctTask(self):
if self.task:
self.task.correct()
def correctCaptcha(self):
self.logDebug("Deprecated method .correctCaptcha, use .correctTask")
self.correctTask()
def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
result_type='textual'):
""" Loads a captcha and decrypts it with ocr, plugin, user input
:param url: url of captcha image
:param get: get part for request
:param post: post part for request
:param cookies: True if cookies should be enabled
:param forceUser: if True, ocr is not used
:param imgtype: Type of the Image
:param result_type: 'textual' if text is written on the captcha\
or 'positional' for captcha where the user have to click\
on a specific region on the captcha
:return: result of decrypting
"""
img = self.load(url, get=get, post=post, cookies=cookies)
id = ("%.2f" % time())[-6:].replace(".", "")
temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb")
temp_file.write(img)
temp_file.close()
name = "%sOCR" % self.__name__
has_plugin = name in self.core.pluginManager.getPlugins("internal")
if self.core.captcha:
OCR = self.core.pluginManager.loadClass("internal", name)
else:
OCR = None
if OCR and not forceUser:
sleep(randint(3000, 5000) / 1000.0)
self.checkAbort()
ocr = OCR()
result = ocr.get_captcha(temp_file.name)
else:
task = self.im.createCaptchaTask(img, imgtype, temp_file.name, self.__name__, result_type)
self.task = task
while task.isWaiting():
if self.abort():
self.im.removeTask(task)
raise Abort()
sleep(1)
#TODO task handling
self.im.removeTask(task)
if task.error and has_plugin: #ignore default error message since the user could use OCR
self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting"))
elif task.error:
self.fail(task.error)
elif not task.result:
self.fail(_("No captcha result obtained in appropriate time."))
result = task.result
self.log.debug("Received captcha result: %s" % str(result))
if not self.core.debug:
try:
remove(temp_file.name)
except:
pass
return result
def fail(self, reason):
""" fail and give reason """
raise Fail(reason)