summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes39
-rw-r--r--.gitignore83
-rw-r--r--.hgignore36
-rw-r--r--CREDITS.md50
-rw-r--r--LICENSE619
-rw-r--r--LICENSE.md596
-rw-r--r--README88
-rw-r--r--README.md282
-rw-r--r--docs/Makefile158
-rw-r--r--docs/access_api.rst20
-rw-r--r--docs/build_docs.py245
-rw-r--r--docs/conf.py238
-rw-r--r--docs/docs.conf7
-rwxr-xr-xdocs/extend_pyload.rst7
-rw-r--r--docs/index.rst2
-rw-r--r--docs/module_overview.rst22
-rw-r--r--docs/plugin_licensing.rst18
-rw-r--r--docs/resources/banner.pngbin0 -> 18927 bytes
-rw-r--r--docs/resources/icon.icobin0 -> 5430 bytes
-rw-r--r--docs/resources/logo.pngbin0 -> 39315 bytes
-rw-r--r--docs/resources/logo.svg317
-rw-r--r--docs/setup.cfg (renamed from setup.cfg)0
-rw-r--r--docs/write_addons.rst172
-rw-r--r--docs/write_hooks.rst162
-rw-r--r--docs/write_plugins.rst46
-rw-r--r--docs/write_scripts.rst25
-rw-r--r--icons/abort.pngbin2569 -> 0 bytes
-rw-r--r--icons/add_small.pngbin3356 -> 0 bytes
-rw-r--r--icons/clipboard.pngbin1344 -> 0 bytes
-rw-r--r--icons/close.pngbin2569 -> 0 bytes
-rw-r--r--icons/edit_small.pngbin570 -> 0 bytes
-rw-r--r--icons/logo-gui.pngbin8093 -> 0 bytes
-rw-r--r--icons/logo.pngbin8172 -> 0 bytes
-rw-r--r--icons/pull_small.pngbin614 -> 0 bytes
-rw-r--r--icons/push_small.pngbin618 -> 0 bytes
-rw-r--r--icons/pyload-gui.icobin51122 -> 0 bytes
-rw-r--r--icons/pyload.icobin7206 -> 0 bytes
-rw-r--r--icons/pyload2.icobin9662 -> 0 bytes
-rw-r--r--icons/refresh1_small.pngbin3474 -> 0 bytes
-rw-r--r--icons/refresh_small.pngbin821 -> 0 bytes
-rw-r--r--icons/remove_small.pngbin287 -> 0 bytes
-rw-r--r--icons/toolbar_add.pngbin932 -> 0 bytes
-rw-r--r--icons/toolbar_pause.pngbin943 -> 0 bytes
-rw-r--r--icons/toolbar_remove.pngbin496 -> 0 bytes
-rw-r--r--icons/toolbar_start.pngbin1504 -> 0 bytes
-rw-r--r--icons/toolbar_stop.pngbin523 -> 0 bytes
-rw-r--r--lib/Python/Lib/BeautifulSoup.py (renamed from module/lib/BeautifulSoup.py)0
-rw-r--r--lib/Python/Lib/Getch.py (renamed from module/lib/Getch.py)0
-rw-r--r--lib/Python/Lib/MultipartPostHandler.py (renamed from module/lib/MultipartPostHandler.py)0
-rw-r--r--lib/Python/Lib/SafeEval.py (renamed from module/lib/SafeEval.py)0
-rw-r--r--lib/Python/Lib/Unzip.py (renamed from module/lib/Unzip.py)0
-rw-r--r--lib/Python/Lib/__init__.py (renamed from module/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/__init__.py (renamed from module/lib/beaker/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/cache.py (renamed from module/lib/beaker/cache.py)0
-rw-r--r--lib/Python/Lib/beaker/container.py (renamed from module/lib/beaker/container.py)0
-rw-r--r--lib/Python/Lib/beaker/converters.py (renamed from module/lib/beaker/converters.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/__init__.py (renamed from module/lib/beaker/crypto/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/jcecrypto.py (renamed from module/lib/beaker/crypto/jcecrypto.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/pbkdf2.py (renamed from module/lib/beaker/crypto/pbkdf2.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/pycrypto.py (renamed from module/lib/beaker/crypto/pycrypto.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/util.py (renamed from module/lib/beaker/crypto/util.py)0
-rw-r--r--lib/Python/Lib/beaker/exceptions.py (renamed from module/lib/beaker/exceptions.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/__init__.py (renamed from module/lib/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/database.py (renamed from module/lib/beaker/ext/database.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/google.py (renamed from module/lib/beaker/ext/google.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/memcached.py (renamed from module/lib/beaker/ext/memcached.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/sqla.py (renamed from module/lib/beaker/ext/sqla.py)0
-rw-r--r--lib/Python/Lib/beaker/middleware.py (renamed from module/lib/beaker/middleware.py)0
-rw-r--r--lib/Python/Lib/beaker/session.py (renamed from module/lib/beaker/session.py)0
-rw-r--r--lib/Python/Lib/beaker/synchronization.py (renamed from module/lib/beaker/synchronization.py)0
-rw-r--r--lib/Python/Lib/beaker/util.py (renamed from module/lib/beaker/util.py)0
-rw-r--r--lib/Python/Lib/bitmath/__init__.py1261
-rw-r--r--lib/Python/Lib/bitmath/integrations.py104
-rw-r--r--lib/Python/Lib/bottle.py (renamed from module/lib/bottle.py)0
-rw-r--r--lib/Python/Lib/colorama/__init__.py7
-rw-r--r--lib/Python/Lib/colorama/ansi.py99
-rw-r--r--lib/Python/Lib/colorama/ansitowin32.py228
-rw-r--r--lib/Python/Lib/colorama/initialise.py66
-rw-r--r--lib/Python/Lib/colorama/win32.py146
-rw-r--r--lib/Python/Lib/colorama/winterm.py151
-rw-r--r--lib/Python/Lib/colorlog/__init__.py14
-rw-r--r--lib/Python/Lib/colorlog/colorlog.py137
-rw-r--r--lib/Python/Lib/colorlog/escape_codes.py57
-rw-r--r--lib/Python/Lib/colorlog/logging.py44
-rw-r--r--lib/Python/Lib/feedparser.py (renamed from module/lib/feedparser.py)0
-rw-r--r--lib/Python/Lib/jinja2/__init__.py (renamed from module/lib/jinja2/__init__.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/__init__.py (renamed from module/lib/jinja2/_markupsafe/__init__.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_bundle.py (renamed from module/lib/jinja2/_markupsafe/_bundle.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_constants.py (renamed from module/lib/jinja2/_markupsafe/_constants.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_native.py (renamed from module/lib/jinja2/_markupsafe/_native.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/tests.py (renamed from module/lib/jinja2/_markupsafe/tests.py)0
-rw-r--r--lib/Python/Lib/jinja2/_stringdefs.py (renamed from module/lib/jinja2/_stringdefs.py)0
-rw-r--r--lib/Python/Lib/jinja2/bccache.py (renamed from module/lib/jinja2/bccache.py)0
-rw-r--r--lib/Python/Lib/jinja2/compiler.py (renamed from module/lib/jinja2/compiler.py)0
-rw-r--r--lib/Python/Lib/jinja2/constants.py (renamed from module/lib/jinja2/constants.py)0
-rw-r--r--lib/Python/Lib/jinja2/debug.py (renamed from module/lib/jinja2/debug.py)0
-rw-r--r--lib/Python/Lib/jinja2/defaults.py (renamed from module/lib/jinja2/defaults.py)0
-rw-r--r--lib/Python/Lib/jinja2/environment.py (renamed from module/lib/jinja2/environment.py)0
-rw-r--r--lib/Python/Lib/jinja2/exceptions.py (renamed from module/lib/jinja2/exceptions.py)0
-rw-r--r--lib/Python/Lib/jinja2/ext.py (renamed from module/lib/jinja2/ext.py)0
-rw-r--r--lib/Python/Lib/jinja2/filters.py (renamed from module/lib/jinja2/filters.py)0
-rw-r--r--lib/Python/Lib/jinja2/lexer.py (renamed from module/lib/jinja2/lexer.py)0
-rw-r--r--lib/Python/Lib/jinja2/loaders.py (renamed from module/lib/jinja2/loaders.py)0
-rw-r--r--lib/Python/Lib/jinja2/meta.py (renamed from module/lib/jinja2/meta.py)0
-rw-r--r--lib/Python/Lib/jinja2/nodes.py (renamed from module/lib/jinja2/nodes.py)0
-rw-r--r--lib/Python/Lib/jinja2/optimizer.py (renamed from module/lib/jinja2/optimizer.py)0
-rw-r--r--lib/Python/Lib/jinja2/parser.py (renamed from module/lib/jinja2/parser.py)0
-rw-r--r--lib/Python/Lib/jinja2/runtime.py (renamed from module/lib/jinja2/runtime.py)0
-rw-r--r--lib/Python/Lib/jinja2/sandbox.py (renamed from module/lib/jinja2/sandbox.py)0
-rw-r--r--lib/Python/Lib/jinja2/tests.py (renamed from module/lib/jinja2/tests.py)0
-rw-r--r--lib/Python/Lib/jinja2/utils.py (renamed from module/lib/jinja2/utils.py)0
-rw-r--r--lib/Python/Lib/jinja2/visitor.py (renamed from module/lib/jinja2/visitor.py)0
-rw-r--r--lib/Python/Lib/rename_process.py (renamed from module/lib/rename_process.py)0
-rw-r--r--lib/Python/Lib/send2trash/__init__.py19
-rw-r--r--lib/Python/Lib/send2trash/compat.py13
-rw-r--r--lib/Python/Lib/send2trash/plat_gio.py14
-rw-r--r--lib/Python/Lib/send2trash/plat_osx.py48
-rw-r--r--lib/Python/Lib/send2trash/plat_other.py160
-rw-r--r--lib/Python/Lib/send2trash/plat_win.py59
-rw-r--r--lib/Python/Lib/simplejson/__init__.py (renamed from module/lib/simplejson/__init__.py)0
-rw-r--r--lib/Python/Lib/simplejson/decoder.py (renamed from module/lib/simplejson/decoder.py)0
-rw-r--r--lib/Python/Lib/simplejson/encoder.py (renamed from module/lib/simplejson/encoder.py)0
-rw-r--r--lib/Python/Lib/simplejson/ordered_dict.py (renamed from module/lib/simplejson/ordered_dict.py)0
-rw-r--r--lib/Python/Lib/simplejson/scanner.py (renamed from module/lib/simplejson/scanner.py)0
-rw-r--r--lib/Python/Lib/simplejson/tool.py (renamed from module/lib/simplejson/tool.py)0
-rw-r--r--lib/Python/Lib/thrift/TSCons.py (renamed from module/lib/thrift/TSCons.py)0
-rw-r--r--lib/Python/Lib/thrift/TSerialization.py (renamed from module/lib/thrift/TSerialization.py)0
-rw-r--r--lib/Python/Lib/thrift/Thrift.py (renamed from module/lib/thrift/Thrift.py)0
-rw-r--r--lib/Python/Lib/thrift/__init__.py (renamed from module/lib/thrift/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TBase.py (renamed from module/lib/thrift/protocol/TBase.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TBinaryProtocol.py (renamed from module/lib/thrift/protocol/TBinaryProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TCompactProtocol.py (renamed from module/lib/thrift/protocol/TCompactProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TProtocol.py (renamed from module/lib/thrift/protocol/TProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/__init__.py (renamed from module/lib/thrift/protocol/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/server/THttpServer.py (renamed from module/lib/thrift/server/THttpServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TNonblockingServer.py (renamed from module/lib/thrift/server/TNonblockingServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TProcessPoolServer.py (renamed from module/lib/thrift/server/TProcessPoolServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TServer.py (renamed from module/lib/thrift/server/TServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/__init__.py (renamed from module/lib/thrift/server/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/THttpClient.py (renamed from module/lib/thrift/transport/THttpClient.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TSocket.py (renamed from module/lib/thrift/transport/TSocket.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TTransport.py (renamed from module/lib/thrift/transport/TTransport.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TTwisted.py (renamed from module/lib/thrift/transport/TTwisted.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TZlibTransport.py (renamed from module/lib/thrift/transport/TZlibTransport.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/__init__.py (renamed from module/lib/thrift/transport/__init__.py)0
-rw-r--r--lib/Python/Lib/wsgiserver/LICENSE.txt (renamed from module/lib/wsgiserver/LICENSE.txt)0
-rw-r--r--lib/Python/Lib/wsgiserver/__init__.py (renamed from module/lib/wsgiserver/__init__.py)0
-rw-r--r--locale/cli.pot182
-rw-r--r--locale/core.pot799
-rw-r--r--locale/crowdin.yaml15
-rw-r--r--locale/cs/LC_MESSAGES/django.mobin8921 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoad.mobin17425 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadCli.mobin4985 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadGui.mobin5810 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/setup.mobin11394 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/django.mobin9080 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoad.mobin18222 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoadCli.mobin5266 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoadGui.mobin6034 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/setup.mobin11467 -> 0 bytes
-rw-r--r--locale/django.pot693
-rwxr-xr-xlocale/en/LC_MESSAGES/django.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoad.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoadCli.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoadGui.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/setup.mobin434 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/django.mobin9001 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoad.mobin17919 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoadCli.mobin5139 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoadGui.mobin5973 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/setup.mobin11585 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/django.mobin8407 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoad.mobin17270 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadCli.mobin5363 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadGui.mobin5977 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/setup.mobin11849 -> 0 bytes
-rw-r--r--locale/gui.pot511
-rw-r--r--locale/it/LC_MESSAGES/django.mobin8770 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoad.mobin16689 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoadCli.mobin5113 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoadGui.mobin5830 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/setup.mobin11235 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/django.mobin7957 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoad.mobin16191 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadCli.mobin5102 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadGui.mobin5884 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/setup.mobin10634 -> 0 bytes
-rw-r--r--locale/pavement.py428
-rw-r--r--locale/pl/LC_MESSAGES/django.mobin7971 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoad.mobin16488 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadCli.mobin5119 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadGui.mobin5881 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/setup.mobin11215 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/django.mobin7957 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoad.mobin10427 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoadCli.mobin5121 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoadGui.mobin5825 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/setup.mobin8329 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/django.mobin9739 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoad.mobin15299 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadCli.mobin4299 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadGui.mobin7275 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/setup.mobin11323 -> 0 bytes
-rw-r--r--locale/setup.pot272
-rw-r--r--locale/sr/LC_MESSAGES/django.mobin9128 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoad.mobin19506 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadCli.mobin6004 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadGui.mobin6756 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/setup.mobin13146 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/django.mobin5358 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoad.mobin6860 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadCli.mobin3366 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadGui.mobin5238 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/setup.mobin10953 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadCli.mobin1298 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--module/Api.py1033
-rw-r--r--module/CaptchaManager.py158
-rw-r--r--module/ConfigParser.py392
-rw-r--r--module/HookManager.py315
-rw-r--r--module/InitHomeDir.py83
-rw-r--r--module/PluginThread.py677
-rw-r--r--module/PullEvents.py120
-rw-r--r--module/PyFile.py285
-rw-r--r--module/PyPackage.py80
-rw-r--r--module/Scheduler.py141
-rw-r--r--module/ThreadManager.py318
-rw-r--r--module/cli/AddPackage.py66
-rw-r--r--module/cli/Handler.py48
-rw-r--r--module/cli/ManageFiles.py204
-rw-r--r--module/cli/__init__.py2
-rw-r--r--module/cli/printer.py26
-rw-r--r--module/common/APIExerciser.py157
-rw-r--r--module/common/ImportDebugger.py19
-rw-r--r--module/common/JsEngine.py162
-rw-r--r--module/common/__init__.py2
-rw-r--r--module/common/json_layer.py13
-rw-r--r--module/common/packagetools.py155
-rw-r--r--module/common/pylgettext.py61
-rw-r--r--module/config/default.conf65
-rw-r--r--module/config/gui_default.xml13
-rw-r--r--module/database/DatabaseBackend.py352
-rw-r--r--module/database/FileDatabase.py944
-rw-r--r--module/database/StorageDatabase.py49
-rw-r--r--module/database/UserDatabase.py108
-rw-r--r--module/database/__init__.py6
-rw-r--r--module/debug.py95
-rw-r--r--module/forwarder.py73
-rw-r--r--module/gui/AccountEdit.py104
-rw-r--r--module/gui/Accounts.py213
-rw-r--r--module/gui/CNLServer.py226
-rw-r--r--module/gui/CaptchaDock.py94
-rw-r--r--module/gui/Collector.py407
-rw-r--r--module/gui/ConnectionManager.py302
-rw-r--r--module/gui/CoreConfigParser.py165
-rw-r--r--module/gui/LinkDock.py56
-rw-r--r--module/gui/MainWindow.py697
-rw-r--r--module/gui/Overview.py197
-rw-r--r--module/gui/PackageDock.py90
-rw-r--r--module/gui/Queue.py390
-rw-r--r--module/gui/SettingsWidget.py202
-rw-r--r--module/gui/XMLParser.py71
-rw-r--r--module/gui/__init__.py1
-rw-r--r--module/gui/connector.py165
-rw-r--r--module/lib/beaker/ext/__init__.py0
-rw-r--r--module/network/Browser.py146
-rw-r--r--module/network/Bucket.py61
-rw-r--r--module/network/CookieJar.py50
-rw-r--r--module/network/HTTPChunk.py293
-rw-r--r--module/network/HTTPDownload.py340
-rw-r--r--module/network/HTTPRequest.py306
-rw-r--r--module/network/RequestFactory.py126
-rw-r--r--module/network/XDCCRequest.py162
-rw-r--r--module/network/__init__.py1
-rw-r--r--module/plugins/Account.py292
-rw-r--r--module/plugins/AccountManager.py185
-rw-r--r--module/plugins/Container.py75
-rw-r--r--module/plugins/Crypter.py72
-rw-r--r--module/plugins/Hook.py161
-rw-r--r--module/plugins/Hoster.py33
-rw-r--r--module/plugins/Plugin.py617
-rw-r--r--module/plugins/PluginManager.py380
-rw-r--r--module/plugins/__init__.py0
-rw-r--r--module/plugins/accounts/AlldebridCom.py63
-rw-r--r--module/plugins/accounts/BackinNet.py16
-rw-r--r--module/plugins/accounts/BillionuploadsCom.py16
-rw-r--r--module/plugins/accounts/BitshareCom.py34
-rw-r--r--module/plugins/accounts/CatShareNet.py61
-rw-r--r--module/plugins/accounts/CloudzillaTo.py37
-rw-r--r--module/plugins/accounts/CramitIn.py16
-rw-r--r--module/plugins/accounts/CzshareCom.py54
-rw-r--r--module/plugins/accounts/DebridItaliaCom.py44
-rw-r--r--module/plugins/accounts/DepositfilesCom.py36
-rw-r--r--module/plugins/accounts/EasybytezCom.py19
-rw-r--r--module/plugins/accounts/EuroshareEu.py41
-rw-r--r--module/plugins/accounts/ExashareCom.py16
-rw-r--r--module/plugins/accounts/FastixRu.py41
-rw-r--r--module/plugins/accounts/FastshareCz.py50
-rw-r--r--module/plugins/accounts/File4SafeCom.py18
-rw-r--r--module/plugins/accounts/FileParadoxIn.py16
-rw-r--r--module/plugins/accounts/FilecloudIo.py59
-rw-r--r--module/plugins/accounts/FilefactoryCom.py48
-rw-r--r--module/plugins/accounts/FilejungleCom.py50
-rw-r--r--module/plugins/accounts/FileomCom.py16
-rw-r--r--module/plugins/accounts/FilerNet.py59
-rw-r--r--module/plugins/accounts/FilerioCom.py16
-rw-r--r--module/plugins/accounts/FilesMailRu.py31
-rw-r--r--module/plugins/accounts/FileserveCom.py44
-rw-r--r--module/plugins/accounts/FourSharedCom.py34
-rw-r--r--module/plugins/accounts/FreakshareCom.py51
-rw-r--r--module/plugins/accounts/FreeWayMe.py52
-rw-r--r--module/plugins/accounts/FshareVn.py62
-rw-r--r--module/plugins/accounts/Ftp.py17
-rw-r--r--module/plugins/accounts/HellshareCz.py79
-rw-r--r--module/plugins/accounts/Http.py17
-rw-r--r--module/plugins/accounts/HugefilesNet.py16
-rw-r--r--module/plugins/accounts/HundredEightyUploadCom.py16
-rw-r--r--module/plugins/accounts/JunkyvideoCom.py16
-rw-r--r--module/plugins/accounts/JunocloudMe.py16
-rw-r--r--module/plugins/accounts/Keep2ShareCc.py73
-rw-r--r--module/plugins/accounts/LetitbitNet.py34
-rw-r--r--module/plugins/accounts/LinestorageCom.py17
-rw-r--r--module/plugins/accounts/LinksnappyCom.py57
-rw-r--r--module/plugins/accounts/MegaDebridEu.py39
-rw-r--r--module/plugins/accounts/MegaRapidCz.py60
-rw-r--r--module/plugins/accounts/MegaRapidoNet.py57
-rw-r--r--module/plugins/accounts/MegasharesCom.py48
-rw-r--r--module/plugins/accounts/MovReelCom.py19
-rw-r--r--module/plugins/accounts/MultihostersCom.py16
-rw-r--r--module/plugins/accounts/MultishareCz.py44
-rw-r--r--module/plugins/accounts/MyfastfileCom.py37
-rw-r--r--module/plugins/accounts/NetloadIn.py63
-rw-r--r--module/plugins/accounts/NoPremiumPl.py81
-rw-r--r--module/plugins/accounts/NosuploadCom.py16
-rw-r--r--module/plugins/accounts/NovafileCom.py16
-rw-r--r--module/plugins/accounts/NowVideoSx.py56
-rw-r--r--module/plugins/accounts/OboomCom.py77
-rw-r--r--module/plugins/accounts/OneFichierCom.py58
-rw-r--r--module/plugins/accounts/OverLoadMe.py43
-rw-r--r--module/plugins/accounts/PremiumTo.py37
-rw-r--r--module/plugins/accounts/PremiumizeMe.py49
-rw-r--r--module/plugins/accounts/PutdriveCom.py16
-rw-r--r--module/plugins/accounts/QuickshareCz.py43
-rw-r--r--module/plugins/accounts/RPNetBiz.py51
-rw-r--r--module/plugins/accounts/RapideoPl.py80
-rw-r--r--module/plugins/accounts/RapidfileshareNet.py18
-rw-r--r--module/plugins/accounts/RapidgatorNet.py72
-rw-r--r--module/plugins/accounts/RapiduNet.py65
-rw-r--r--module/plugins/accounts/RarefileNet.py16
-rw-r--r--module/plugins/accounts/RealdebridCom.py40
-rw-r--r--module/plugins/accounts/RehostTo.py54
-rw-r--r--module/plugins/accounts/RyushareCom.py16
-rw-r--r--module/plugins/accounts/SafesharingEu.py16
-rw-r--r--module/plugins/accounts/SecureUploadEu.py16
-rw-r--r--module/plugins/accounts/SendmywayCom.py16
-rw-r--r--module/plugins/accounts/ShareonlineBiz.py65
-rw-r--r--module/plugins/accounts/SimplyPremiumCom.py48
-rw-r--r--module/plugins/accounts/SimplydebridCom.py35
-rw-r--r--module/plugins/accounts/SmoozedCom.py76
-rw-r--r--module/plugins/accounts/StahnuTo.py35
-rw-r--r--module/plugins/accounts/StreamcloudEu.py16
-rw-r--r--module/plugins/accounts/TurbobitNet.py43
-rw-r--r--module/plugins/accounts/TusfilesNet.py22
-rw-r--r--module/plugins/accounts/UlozTo.py49
-rw-r--r--module/plugins/accounts/UnrestrictLi.py44
-rw-r--r--module/plugins/accounts/UploadableCh.py34
-rw-r--r--module/plugins/accounts/UploadcCom.py16
-rw-r--r--module/plugins/accounts/UploadedTo.py71
-rw-r--r--module/plugins/accounts/UploadheroCom.py42
-rw-r--r--module/plugins/accounts/UploadingCom.py65
-rw-r--r--module/plugins/accounts/UptoboxCom.py18
-rw-r--r--module/plugins/accounts/VidPlayNet.py16
-rw-r--r--module/plugins/accounts/WebshareCz.py68
-rw-r--r--module/plugins/accounts/XFileSharingPro.py34
-rw-r--r--module/plugins/accounts/YibaishiwuCom.py40
-rw-r--r--module/plugins/accounts/ZeveraCom.py78
-rw-r--r--module/plugins/accounts/__init__.py0
-rw-r--r--module/plugins/captcha/GigasizeCom.py24
-rw-r--r--module/plugins/captcha/LinksaveIn.py157
-rw-r--r--module/plugins/captcha/NetloadIn.py29
-rw-r--r--module/plugins/captcha/OCR.py319
-rw-r--r--module/plugins/captcha/ShareonlineBiz.py39
-rw-r--r--module/plugins/captcha/__init__.py0
-rw-r--r--module/plugins/container/CCF.py47
-rw-r--r--module/plugins/container/DLC.py72
-rw-r--r--module/plugins/container/RSDF.py61
-rw-r--r--module/plugins/container/TXT.py69
-rw-r--r--module/plugins/container/__init__.py0
-rw-r--r--module/plugins/crypter/BitshareComFolder.py25
-rw-r--r--module/plugins/crypter/C1NeonCom.py19
-rw-r--r--module/plugins/crypter/ChipDe.py29
-rw-r--r--module/plugins/crypter/CloudzillaToFolder.py38
-rw-r--r--module/plugins/crypter/CrockoComFolder.py24
-rw-r--r--module/plugins/crypter/CryptItCom.py19
-rw-r--r--module/plugins/crypter/CzshareComFolder.py32
-rw-r--r--module/plugins/crypter/DailymotionComFolder.py105
-rw-r--r--module/plugins/crypter/DataHuFolder.py44
-rw-r--r--module/plugins/crypter/DdlstorageComFolder.py20
-rw-r--r--module/plugins/crypter/DepositfilesComFolder.py24
-rw-r--r--module/plugins/crypter/Dereferer.py17
-rw-r--r--module/plugins/crypter/DevhostStFolder.py65
-rw-r--r--module/plugins/crypter/DlProtectCom.py69
-rw-r--r--module/plugins/crypter/DontKnowMe.py17
-rw-r--r--module/plugins/crypter/DuckCryptInfo.py59
-rw-r--r--module/plugins/crypter/DuploadOrgFolder.py19
-rw-r--r--module/plugins/crypter/EasybytezComFolder.py23
-rw-r--r--module/plugins/crypter/EmbeduploadCom.py60
-rw-r--r--module/plugins/crypter/FilebeerInfoFolder.py19
-rw-r--r--module/plugins/crypter/FilecloudIoFolder.py25
-rw-r--r--module/plugins/crypter/FilecryptCc.py179
-rw-r--r--module/plugins/crypter/FilefactoryComFolder.py32
-rw-r--r--module/plugins/crypter/FilerNetFolder.py28
-rw-r--r--module/plugins/crypter/FileserveComFolder.py38
-rw-r--r--module/plugins/crypter/FilesonicComFolder.py19
-rw-r--r--module/plugins/crypter/FilestubeCom.py25
-rw-r--r--module/plugins/crypter/FiletramCom.py26
-rw-r--r--module/plugins/crypter/FiredriveComFolder.py19
-rw-r--r--module/plugins/crypter/FourChanOrg.py27
-rw-r--r--module/plugins/crypter/FreakhareComFolder.py42
-rw-r--r--module/plugins/crypter/FreetexthostCom.py31
-rw-r--r--module/plugins/crypter/FshareVnFolder.py24
-rw-r--r--module/plugins/crypter/Go4UpCom.py51
-rw-r--r--module/plugins/crypter/GooGl.py32
-rw-r--r--module/plugins/crypter/HoerbuchIn.py62
-rw-r--r--module/plugins/crypter/HotfileComFolder.py19
-rw-r--r--module/plugins/crypter/ILoadTo.py19
-rw-r--r--module/plugins/crypter/ImgurComAlbum.py31
-rw-r--r--module/plugins/crypter/LetitbitNetFolder.py33
-rw-r--r--module/plugins/crypter/LinkCryptWs.py322
-rw-r--r--module/plugins/crypter/LinkSaveIn.py22
-rw-r--r--module/plugins/crypter/LinkdecrypterCom.py69
-rw-r--r--module/plugins/crypter/LixIn.py62
-rw-r--r--module/plugins/crypter/LofCc.py19
-rw-r--r--module/plugins/crypter/MBLinkInfo.py20
-rw-r--r--module/plugins/crypter/MediafireComFolder.py58
-rw-r--r--module/plugins/crypter/MegaCoNzFolder.py32
-rw-r--r--module/plugins/crypter/MegaRapidCzFolder.py24
-rw-r--r--module/plugins/crypter/MegauploadComFolder.py19
-rw-r--r--module/plugins/crypter/Movie2KTo.py19
-rw-r--r--module/plugins/crypter/MultiUpOrg.py42
-rw-r--r--module/plugins/crypter/MultiloadCz.py42
-rw-r--r--module/plugins/crypter/MultiuploadCom.py19
-rw-r--r--module/plugins/crypter/NCryptIn.py310
-rw-r--r--module/plugins/crypter/NetfolderIn.py74
-rw-r--r--module/plugins/crypter/NosvideoCom.py25
-rw-r--r--module/plugins/crypter/OneKhDe.py42
-rw-r--r--module/plugins/crypter/OronComFolder.py19
-rw-r--r--module/plugins/crypter/PastebinCom.py25
-rw-r--r--module/plugins/crypter/QuickshareCzFolder.py31
-rw-r--r--module/plugins/crypter/RSLayerCom.py19
-rw-r--r--module/plugins/crypter/RelinkUs.py293
-rw-r--r--module/plugins/crypter/SafelinkingNet.py81
-rw-r--r--module/plugins/crypter/SecuredIn.py19
-rw-r--r--module/plugins/crypter/SexuriaCom.py94
-rw-r--r--module/plugins/crypter/ShareLinksBiz.py279
-rw-r--r--module/plugins/crypter/SharingmatrixComFolder.py19
-rw-r--r--module/plugins/crypter/SpeedLoadOrgFolder.py19
-rw-r--r--module/plugins/crypter/StealthTo.py19
-rw-r--r--module/plugins/crypter/TnyCz.py31
-rw-r--r--module/plugins/crypter/TrailerzoneInfo.py19
-rw-r--r--module/plugins/crypter/TurbobitNetFolder.py48
-rw-r--r--module/plugins/crypter/TusfilesNetFolder.py46
-rw-r--r--module/plugins/crypter/UlozToFolder.py46
-rw-r--r--module/plugins/crypter/UploadableChFolder.py28
-rw-r--r--module/plugins/crypter/UploadedToFolder.py37
-rw-r--r--module/plugins/crypter/WiiReloadedOrg.py19
-rw-r--r--module/plugins/crypter/WuploadComFolder.py19
-rw-r--r--module/plugins/crypter/XFileSharingProFolder.py52
-rw-r--r--module/plugins/crypter/XupPl.py25
-rw-r--r--module/plugins/crypter/YoutubeComFolder.py147
-rw-r--r--module/plugins/crypter/__init__.py0
-rw-r--r--module/plugins/hooks/AlldebridComHook.py27
-rw-r--r--module/plugins/hooks/AndroidPhoneNotify.py110
-rw-r--r--module/plugins/hooks/AntiVirus.py111
-rw-r--r--module/plugins/hooks/BypassCaptcha.py141
-rw-r--r--module/plugins/hooks/Captcha9Kw.py254
-rw-r--r--module/plugins/hooks/CaptchaBrotherhood.py172
-rw-r--r--module/plugins/hooks/Checksum.py196
-rw-r--r--module/plugins/hooks/ClickAndLoad.py94
-rw-r--r--module/plugins/hooks/DeathByCaptcha.py220
-rw-r--r--module/plugins/hooks/DebridItaliaComHook.py26
-rw-r--r--module/plugins/hooks/DeleteFinished.py81
-rw-r--r--module/plugins/hooks/DownloadScheduler.py81
-rw-r--r--module/plugins/hooks/EasybytezComHook.py30
-rw-r--r--module/plugins/hooks/ExpertDecoders.py103
-rw-r--r--module/plugins/hooks/ExternalScripts.py217
-rw-r--r--module/plugins/hooks/ExtractArchive.py561
-rw-r--r--module/plugins/hooks/FastixRuHook.py29
-rw-r--r--module/plugins/hooks/FreeWayMeHook.py32
-rw-r--r--module/plugins/hooks/HotFolder.py71
-rw-r--r--module/plugins/hooks/IRCInterface.py433
-rw-r--r--module/plugins/hooks/ImageTyperz.py159
-rw-r--r--module/plugins/hooks/JustPremium.py56
-rw-r--r--module/plugins/hooks/LinkdecrypterComHook.py26
-rw-r--r--module/plugins/hooks/LinksnappyComHook.py27
-rw-r--r--module/plugins/hooks/MegaDebridEuHook.py33
-rw-r--r--module/plugins/hooks/MegaRapidoNetHook.py81
-rw-r--r--module/plugins/hooks/MergeFiles.py84
-rw-r--r--module/plugins/hooks/MultiHome.py88
-rw-r--r--module/plugins/hooks/MultihostersComHook.py18
-rw-r--r--module/plugins/hooks/MultishareCzHook.py29
-rw-r--r--module/plugins/hooks/MyfastfileComHook.py28
-rw-r--r--module/plugins/hooks/NoPremiumPlHook.py29
-rw-r--r--module/plugins/hooks/OverLoadMeHook.py29
-rw-r--r--module/plugins/hooks/PremiumToHook.py27
-rw-r--r--module/plugins/hooks/PremiumizeMeHook.py38
-rw-r--r--module/plugins/hooks/PutdriveComHook.py18
-rw-r--r--module/plugins/hooks/RPNetBizHook.py36
-rw-r--r--module/plugins/hooks/RapideoPlHook.py29
-rw-r--r--module/plugins/hooks/RealdebridComHook.py27
-rw-r--r--module/plugins/hooks/RehostToHook.py27
-rw-r--r--module/plugins/hooks/RestartFailed.py46
-rw-r--r--module/plugins/hooks/SimplyPremiumComHook.py29
-rw-r--r--module/plugins/hooks/SimplydebridComHook.py24
-rw-r--r--module/plugins/hooks/SkipRev.py112
-rw-r--r--module/plugins/hooks/SmoozedComHook.py24
-rw-r--r--module/plugins/hooks/UnSkipOnFail.py97
-rw-r--r--module/plugins/hooks/UnrestrictLiHook.py28
-rw-r--r--module/plugins/hooks/UpdateManager.py329
-rw-r--r--module/plugins/hooks/UserAgentSwitcher.py47
-rw-r--r--module/plugins/hooks/WindowsPhoneNotify.py126
-rw-r--r--module/plugins/hooks/XFileSharingPro.py132
-rw-r--r--module/plugins/hooks/XMPPInterface.py252
-rw-r--r--module/plugins/hooks/ZeveraComHook.py25
-rw-r--r--module/plugins/hooks/__init__.py0
-rw-r--r--module/plugins/hoster/AlldebridCom.py54
-rw-r--r--module/plugins/hoster/AndroidfilehostCom.py64
-rw-r--r--module/plugins/hoster/BasePlugin.py103
-rw-r--r--module/plugins/hoster/BasketbuildCom.py62
-rw-r--r--module/plugins/hoster/BayfilesCom.py19
-rw-r--r--module/plugins/hoster/BezvadataCz.py95
-rw-r--r--module/plugins/hoster/BillionuploadsCom.py22
-rw-r--r--module/plugins/hoster/BitshareCom.py159
-rw-r--r--module/plugins/hoster/BoltsharingCom.py19
-rw-r--r--module/plugins/hoster/CatShareNet.py62
-rw-r--r--module/plugins/hoster/CloudzerNet.py21
-rw-r--r--module/plugins/hoster/CloudzillaTo.py62
-rw-r--r--module/plugins/hoster/CramitIn.py23
-rw-r--r--module/plugins/hoster/CrockoCom.py67
-rw-r--r--module/plugins/hoster/CyberlockerCh.py19
-rw-r--r--module/plugins/hoster/CzshareCom.py162
-rw-r--r--module/plugins/hoster/DailymotionCom.py125
-rw-r--r--module/plugins/hoster/DataHu.py35
-rw-r--r--module/plugins/hoster/DataportCz.py58
-rw-r--r--module/plugins/hoster/DateiTo.py83
-rw-r--r--module/plugins/hoster/DdlstorageCom.py20
-rw-r--r--module/plugins/hoster/DebridItaliaCom.py44
-rw-r--r--module/plugins/hoster/DepositfilesCom.py92
-rw-r--r--module/plugins/hoster/DevhostSt.py37
-rw-r--r--module/plugins/hoster/DlFreeFr.py140
-rw-r--r--module/plugins/hoster/DodanePl.py19
-rw-r--r--module/plugins/hoster/DropboxCom.py39
-rw-r--r--module/plugins/hoster/DuploadOrg.py19
-rw-r--r--module/plugins/hoster/EasybytezCom.py24
-rw-r--r--module/plugins/hoster/EdiskCz.py57
-rw-r--r--module/plugins/hoster/EgoFilesCom.py19
-rw-r--r--module/plugins/hoster/EnteruploadCom.py19
-rw-r--r--module/plugins/hoster/EpicShareNet.py19
-rw-r--r--module/plugins/hoster/EuroshareEu.py68
-rw-r--r--module/plugins/hoster/ExashareCom.py38
-rw-r--r--module/plugins/hoster/ExtabitCom.py78
-rw-r--r--module/plugins/hoster/FastixRu.py44
-rw-r--r--module/plugins/hoster/FastshareCz.py80
-rw-r--r--module/plugins/hoster/FileApeCom.py19
-rw-r--r--module/plugins/hoster/FileSharkPl.py117
-rw-r--r--module/plugins/hoster/FileStoreTo.py38
-rw-r--r--module/plugins/hoster/FilebeerInfo.py19
-rw-r--r--module/plugins/hoster/FilecloudIo.py126
-rw-r--r--module/plugins/hoster/FilefactoryCom.py85
-rw-r--r--module/plugins/hoster/FilejungleCom.py29
-rw-r--r--module/plugins/hoster/FileomCom.py33
-rw-r--r--module/plugins/hoster/FilepostCom.py124
-rw-r--r--module/plugins/hoster/FilepupNet.py48
-rw-r--r--module/plugins/hoster/FilerNet.py66
-rw-r--r--module/plugins/hoster/FilerioCom.py23
-rw-r--r--module/plugins/hoster/FilesMailRu.py105
-rw-r--r--module/plugins/hoster/FileserveCom.py216
-rw-r--r--module/plugins/hoster/FileshareInUa.py19
-rw-r--r--module/plugins/hoster/FilesonicCom.py20
-rw-r--r--module/plugins/hoster/FilezyNet.py19
-rw-r--r--module/plugins/hoster/FiredriveCom.py19
-rw-r--r--module/plugins/hoster/FlyFilesNet.py43
-rw-r--r--module/plugins/hoster/FourSharedCom.py63
-rw-r--r--module/plugins/hoster/FreakshareCom.py182
-rw-r--r--module/plugins/hoster/FreeWayMe.py54
-rw-r--r--module/plugins/hoster/FreevideoCz.py19
-rw-r--r--module/plugins/hoster/FshareVn.py110
-rw-r--r--module/plugins/hoster/Ftp.py77
-rw-r--r--module/plugins/hoster/GamefrontCom.py90
-rw-r--r--module/plugins/hoster/GigapetaCom.py66
-rw-r--r--module/plugins/hoster/GooIm.py38
-rw-r--r--module/plugins/hoster/GoogledriveCom.py64
-rw-r--r--module/plugins/hoster/HellshareCz.py36
-rw-r--r--module/plugins/hoster/HellspyCz.py19
-rw-r--r--module/plugins/hoster/HostujeNet.py49
-rw-r--r--module/plugins/hoster/HotfileCom.py22
-rw-r--r--module/plugins/hoster/HugefilesNet.py25
-rw-r--r--module/plugins/hoster/HundredEightyUploadCom.py21
-rw-r--r--module/plugins/hoster/IFileWs.py19
-rw-r--r--module/plugins/hoster/IcyFilesCom.py19
-rw-r--r--module/plugins/hoster/IfileIt.py19
-rw-r--r--module/plugins/hoster/IfolderRu.py67
-rw-r--r--module/plugins/hoster/JumbofilesCom.py37
-rw-r--r--module/plugins/hoster/JunocloudMe.py24
-rw-r--r--module/plugins/hoster/Keep2ShareCc.py118
-rw-r--r--module/plugins/hoster/KickloadCom.py19
-rw-r--r--module/plugins/hoster/KingfilesNet.py79
-rw-r--r--module/plugins/hoster/LemUploadsCom.py19
-rw-r--r--module/plugins/hoster/LetitbitNet.py136
-rw-r--r--module/plugins/hoster/LinksnappyCom.py58
-rw-r--r--module/plugins/hoster/LoadTo.py67
-rw-r--r--module/plugins/hoster/LolabitsEs.py48
-rw-r--r--module/plugins/hoster/LomafileCom.py20
-rw-r--r--module/plugins/hoster/LuckyShareNet.py75
-rw-r--r--module/plugins/hoster/MediafireCom.py63
-rw-r--r--module/plugins/hoster/MegaCoNz.py217
-rw-r--r--module/plugins/hoster/MegaDebridEu.py60
-rw-r--r--module/plugins/hoster/MegaFilesSe.py19
-rw-r--r--module/plugins/hoster/MegaRapidCz.py65
-rw-r--r--module/plugins/hoster/MegaRapidoNet.py54
-rw-r--r--module/plugins/hoster/MegacrypterCom.py57
-rw-r--r--module/plugins/hoster/MegareleaseOrg.py20
-rw-r--r--module/plugins/hoster/MegasharesCom.py112
-rw-r--r--module/plugins/hoster/MegauploadCom.py19
-rw-r--r--module/plugins/hoster/MegavideoCom.py20
-rw-r--r--module/plugins/hoster/MovReelCom.py21
-rw-r--r--module/plugins/hoster/MultihostersCom.py15
-rw-r--r--module/plugins/hoster/MultishareCz.py54
-rw-r--r--module/plugins/hoster/MyfastfileCom.py39
-rw-r--r--module/plugins/hoster/MystoreTo.py46
-rw-r--r--module/plugins/hoster/MyvideoDe.py49
-rw-r--r--module/plugins/hoster/NahrajCz.py19
-rw-r--r--module/plugins/hoster/NarodRu.py64
-rw-r--r--module/plugins/hoster/NetloadIn.py297
-rw-r--r--module/plugins/hoster/NitroflareCom.py101
-rw-r--r--module/plugins/hoster/NoPremiumPl.py104
-rw-r--r--module/plugins/hoster/NosuploadCom.py42
-rw-r--r--module/plugins/hoster/NovafileCom.py29
-rw-r--r--module/plugins/hoster/NowDownloadSx.py65
-rw-r--r--module/plugins/hoster/NowVideoSx.py45
-rw-r--r--module/plugins/hoster/OboomCom.py145
-rw-r--r--module/plugins/hoster/OneFichierCom.py62
-rw-r--r--module/plugins/hoster/OronCom.py20
-rw-r--r--module/plugins/hoster/OverLoadMe.py51
-rw-r--r--module/plugins/hoster/PandaplaNet.py19
-rw-r--r--module/plugins/hoster/PornhostCom.py79
-rw-r--r--module/plugins/hoster/PornhubCom.py89
-rw-r--r--module/plugins/hoster/PotloadCom.py19
-rw-r--r--module/plugins/hoster/PremiumTo.py56
-rw-r--r--module/plugins/hoster/PremiumizeMe.py61
-rw-r--r--module/plugins/hoster/PromptfileCom.py46
-rw-r--r--module/plugins/hoster/PrzeklejPl.py19
-rw-r--r--module/plugins/hoster/PutdriveCom.py15
-rw-r--r--module/plugins/hoster/QuickshareCz.py91
-rw-r--r--module/plugins/hoster/RPNetBiz.py81
-rw-r--r--module/plugins/hoster/RapideoPl.py104
-rw-r--r--module/plugins/hoster/RapidfileshareNet.py25
-rw-r--r--module/plugins/hoster/RapidgatorNet.py162
-rw-r--r--module/plugins/hoster/RapiduNet.py84
-rw-r--r--module/plugins/hoster/RarefileNet.py23
-rw-r--r--module/plugins/hoster/RealdebridCom.py56
-rw-r--r--module/plugins/hoster/RedtubeCom.py62
-rw-r--r--module/plugins/hoster/RemixshareCom.py58
-rw-r--r--module/plugins/hoster/RgHostNet.py28
-rw-r--r--module/plugins/hoster/SafesharingEu.py21
-rw-r--r--module/plugins/hoster/SecureUploadEu.py21
-rw-r--r--module/plugins/hoster/SendspaceCom.py60
-rw-r--r--module/plugins/hoster/Share4WebCom.py22
-rw-r--r--module/plugins/hoster/Share76Com.py19
-rw-r--r--module/plugins/hoster/ShareFilesCo.py19
-rw-r--r--module/plugins/hoster/SharebeesCom.py19
-rw-r--r--module/plugins/hoster/ShareonlineBiz.py184
-rw-r--r--module/plugins/hoster/ShareplaceCom.py88
-rw-r--r--module/plugins/hoster/SharingmatrixCom.py20
-rw-r--r--module/plugins/hoster/ShragleCom.py20
-rw-r--r--module/plugins/hoster/SimplyPremiumCom.py80
-rw-r--r--module/plugins/hoster/SimplydebridCom.py49
-rw-r--r--module/plugins/hoster/SmoozedCom.py64
-rw-r--r--module/plugins/hoster/SockshareCom.py21
-rw-r--r--module/plugins/hoster/SolidfilesCom.py33
-rw-r--r--module/plugins/hoster/SoundcloudCom.py56
-rw-r--r--module/plugins/hoster/SpeedLoadOrg.py19
-rw-r--r--module/plugins/hoster/SpeedfileCz.py19
-rw-r--r--module/plugins/hoster/SpeedyshareCom.py44
-rw-r--r--module/plugins/hoster/StorageTo.py19
-rw-r--r--module/plugins/hoster/StreamCz.py71
-rw-r--r--module/plugins/hoster/StreamcloudEu.py31
-rw-r--r--module/plugins/hoster/TurbobitNet.py168
-rw-r--r--module/plugins/hoster/TurbouploadCom.py19
-rw-r--r--module/plugins/hoster/TusfilesNet.py40
-rw-r--r--module/plugins/hoster/TwoSharedCom.py33
-rw-r--r--module/plugins/hoster/UlozTo.py152
-rw-r--r--module/plugins/hoster/UloziskoSk.py71
-rw-r--r--module/plugins/hoster/UnibytesCom.py75
-rw-r--r--module/plugins/hoster/UnrestrictLi.py86
-rw-r--r--module/plugins/hoster/UpleaCom.py66
-rw-r--r--module/plugins/hoster/UploadStationCom.py20
-rw-r--r--module/plugins/hoster/UploadableCh.py77
-rw-r--r--module/plugins/hoster/UploadboxCom.py19
-rw-r--r--module/plugins/hoster/UploadedTo.py124
-rw-r--r--module/plugins/hoster/UploadhereCom.py19
-rw-r--r--module/plugins/hoster/UploadheroCom.py70
-rw-r--r--module/plugins/hoster/UploadingCom.py97
-rw-r--r--module/plugins/hoster/UploadkingCom.py19
-rw-r--r--module/plugins/hoster/UpstoreNet.py73
-rw-r--r--module/plugins/hoster/UptoboxCom.py33
-rw-r--r--module/plugins/hoster/VeehdCom.py81
-rw-r--r--module/plugins/hoster/VeohCom.py54
-rw-r--r--module/plugins/hoster/VidPlayNet.py24
-rw-r--r--module/plugins/hoster/VimeoCom.py75
-rw-r--r--module/plugins/hoster/Vipleech4UCom.py19
-rw-r--r--module/plugins/hoster/WarserverCz.py19
-rw-r--r--module/plugins/hoster/WebshareCz.py64
-rw-r--r--module/plugins/hoster/WrzucTo.py51
-rw-r--r--module/plugins/hoster/WuploadCom.py20
-rw-r--r--module/plugins/hoster/X7To.py19
-rw-r--r--module/plugins/hoster/XFileSharingPro.py59
-rw-r--r--module/plugins/hoster/XHamsterCom.py128
-rw-r--r--module/plugins/hoster/XdadevelopersCom.py39
-rw-r--r--module/plugins/hoster/Xdcc.py208
-rw-r--r--module/plugins/hoster/YadiSk.py87
-rw-r--r--module/plugins/hoster/YibaishiwuCom.py59
-rw-r--r--module/plugins/hoster/YoupornCom.py60
-rw-r--r--module/plugins/hoster/YourfilesTo.py85
-rw-r--r--module/plugins/hoster/YoutubeCom.py191
-rw-r--r--module/plugins/hoster/ZDF.py59
-rw-r--r--module/plugins/hoster/ZShareNet.py20
-rw-r--r--module/plugins/hoster/ZippyshareCom.py94
-rw-r--r--module/plugins/hoster/__init__.py0
-rw-r--r--module/plugins/internal/DeadCrypter.py31
-rw-r--r--module/plugins/internal/DeadHoster.py31
-rw-r--r--module/plugins/internal/Extractor.py150
-rw-r--r--module/plugins/internal/MultiHook.py296
-rw-r--r--module/plugins/internal/MultiHoster.py116
-rw-r--r--module/plugins/internal/SevenZip.py153
-rw-r--r--module/plugins/internal/SimpleCrypter.py167
-rw-r--r--module/plugins/internal/SimpleDereferer.py97
-rw-r--r--module/plugins/internal/SimpleHoster.py761
-rw-r--r--module/plugins/internal/UnRar.py243
-rw-r--r--module/plugins/internal/UnZip.py72
-rw-r--r--module/plugins/internal/XFSAccount.py178
-rw-r--r--module/plugins/internal/XFSCrypter.py45
-rw-r--r--module/plugins/internal/XFSHoster.py329
-rw-r--r--module/plugins/internal/__init__.py0
-rw-r--r--module/remote/ClickAndLoadBackend.py170
-rw-r--r--module/remote/RemoteManager.py91
-rw-r--r--module/remote/SocketBackend.py25
-rw-r--r--module/remote/ThriftBackend.py56
-rw-r--r--module/remote/__init__.py2
-rw-r--r--module/remote/socketbackend/__init__.py2
-rw-r--r--module/remote/socketbackend/create_ttypes.py91
-rw-r--r--module/remote/socketbackend/ttypes.py383
-rw-r--r--module/remote/thriftbackend/Processor.py77
-rw-r--r--module/remote/thriftbackend/Protocol.py30
-rw-r--r--module/remote/thriftbackend/Socket.py129
-rw-r--r--module/remote/thriftbackend/ThriftClient.py109
-rw-r--r--module/remote/thriftbackend/ThriftTest.py92
-rw-r--r--module/remote/thriftbackend/Transport.py37
-rw-r--r--module/remote/thriftbackend/__init__.py0
-rw-r--r--module/remote/thriftbackend/pyload.thrift337
-rw-r--r--module/remote/thriftbackend/thriftgen/__init__.py0
-rwxr-xr-xmodule/remote/thriftbackend/thriftgen/pyload/Pyload-remote571
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/Pyload.py5534
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/__init__.py1
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/constants.py11
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/ttypes.py835
-rw-r--r--module/setup.py514
-rw-r--r--module/unescape.py3
-rw-r--r--module/utils.py201
-rw-r--r--module/web/ServerThread.py109
-rw-r--r--module/web/__init__.py0
-rw-r--r--module/web/api_app.py102
-rw-r--r--module/web/cnl_app.py164
-rw-r--r--module/web/filters.py62
-rw-r--r--module/web/json_app.py312
-rw-r--r--module/web/media/default/css/MooDialog.css92
-rw-r--r--module/web/media/default/css/default.css908
-rw-r--r--module/web/media/default/css/log.css72
-rw-r--r--module/web/media/default/css/pathchooser.css68
-rw-r--r--module/web/media/img/favicon.icobin7206 -> 0 bytes
-rw-r--r--module/web/media/js/MooDialog_static.js401
-rw-r--r--module/web/media/js/MooDropMenu_static.js89
-rw-r--r--module/web/media/js/mootools-core-1.4.1.js476
-rw-r--r--module/web/media/js/mootools-more-1.4.0.1.js216
-rw-r--r--module/web/media/js/package_ui.js377
-rw-r--r--module/web/media/js/purr_static.js308
-rw-r--r--module/web/media/js/settings.js3
-rw-r--r--module/web/media/js/tinytab_static.js50
-rw-r--r--module/web/middlewares.py133
-rw-r--r--module/web/pyload_app.py533
-rw-r--r--module/web/servers/lighttpd_default.conf153
-rw-r--r--module/web/servers/nginx_default.conf87
-rw-r--r--module/web/templates/500.html10
-rw-r--r--module/web/templates/default/admin.html98
-rw-r--r--module/web/templates/default/base.html180
-rw-r--r--module/web/templates/default/captcha.html42
-rw-r--r--module/web/templates/default/downloads.html29
-rw-r--r--module/web/templates/default/filemanager.html80
-rw-r--r--module/web/templates/default/folder.html15
-rw-r--r--module/web/templates/default/home.html266
-rw-r--r--module/web/templates/default/info.html81
-rw-r--r--module/web/templates/default/login.html36
-rw-r--r--module/web/templates/default/logout.html9
-rw-r--r--module/web/templates/default/logs.html41
-rw-r--r--module/web/templates/default/pathchooser.html76
-rw-r--r--module/web/templates/default/queue.html104
-rw-r--r--module/web/templates/default/settings.html204
-rw-r--r--module/web/templates/default/settings_item.html48
-rw-r--r--module/web/templates/default/setup.html13
-rw-r--r--module/web/templates/default/window.html46
-rw-r--r--module/web/utils.py137
-rw-r--r--module/web/webinterface.py157
-rw-r--r--pavement.py332
-rwxr-xr-xpyLoadCli.py590
-rwxr-xr-xpyLoadCore.py668
-rwxr-xr-xpyLoadGui.py765
-rw-r--r--pyload-cli.py7
-rwxr-xr-xpyload.py7
-rwxr-xr-xpyload/Core.py686
-rw-r--r--pyload/__init__.py104
-rw-r--r--pyload/api/__init__.py1052
-rw-r--r--pyload/api/types.py562
-rw-r--r--pyload/cli/AddPackage.py52
-rw-r--r--pyload/cli/Cli.py588
-rw-r--r--pyload/cli/Handler.py40
-rw-r--r--pyload/cli/ManageFiles.py178
-rw-r--r--pyload/cli/__init__.py4
-rw-r--r--pyload/config/Parser.py357
-rw-r--r--pyload/config/Setup.py568
-rw-r--r--pyload/config/__init__.py1
-rw-r--r--pyload/config/default.conf76
-rw-r--r--pyload/database/Backend.py329
-rw-r--r--pyload/database/File.py986
-rw-r--r--pyload/database/Storage.py37
-rw-r--r--pyload/database/User.py93
-rw-r--r--pyload/database/__init__.py6
-rw-r--r--pyload/datatype/File.py299
-rw-r--r--pyload/datatype/Package.py73
-rw-r--r--pyload/datatype/__init__.py1
-rw-r--r--pyload/manager/Account.py196
-rw-r--r--pyload/manager/Addon.py303
-rw-r--r--pyload/manager/Captcha.py158
-rw-r--r--pyload/manager/Event.py131
-rw-r--r--pyload/manager/Plugin.py404
-rw-r--r--pyload/manager/Remote.py85
-rw-r--r--pyload/manager/Thread.py315
-rw-r--r--pyload/manager/__init__.py1
-rw-r--r--pyload/manager/event/Scheduler.py140
-rw-r--r--pyload/manager/event/__init__.py1
-rw-r--r--pyload/manager/thread/Addon.py72
-rw-r--r--pyload/manager/thread/Decrypter.py107
-rw-r--r--pyload/manager/thread/Download.py213
-rw-r--r--pyload/manager/thread/Info.py223
-rw-r--r--pyload/manager/thread/Plugin.py133
-rw-r--r--pyload/manager/thread/Server.py122
-rw-r--r--pyload/manager/thread/__init__.py1
-rw-r--r--pyload/network/Browser.py151
-rw-r--r--pyload/network/Bucket.py49
-rw-r--r--pyload/network/CookieJar.py43
-rw-r--r--pyload/network/HTTPChunk.py318
-rw-r--r--pyload/network/HTTPDownload.py321
-rw-r--r--pyload/network/HTTPRequest.py321
-rw-r--r--pyload/network/JsEngine.py257
-rw-r--r--pyload/network/RequestFactory.py133
-rw-r--r--pyload/network/XDCCRequest.py149
-rw-r--r--pyload/network/__init__.py1
-rw-r--r--pyload/plugin/Account.py308
-rw-r--r--pyload/plugin/Addon.py189
-rw-r--r--pyload/plugin/Captcha.py34
-rw-r--r--pyload/plugin/Container.py66
-rw-r--r--pyload/plugin/Crypter.py107
-rw-r--r--pyload/plugin/Extractor.py154
-rw-r--r--pyload/plugin/Hook.py15
-rw-r--r--pyload/plugin/Hoster.py21
-rw-r--r--pyload/plugin/OCR.py322
-rw-r--r--pyload/plugin/Plugin.py777
-rw-r--r--pyload/plugin/__init__.py1
-rw-r--r--pyload/plugin/account/AlldebridCom.py63
-rw-r--r--pyload/plugin/account/BackinNet.py16
-rw-r--r--pyload/plugin/account/BillionuploadsCom.py16
-rw-r--r--pyload/plugin/account/BitshareCom.py34
-rw-r--r--pyload/plugin/account/CatShareNet.py61
-rw-r--r--pyload/plugin/account/CloudzillaTo.py37
-rw-r--r--pyload/plugin/account/CramitIn.py16
-rw-r--r--pyload/plugin/account/CzshareCom.py54
-rw-r--r--pyload/plugin/account/DebridItaliaCom.py44
-rw-r--r--pyload/plugin/account/DepositfilesCom.py36
-rw-r--r--pyload/plugin/account/DropboxCom.py36
-rw-r--r--pyload/plugin/account/EasybytezCom.py19
-rw-r--r--pyload/plugin/account/EuroshareEu.py41
-rw-r--r--pyload/plugin/account/ExashareCom.py16
-rw-r--r--pyload/plugin/account/FastixRu.py42
-rw-r--r--pyload/plugin/account/FastshareCz.py51
-rw-r--r--pyload/plugin/account/File4SafeCom.py18
-rw-r--r--pyload/plugin/account/FileParadoxIn.py16
-rw-r--r--pyload/plugin/account/FilecloudIo.py59
-rw-r--r--pyload/plugin/account/FilefactoryCom.py48
-rw-r--r--pyload/plugin/account/FilejungleCom.py50
-rw-r--r--pyload/plugin/account/FileomCom.py16
-rw-r--r--pyload/plugin/account/FilerNet.py59
-rw-r--r--pyload/plugin/account/FilerioCom.py16
-rw-r--r--pyload/plugin/account/FilesMailRu.py31
-rw-r--r--pyload/plugin/account/FileserveCom.py44
-rw-r--r--pyload/plugin/account/FourSharedCom.py35
-rw-r--r--pyload/plugin/account/FreakshareCom.py51
-rw-r--r--pyload/plugin/account/FreeWayMe.py52
-rw-r--r--pyload/plugin/account/FshareVn.py62
-rw-r--r--pyload/plugin/account/Ftp.py17
-rw-r--r--pyload/plugin/account/HellshareCz.py79
-rw-r--r--pyload/plugin/account/Http.py17
-rw-r--r--pyload/plugin/account/HugefilesNet.py16
-rw-r--r--pyload/plugin/account/HundredEightyUploadCom.py16
-rw-r--r--pyload/plugin/account/JunkyvideoCom.py16
-rw-r--r--pyload/plugin/account/JunocloudMe.py16
-rw-r--r--pyload/plugin/account/Keep2ShareCc.py73
-rw-r--r--pyload/plugin/account/LetitbitNet.py34
-rw-r--r--pyload/plugin/account/LinestorageCom.py17
-rw-r--r--pyload/plugin/account/LinksnappyCom.py57
-rw-r--r--pyload/plugin/account/MegaDebridEu.py39
-rw-r--r--pyload/plugin/account/MegaRapidCz.py60
-rw-r--r--pyload/plugin/account/MegaRapidoNet.py57
-rw-r--r--pyload/plugin/account/MegasharesCom.py48
-rw-r--r--pyload/plugin/account/MovReelCom.py19
-rw-r--r--pyload/plugin/account/MultihostersCom.py16
-rw-r--r--pyload/plugin/account/MultishareCz.py44
-rw-r--r--pyload/plugin/account/MyfastfileCom.py37
-rw-r--r--pyload/plugin/account/NetloadIn.py63
-rw-r--r--pyload/plugin/account/NoPremiumPl.py85
-rw-r--r--pyload/plugin/account/NosuploadCom.py16
-rw-r--r--pyload/plugin/account/NovafileCom.py16
-rw-r--r--pyload/plugin/account/NowVideoSx.py56
-rw-r--r--pyload/plugin/account/OboomCom.py79
-rw-r--r--pyload/plugin/account/OneFichierCom.py58
-rw-r--r--pyload/plugin/account/OverLoadMe.py43
-rw-r--r--pyload/plugin/account/PremiumTo.py37
-rw-r--r--pyload/plugin/account/PremiumizeMe.py49
-rw-r--r--pyload/plugin/account/PutdriveCom.py16
-rw-r--r--pyload/plugin/account/QuickshareCz.py43
-rw-r--r--pyload/plugin/account/RPNetBiz.py51
-rw-r--r--pyload/plugin/account/RapideoPl.py84
-rw-r--r--pyload/plugin/account/RapidfileshareNet.py18
-rw-r--r--pyload/plugin/account/RapidgatorNet.py72
-rw-r--r--pyload/plugin/account/RapiduNet.py65
-rw-r--r--pyload/plugin/account/RarefileNet.py16
-rw-r--r--pyload/plugin/account/RealdebridCom.py40
-rw-r--r--pyload/plugin/account/RehostTo.py54
-rw-r--r--pyload/plugin/account/RyushareCom.py16
-rw-r--r--pyload/plugin/account/SafesharingEu.py16
-rw-r--r--pyload/plugin/account/SecureUploadEu.py16
-rw-r--r--pyload/plugin/account/SendmywayCom.py16
-rw-r--r--pyload/plugin/account/ShareonlineBiz.py62
-rw-r--r--pyload/plugin/account/SimplyPremiumCom.py48
-rw-r--r--pyload/plugin/account/SimplydebridCom.py35
-rw-r--r--pyload/plugin/account/SmoozedCom.py78
-rw-r--r--pyload/plugin/account/StahnuTo.py35
-rw-r--r--pyload/plugin/account/StreamcloudEu.py16
-rw-r--r--pyload/plugin/account/TurbobitNet.py43
-rw-r--r--pyload/plugin/account/TusfilesNet.py22
-rw-r--r--pyload/plugin/account/UlozTo.py49
-rw-r--r--pyload/plugin/account/UnrestrictLi.py44
-rw-r--r--pyload/plugin/account/UploadableCh.py34
-rw-r--r--pyload/plugin/account/UploadcCom.py16
-rw-r--r--pyload/plugin/account/UploadedTo.py67
-rw-r--r--pyload/plugin/account/UploadheroCom.py42
-rw-r--r--pyload/plugin/account/UploadingCom.py65
-rw-r--r--pyload/plugin/account/UptoboxCom.py18
-rw-r--r--pyload/plugin/account/VidPlayNet.py16
-rw-r--r--pyload/plugin/account/WebshareCz.py68
-rw-r--r--pyload/plugin/account/XFileSharingPro.py34
-rw-r--r--pyload/plugin/account/YibaishiwuCom.py40
-rw-r--r--pyload/plugin/account/ZeveraCom.py73
-rw-r--r--pyload/plugin/account/__init__.py1
-rw-r--r--pyload/plugin/addon/AndroidPhoneNotify.py108
-rw-r--r--pyload/plugin/addon/AntiVirus.py107
-rw-r--r--pyload/plugin/addon/Checksum.py195
-rw-r--r--pyload/plugin/addon/ClickNLoad.py87
-rw-r--r--pyload/plugin/addon/DeleteFinished.py87
-rw-r--r--pyload/plugin/addon/DownloadScheduler.py72
-rw-r--r--pyload/plugin/addon/ExternalScripts.py215
-rw-r--r--pyload/plugin/addon/ExtractArchive.py562
-rw-r--r--pyload/plugin/addon/HotFolder.py74
-rw-r--r--pyload/plugin/addon/IRCInterface.py431
-rw-r--r--pyload/plugin/addon/JustPremium.py51
-rw-r--r--pyload/plugin/addon/MergeFiles.py80
-rw-r--r--pyload/plugin/addon/MultiHome.py84
-rw-r--r--pyload/plugin/addon/RestartFailed.py43
-rw-r--r--pyload/plugin/addon/SkipRev.py105
-rw-r--r--pyload/plugin/addon/UnSkipOnFail.py90
-rw-r--r--pyload/plugin/addon/UpdateManager.py325
-rw-r--r--pyload/plugin/addon/UserAgentSwitcher.py40
-rw-r--r--pyload/plugin/addon/WindowsPhoneNotify.py124
-rw-r--r--pyload/plugin/addon/XMPPInterface.py251
-rw-r--r--pyload/plugin/addon/__init__.py1
-rw-r--r--pyload/plugin/captcha/AdYouLike.py108
-rw-r--r--pyload/plugin/captcha/AdsCaptcha.py80
-rw-r--r--pyload/plugin/captcha/ReCaptcha.py199
-rw-r--r--pyload/plugin/captcha/SolveMedia.py114
-rw-r--r--pyload/plugin/captcha/__init__.py1
-rw-r--r--pyload/plugin/container/CCF.py47
-rw-r--r--pyload/plugin/container/DLC.py72
-rw-r--r--pyload/plugin/container/RSDF.py61
-rw-r--r--pyload/plugin/container/TXT.py69
-rw-r--r--pyload/plugin/container/__init__.py1
-rw-r--r--pyload/plugin/crypter/BitshareCom.py22
-rw-r--r--pyload/plugin/crypter/C1NeonCom.py16
-rw-r--r--pyload/plugin/crypter/ChipDe.py29
-rw-r--r--pyload/plugin/crypter/CloudzillaTo.py35
-rw-r--r--pyload/plugin/crypter/CrockoCom.py21
-rw-r--r--pyload/plugin/crypter/CryptItCom.py16
-rw-r--r--pyload/plugin/crypter/CzshareCom.py32
-rw-r--r--pyload/plugin/crypter/DailymotionComFolder.py105
-rw-r--r--pyload/plugin/crypter/DataHu.py41
-rw-r--r--pyload/plugin/crypter/DdlstorageCom.py17
-rw-r--r--pyload/plugin/crypter/DepositfilesCom.py21
-rw-r--r--pyload/plugin/crypter/Dereferer.py17
-rw-r--r--pyload/plugin/crypter/DevhostSt.py61
-rw-r--r--pyload/plugin/crypter/DlProtectCom.py66
-rw-r--r--pyload/plugin/crypter/DontKnowMe.py17
-rw-r--r--pyload/plugin/crypter/DuckCryptInfo.py59
-rw-r--r--pyload/plugin/crypter/DuploadOrg.py16
-rw-r--r--pyload/plugin/crypter/EasybytezCom.py20
-rw-r--r--pyload/plugin/crypter/EmbeduploadCom.py60
-rw-r--r--pyload/plugin/crypter/FilebeerInfo.py16
-rw-r--r--pyload/plugin/crypter/FilecloudIo.py22
-rw-r--r--pyload/plugin/crypter/FilecryptCc.py179
-rw-r--r--pyload/plugin/crypter/FilefactoryCom.py29
-rw-r--r--pyload/plugin/crypter/FilerNet.py25
-rw-r--r--pyload/plugin/crypter/FileserveCom.py38
-rw-r--r--pyload/plugin/crypter/FilesonicCom.py16
-rw-r--r--pyload/plugin/crypter/FilestubeCom.py22
-rw-r--r--pyload/plugin/crypter/FiletramCom.py23
-rw-r--r--pyload/plugin/crypter/FiredriveCom.py16
-rw-r--r--pyload/plugin/crypter/FourChanOrg.py27
-rw-r--r--pyload/plugin/crypter/FreakhareCom.py39
-rw-r--r--pyload/plugin/crypter/FreetexthostCom.py28
-rw-r--r--pyload/plugin/crypter/FshareVn.py21
-rw-r--r--pyload/plugin/crypter/Go4UpCom.py48
-rw-r--r--pyload/plugin/crypter/GooGl.py32
-rw-r--r--pyload/plugin/crypter/HoerbuchIn.py62
-rw-r--r--pyload/plugin/crypter/HotfileCom.py16
-rw-r--r--pyload/plugin/crypter/ILoadTo.py16
-rw-r--r--pyload/plugin/crypter/ImgurComAlbum.py28
-rw-r--r--pyload/plugin/crypter/LetitbitNet.py33
-rw-r--r--pyload/plugin/crypter/LinkCryptWs.py322
-rw-r--r--pyload/plugin/crypter/LinkSaveIn.py22
-rw-r--r--pyload/plugin/crypter/LinkdecrypterCom.py69
-rw-r--r--pyload/plugin/crypter/LixIn.py62
-rw-r--r--pyload/plugin/crypter/LofCc.py16
-rw-r--r--pyload/plugin/crypter/MBLinkInfo.py17
-rw-r--r--pyload/plugin/crypter/MediafireCom.py58
-rw-r--r--pyload/plugin/crypter/MegaCoNz.py29
-rw-r--r--pyload/plugin/crypter/MegaRapidCz.py21
-rw-r--r--pyload/plugin/crypter/MegauploadCom.py16
-rw-r--r--pyload/plugin/crypter/Movie2KTo.py16
-rw-r--r--pyload/plugin/crypter/MultiUpOrg.py39
-rw-r--r--pyload/plugin/crypter/MultiloadCz.py42
-rw-r--r--pyload/plugin/crypter/MultiuploadCom.py15
-rw-r--r--pyload/plugin/crypter/NCryptIn.py310
-rw-r--r--pyload/plugin/crypter/NetfolderIn.py71
-rw-r--r--pyload/plugin/crypter/NosvideoCom.py22
-rw-r--r--pyload/plugin/crypter/OneKhDe.py41
-rw-r--r--pyload/plugin/crypter/OronCom.py16
-rw-r--r--pyload/plugin/crypter/PastebinCom.py22
-rw-r--r--pyload/plugin/crypter/QuickshareCz.py31
-rw-r--r--pyload/plugin/crypter/RSLayerCom.py16
-rw-r--r--pyload/plugin/crypter/RelinkUs.py293
-rw-r--r--pyload/plugin/crypter/SafelinkingNet.py81
-rw-r--r--pyload/plugin/crypter/SecuredIn.py16
-rw-r--r--pyload/plugin/crypter/SexuriaCom.py94
-rw-r--r--pyload/plugin/crypter/ShareLinksBiz.py279
-rw-r--r--pyload/plugin/crypter/SharingmatrixCom.py16
-rw-r--r--pyload/plugin/crypter/SpeedLoadOrg.py16
-rw-r--r--pyload/plugin/crypter/StealthTo.py16
-rw-r--r--pyload/plugin/crypter/TnyCz.py28
-rw-r--r--pyload/plugin/crypter/TrailerzoneInfo.py16
-rw-r--r--pyload/plugin/crypter/TurbobitNet.py45
-rw-r--r--pyload/plugin/crypter/TusfilesNet.py43
-rw-r--r--pyload/plugin/crypter/UlozTo.py46
-rw-r--r--pyload/plugin/crypter/UploadableCh.py25
-rw-r--r--pyload/plugin/crypter/UploadedTo.py34
-rw-r--r--pyload/plugin/crypter/WiiReloadedOrg.py16
-rw-r--r--pyload/plugin/crypter/WuploadCom.py16
-rw-r--r--pyload/plugin/crypter/XFileSharingPro.py49
-rw-r--r--pyload/plugin/crypter/XupPl.py25
-rw-r--r--pyload/plugin/crypter/YoutubeComFolder.py147
-rw-r--r--pyload/plugin/crypter/__init__.py1
-rw-r--r--pyload/plugin/extractor/SevenZip.py154
-rw-r--r--pyload/plugin/extractor/UnRar.py245
-rw-r--r--pyload/plugin/extractor/UnZip.py74
-rw-r--r--pyload/plugin/extractor/__init__.py1
-rw-r--r--pyload/plugin/hook/AlldebridCom.py27
-rw-r--r--pyload/plugin/hook/BypassCaptcha.py135
-rw-r--r--pyload/plugin/hook/Captcha9Kw.py251
-rw-r--r--pyload/plugin/hook/CaptchaBrotherhood.py171
-rw-r--r--pyload/plugin/hook/DeathByCaptcha.py219
-rw-r--r--pyload/plugin/hook/DebridItaliaCom.py26
-rw-r--r--pyload/plugin/hook/EasybytezCom.py30
-rw-r--r--pyload/plugin/hook/ExpertDecoders.py102
-rw-r--r--pyload/plugin/hook/FastixRu.py29
-rw-r--r--pyload/plugin/hook/FreeWayMe.py32
-rw-r--r--pyload/plugin/hook/ImageTyperz.py153
-rw-r--r--pyload/plugin/hook/LinkdecrypterCom.py26
-rw-r--r--pyload/plugin/hook/LinksnappyCom.py27
-rw-r--r--pyload/plugin/hook/MegaDebridEu.py33
-rw-r--r--pyload/plugin/hook/MegaRapidoNet.py81
-rw-r--r--pyload/plugin/hook/MultihostersCom.py18
-rw-r--r--pyload/plugin/hook/MultishareCz.py29
-rw-r--r--pyload/plugin/hook/MyfastfileCom.py28
-rw-r--r--pyload/plugin/hook/NoPremiumPl.py29
-rw-r--r--pyload/plugin/hook/OverLoadMe.py29
-rw-r--r--pyload/plugin/hook/PremiumTo.py27
-rw-r--r--pyload/plugin/hook/PremiumizeMe.py38
-rw-r--r--pyload/plugin/hook/PutdriveCom.py18
-rw-r--r--pyload/plugin/hook/RPNetBiz.py36
-rw-r--r--pyload/plugin/hook/RapideoPl.py29
-rw-r--r--pyload/plugin/hook/RealdebridCom.py27
-rw-r--r--pyload/plugin/hook/RehostTo.py27
-rw-r--r--pyload/plugin/hook/SimplyPremiumCom.py29
-rw-r--r--pyload/plugin/hook/SimplydebridCom.py24
-rw-r--r--pyload/plugin/hook/SmoozedCom.py24
-rw-r--r--pyload/plugin/hook/UnrestrictLi.py28
-rw-r--r--pyload/plugin/hook/XFileSharingPro.py110
-rw-r--r--pyload/plugin/hook/ZeveraCom.py25
-rw-r--r--pyload/plugin/hook/__init__.py1
-rw-r--r--pyload/plugin/hoster/AlldebridCom.py51
-rw-r--r--pyload/plugin/hoster/AndroidfilehostCom.py61
-rw-r--r--pyload/plugin/hoster/BasketbuildCom.py59
-rw-r--r--pyload/plugin/hoster/BayfilesCom.py16
-rw-r--r--pyload/plugin/hoster/BezvadataCz.py92
-rw-r--r--pyload/plugin/hoster/BillionuploadsCom.py19
-rw-r--r--pyload/plugin/hoster/BitshareCom.py156
-rw-r--r--pyload/plugin/hoster/BoltsharingCom.py16
-rw-r--r--pyload/plugin/hoster/CatShareNet.py59
-rw-r--r--pyload/plugin/hoster/CloudzerNet.py18
-rw-r--r--pyload/plugin/hoster/CloudzillaTo.py59
-rw-r--r--pyload/plugin/hoster/CramitIn.py20
-rw-r--r--pyload/plugin/hoster/CrockoCom.py64
-rw-r--r--pyload/plugin/hoster/CyberlockerCh.py16
-rw-r--r--pyload/plugin/hoster/CzshareCom.py159
-rw-r--r--pyload/plugin/hoster/DailymotionCom.py125
-rw-r--r--pyload/plugin/hoster/DataHu.py32
-rw-r--r--pyload/plugin/hoster/DataportCz.py55
-rw-r--r--pyload/plugin/hoster/DateiTo.py80
-rw-r--r--pyload/plugin/hoster/DdlstorageCom.py17
-rw-r--r--pyload/plugin/hoster/DebridItaliaCom.py41
-rw-r--r--pyload/plugin/hoster/DepositfilesCom.py89
-rw-r--r--pyload/plugin/hoster/DevhostSt.py34
-rw-r--r--pyload/plugin/hoster/DlFreeFr.py138
-rw-r--r--pyload/plugin/hoster/DodanePl.py16
-rw-r--r--pyload/plugin/hoster/DuploadOrg.py16
-rw-r--r--pyload/plugin/hoster/EasybytezCom.py21
-rw-r--r--pyload/plugin/hoster/EdiskCz.py54
-rw-r--r--pyload/plugin/hoster/EgoFilesCom.py16
-rw-r--r--pyload/plugin/hoster/EnteruploadCom.py16
-rw-r--r--pyload/plugin/hoster/EpicShareNet.py16
-rw-r--r--pyload/plugin/hoster/EuroshareEu.py65
-rw-r--r--pyload/plugin/hoster/ExashareCom.py35
-rw-r--r--pyload/plugin/hoster/ExtabitCom.py75
-rw-r--r--pyload/plugin/hoster/FastixRu.py41
-rw-r--r--pyload/plugin/hoster/FastshareCz.py77
-rw-r--r--pyload/plugin/hoster/FileApeCom.py16
-rw-r--r--pyload/plugin/hoster/FileSharkPl.py114
-rw-r--r--pyload/plugin/hoster/FileStoreTo.py35
-rw-r--r--pyload/plugin/hoster/FilebeerInfo.py16
-rw-r--r--pyload/plugin/hoster/FilecloudIo.py123
-rw-r--r--pyload/plugin/hoster/FilefactoryCom.py85
-rw-r--r--pyload/plugin/hoster/FilejungleCom.py29
-rw-r--r--pyload/plugin/hoster/FileomCom.py30
-rw-r--r--pyload/plugin/hoster/FilepostCom.py121
-rw-r--r--pyload/plugin/hoster/FilepupNet.py45
-rw-r--r--pyload/plugin/hoster/FilerNet.py58
-rw-r--r--pyload/plugin/hoster/FilerioCom.py20
-rw-r--r--pyload/plugin/hoster/FilesMailRu.py105
-rw-r--r--pyload/plugin/hoster/FileserveCom.py216
-rw-r--r--pyload/plugin/hoster/FileshareInUa.py16
-rw-r--r--pyload/plugin/hoster/FilesonicCom.py17
-rw-r--r--pyload/plugin/hoster/FilezyNet.py16
-rw-r--r--pyload/plugin/hoster/FiredriveCom.py16
-rw-r--r--pyload/plugin/hoster/FlyFilesNet.py43
-rw-r--r--pyload/plugin/hoster/FourSharedCom.py60
-rw-r--r--pyload/plugin/hoster/FreakshareCom.py182
-rw-r--r--pyload/plugin/hoster/FreeWayMe.py51
-rw-r--r--pyload/plugin/hoster/FreevideoCz.py16
-rw-r--r--pyload/plugin/hoster/FshareVn.py110
-rw-r--r--pyload/plugin/hoster/Ftp.py75
-rw-r--r--pyload/plugin/hoster/GamefrontCom.py90
-rw-r--r--pyload/plugin/hoster/GigapetaCom.py60
-rw-r--r--pyload/plugin/hoster/GooIm.py35
-rw-r--r--pyload/plugin/hoster/GoogledriveCom.py61
-rw-r--r--pyload/plugin/hoster/HellshareCz.py33
-rw-r--r--pyload/plugin/hoster/HellspyCz.py16
-rw-r--r--pyload/plugin/hoster/HostujeNet.py46
-rw-r--r--pyload/plugin/hoster/HotfileCom.py19
-rw-r--r--pyload/plugin/hoster/HugefilesNet.py22
-rw-r--r--pyload/plugin/hoster/HundredEightyUploadCom.py18
-rw-r--r--pyload/plugin/hoster/IFileWs.py16
-rw-r--r--pyload/plugin/hoster/IcyFilesCom.py16
-rw-r--r--pyload/plugin/hoster/IfileIt.py16
-rw-r--r--pyload/plugin/hoster/IfolderRu.py63
-rw-r--r--pyload/plugin/hoster/JumbofilesCom.py34
-rw-r--r--pyload/plugin/hoster/JunocloudMe.py21
-rw-r--r--pyload/plugin/hoster/Keep2ShareCc.py115
-rw-r--r--pyload/plugin/hoster/KickloadCom.py16
-rw-r--r--pyload/plugin/hoster/KingfilesNet.py76
-rw-r--r--pyload/plugin/hoster/LemUploadsCom.py16
-rw-r--r--pyload/plugin/hoster/LetitbitNet.py136
-rw-r--r--pyload/plugin/hoster/LinksnappyCom.py55
-rw-r--r--pyload/plugin/hoster/LoadTo.py64
-rw-r--r--pyload/plugin/hoster/LolabitsEs.py45
-rw-r--r--pyload/plugin/hoster/LomafileCom.py17
-rw-r--r--pyload/plugin/hoster/LuckyShareNet.py74
-rw-r--r--pyload/plugin/hoster/MediafireCom.py60
-rw-r--r--pyload/plugin/hoster/MegaCoNz.py217
-rw-r--r--pyload/plugin/hoster/MegaDebridEu.py57
-rw-r--r--pyload/plugin/hoster/MegaFilesSe.py16
-rw-r--r--pyload/plugin/hoster/MegaRapidCz.py65
-rw-r--r--pyload/plugin/hoster/MegaRapidoNet.py54
-rw-r--r--pyload/plugin/hoster/MegacrypterCom.py57
-rw-r--r--pyload/plugin/hoster/MegareleaseOrg.py17
-rw-r--r--pyload/plugin/hoster/MegasharesCom.py109
-rw-r--r--pyload/plugin/hoster/MegauploadCom.py16
-rw-r--r--pyload/plugin/hoster/MegavideoCom.py17
-rw-r--r--pyload/plugin/hoster/MovReelCom.py18
-rw-r--r--pyload/plugin/hoster/MultihostersCom.py15
-rw-r--r--pyload/plugin/hoster/MultishareCz.py51
-rw-r--r--pyload/plugin/hoster/MyfastfileCom.py36
-rw-r--r--pyload/plugin/hoster/MystoreTo.py43
-rw-r--r--pyload/plugin/hoster/MyvideoDe.py49
-rw-r--r--pyload/plugin/hoster/NahrajCz.py16
-rw-r--r--pyload/plugin/hoster/NarodRu.py61
-rw-r--r--pyload/plugin/hoster/NetloadIn.py297
-rw-r--r--pyload/plugin/hoster/NitroflareCom.py101
-rw-r--r--pyload/plugin/hoster/NoPremiumPl.py104
-rw-r--r--pyload/plugin/hoster/NosuploadCom.py39
-rw-r--r--pyload/plugin/hoster/NovafileCom.py26
-rw-r--r--pyload/plugin/hoster/NowDownloadSx.py62
-rw-r--r--pyload/plugin/hoster/NowVideoSx.py42
-rw-r--r--pyload/plugin/hoster/OboomCom.py145
-rw-r--r--pyload/plugin/hoster/OneFichierCom.py59
-rw-r--r--pyload/plugin/hoster/OronCom.py17
-rw-r--r--pyload/plugin/hoster/OverLoadMe.py48
-rw-r--r--pyload/plugin/hoster/PandaplaNet.py16
-rw-r--r--pyload/plugin/hoster/PornhostCom.py81
-rw-r--r--pyload/plugin/hoster/PornhubCom.py89
-rw-r--r--pyload/plugin/hoster/PotloadCom.py16
-rw-r--r--pyload/plugin/hoster/PremiumTo.py53
-rw-r--r--pyload/plugin/hoster/PremiumizeMe.py58
-rw-r--r--pyload/plugin/hoster/PromptfileCom.py43
-rw-r--r--pyload/plugin/hoster/PrzeklejPl.py16
-rw-r--r--pyload/plugin/hoster/PutdriveCom.py15
-rw-r--r--pyload/plugin/hoster/QuickshareCz.py85
-rw-r--r--pyload/plugin/hoster/RPNetBiz.py78
-rw-r--r--pyload/plugin/hoster/RapideoPl.py104
-rw-r--r--pyload/plugin/hoster/RapidfileshareNet.py22
-rw-r--r--pyload/plugin/hoster/RapidgatorNet.py161
-rw-r--r--pyload/plugin/hoster/RapiduNet.py81
-rw-r--r--pyload/plugin/hoster/RarefileNet.py20
-rw-r--r--pyload/plugin/hoster/RealdebridCom.py53
-rw-r--r--pyload/plugin/hoster/RedtubeCom.py62
-rw-r--r--pyload/plugin/hoster/RehostTo.py26
-rw-r--r--pyload/plugin/hoster/RemixshareCom.py55
-rw-r--r--pyload/plugin/hoster/RgHostNet.py25
-rw-r--r--pyload/plugin/hoster/SafesharingEu.py18
-rw-r--r--pyload/plugin/hoster/SecureUploadEu.py18
-rw-r--r--pyload/plugin/hoster/SendspaceCom.py57
-rw-r--r--pyload/plugin/hoster/Share4WebCom.py18
-rw-r--r--pyload/plugin/hoster/Share76Com.py16
-rw-r--r--pyload/plugin/hoster/ShareFilesCo.py16
-rw-r--r--pyload/plugin/hoster/SharebeesCom.py16
-rw-r--r--pyload/plugin/hoster/ShareonlineBiz.py181
-rw-r--r--pyload/plugin/hoster/ShareplaceCom.py88
-rw-r--r--pyload/plugin/hoster/SharingmatrixCom.py17
-rw-r--r--pyload/plugin/hoster/ShragleCom.py17
-rw-r--r--pyload/plugin/hoster/SimplyPremiumCom.py77
-rw-r--r--pyload/plugin/hoster/SimplydebridCom.py46
-rw-r--r--pyload/plugin/hoster/SmoozedCom.py64
-rw-r--r--pyload/plugin/hoster/SockshareCom.py18
-rw-r--r--pyload/plugin/hoster/SolidfilesCom.py30
-rw-r--r--pyload/plugin/hoster/SoundcloudCom.py53
-rw-r--r--pyload/plugin/hoster/SpeedLoadOrg.py16
-rw-r--r--pyload/plugin/hoster/SpeedfileCz.py16
-rw-r--r--pyload/plugin/hoster/SpeedyshareCom.py41
-rw-r--r--pyload/plugin/hoster/StorageTo.py16
-rw-r--r--pyload/plugin/hoster/StreamCz.py71
-rw-r--r--pyload/plugin/hoster/StreamcloudEu.py28
-rw-r--r--pyload/plugin/hoster/TurbobitNet.py165
-rw-r--r--pyload/plugin/hoster/TurbouploadCom.py16
-rw-r--r--pyload/plugin/hoster/TusfilesNet.py37
-rw-r--r--pyload/plugin/hoster/TwoSharedCom.py30
-rw-r--r--pyload/plugin/hoster/UlozTo.py149
-rw-r--r--pyload/plugin/hoster/UloziskoSk.py68
-rw-r--r--pyload/plugin/hoster/UnibytesCom.py68
-rw-r--r--pyload/plugin/hoster/UnrestrictLi.py83
-rw-r--r--pyload/plugin/hoster/UpleaCom.py63
-rw-r--r--pyload/plugin/hoster/UploadStationCom.py17
-rw-r--r--pyload/plugin/hoster/UploadableCh.py74
-rw-r--r--pyload/plugin/hoster/UploadboxCom.py16
-rw-r--r--pyload/plugin/hoster/UploadedTo.py121
-rw-r--r--pyload/plugin/hoster/UploadhereCom.py16
-rw-r--r--pyload/plugin/hoster/UploadheroCom.py67
-rw-r--r--pyload/plugin/hoster/UploadingCom.py94
-rw-r--r--pyload/plugin/hoster/UploadkingCom.py16
-rw-r--r--pyload/plugin/hoster/UpstoreNet.py70
-rw-r--r--pyload/plugin/hoster/UptoboxCom.py30
-rw-r--r--pyload/plugin/hoster/VeehdCom.py81
-rw-r--r--pyload/plugin/hoster/VeohCom.py51
-rw-r--r--pyload/plugin/hoster/VidPlayNet.py21
-rw-r--r--pyload/plugin/hoster/VimeoCom.py72
-rw-r--r--pyload/plugin/hoster/Vipleech4UCom.py16
-rw-r--r--pyload/plugin/hoster/WarserverCz.py16
-rw-r--r--pyload/plugin/hoster/WebshareCz.py61
-rw-r--r--pyload/plugin/hoster/WrzucTo.py48
-rw-r--r--pyload/plugin/hoster/WuploadCom.py17
-rw-r--r--pyload/plugin/hoster/X7To.py16
-rw-r--r--pyload/plugin/hoster/XFileSharingPro.py56
-rw-r--r--pyload/plugin/hoster/XHamsterCom.py128
-rw-r--r--pyload/plugin/hoster/XVideosCom.py28
-rw-r--r--pyload/plugin/hoster/XdadevelopersCom.py37
-rw-r--r--pyload/plugin/hoster/Xdcc.py208
-rw-r--r--pyload/plugin/hoster/YadiSk.py84
-rw-r--r--pyload/plugin/hoster/YibaishiwuCom.py56
-rw-r--r--pyload/plugin/hoster/YoupornCom.py60
-rw-r--r--pyload/plugin/hoster/YourfilesTo.py85
-rw-r--r--pyload/plugin/hoster/YoutubeCom.py191
-rw-r--r--pyload/plugin/hoster/ZDF.py59
-rw-r--r--pyload/plugin/hoster/ZShareNet.py17
-rw-r--r--pyload/plugin/hoster/ZeveraCom.py32
-rw-r--r--pyload/plugin/hoster/ZippyshareCom.py92
-rw-r--r--pyload/plugin/hoster/__init__.py1
-rw-r--r--pyload/plugin/internal/BasePlugin.py98
-rw-r--r--pyload/plugin/internal/DeadCrypter.py27
-rw-r--r--pyload/plugin/internal/DeadHoster.py27
-rw-r--r--pyload/plugin/internal/MultiHook.py284
-rw-r--r--pyload/plugin/internal/MultiHoster.py116
-rw-r--r--pyload/plugin/internal/SimpleCrypter.py153
-rw-r--r--pyload/plugin/internal/SimpleDereferer.py94
-rw-r--r--pyload/plugin/internal/SimpleHoster.py757
-rw-r--r--pyload/plugin/internal/XFSAccount.py173
-rw-r--r--pyload/plugin/internal/XFSCrypter.py45
-rw-r--r--pyload/plugin/internal/XFSHoster.py325
-rw-r--r--pyload/plugin/internal/__init__.py1
-rw-r--r--pyload/plugin/ocr/GigasizeCom.py24
-rw-r--r--pyload/plugin/ocr/LinksaveIn.py157
-rw-r--r--pyload/plugin/ocr/NetloadIn.py29
-rw-r--r--pyload/plugin/ocr/ShareonlineBiz.py39
-rw-r--r--pyload/plugin/ocr/__init__.py1
-rw-r--r--pyload/remote/ClickNLoadBackend.py171
-rw-r--r--pyload/remote/SocketBackend.py26
-rw-r--r--pyload/remote/ThriftBackend.py45
-rw-r--r--pyload/remote/__init__.py3
-rw-r--r--pyload/remote/socketbackend/__init__.py1
-rw-r--r--pyload/remote/socketbackend/create_ttypes.py87
-rw-r--r--pyload/remote/thriftbackend/Processor.py81
-rw-r--r--pyload/remote/thriftbackend/Protocol.py33
-rw-r--r--pyload/remote/thriftbackend/Socket.py144
-rw-r--r--pyload/remote/thriftbackend/ThriftClient.py91
-rw-r--r--pyload/remote/thriftbackend/ThriftTest.py95
-rw-r--r--pyload/remote/thriftbackend/Transport.py45
-rw-r--r--pyload/remote/thriftbackend/__init__.py1
-rw-r--r--pyload/remote/thriftbackend/pyload.thrift337
-rw-r--r--pyload/remote/thriftbackend/thriftgen/__init__.py1
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote570
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py5976
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/__init__.py3
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/constants.py11
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py860
-rw-r--r--pyload/utils/__init__.py270
-rw-r--r--pyload/utils/packagetools.py155
-rw-r--r--pyload/utils/printer.py17
-rw-r--r--pyload/utils/pylgettext.py57
-rw-r--r--pyload/webui/__init__.py131
-rw-r--r--pyload/webui/app/__init__.py3
-rw-r--r--pyload/webui/app/api.py100
-rw-r--r--pyload/webui/app/cnl.py172
-rw-r--r--pyload/webui/app/json.py314
-rw-r--r--pyload/webui/app/pyloadweb.py530
-rw-r--r--pyload/webui/app/utils.py127
-rw-r--r--pyload/webui/filters.py69
-rw-r--r--pyload/webui/middlewares.py144
-rw-r--r--pyload/webui/servers/lighttpd_default.conf154
-rw-r--r--pyload/webui/servers/nginx_default.conf87
-rw-r--r--pyload/webui/themes/Dark/css/MooDialog.css94
-rw-r--r--pyload/webui/themes/Dark/css/base.css962
-rw-r--r--pyload/webui/themes/Dark/css/log.css75
-rw-r--r--pyload/webui/themes/Dark/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Dark/css/window.css92
-rw-r--r--pyload/webui/themes/Dark/img/add_folder.png (renamed from module/web/media/default/img/add_folder.png)bin571 -> 571 bytes
-rw-r--r--pyload/webui/themes/Dark/img/ajax-loader.gif (renamed from module/web/media/default/img/ajax-loader.gif)bin404 -> 404 bytes
-rw-r--r--pyload/webui/themes/Dark/img/arrow_refresh.png (renamed from module/web/media/default/img/arrow_refresh.png)bin685 -> 685 bytes
-rw-r--r--pyload/webui/themes/Dark/img/arrow_right.png (renamed from module/web/media/default/img/arrow_right.png)bin349 -> 349 bytes
-rw-r--r--pyload/webui/themes/Dark/img/big_button.gif (renamed from module/web/media/default/img/big_button.gif)bin1905 -> 1905 bytes
-rw-r--r--pyload/webui/themes/Dark/img/big_button_over.gif (renamed from module/web/media/default/img/big_button_over.gif)bin728 -> 728 bytes
-rw-r--r--pyload/webui/themes/Dark/img/body.png (renamed from module/web/media/default/img/body.png)bin402 -> 402 bytes
-rw-r--r--pyload/webui/themes/Dark/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Dark/img/closebtn.gif (renamed from module/web/media/default/img/closebtn.gif)bin254 -> 254 bytes
-rw-r--r--pyload/webui/themes/Dark/img/cog.png (renamed from module/web/media/default/img/cog.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_add.png (renamed from module/web/media/default/img/control_add.png)bin446 -> 446 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_add_blue.png (renamed from module/web/media/default/img/control_add_blue.png)bin845 -> 845 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_cancel.png (renamed from module/web/media/default/img/control_cancel.png)bin3349 -> 3349 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_cancel_blue.png (renamed from module/web/media/default/img/control_cancel_blue.png)bin787 -> 787 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_pause.png (renamed from module/web/media/default/img/control_pause.png)bin598 -> 598 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_pause_blue.png (renamed from module/web/media/default/img/control_pause_blue.png)bin721 -> 721 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_play.png (renamed from module/web/media/default/img/control_play.png)bin592 -> 592 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_play_blue.png (renamed from module/web/media/default/img/control_play_blue.png)bin717 -> 717 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_stop.png (renamed from module/web/media/default/img/control_stop.png)bin403 -> 403 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_stop_blue.png (renamed from module/web/media/default/img/control_stop_blue.png)bin695 -> 695 bytes
-rw-r--r--pyload/webui/themes/Dark/img/dark-bg.jpgbin0 -> 40930 bytes
-rw-r--r--pyload/webui/themes/Dark/img/delete.png (renamed from module/web/media/default/img/delete.png)bin715 -> 715 bytes
-rw-r--r--pyload/webui/themes/Dark/img/drag_corner.gif (renamed from module/web/media/default/img/drag_corner.gif)bin76 -> 76 bytes
-rw-r--r--pyload/webui/themes/Dark/img/error.png (renamed from module/web/media/default/img/error.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/Dark/img/folder.png (renamed from module/web/media/default/img/folder.png)bin537 -> 537 bytes
-rw-r--r--pyload/webui/themes/Dark/img/full.png (renamed from module/web/media/default/img/full.png)bin3543 -> 3543 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-login.png (renamed from module/web/media/default/img/head-login.png)bin1288 -> 1288 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-collector.png (renamed from module/web/media/default/img/head-menu-collector.png)bin1953 -> 1953 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-config.png (renamed from module/web/media/default/img/head-menu-config.png)bin1802 -> 1802 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-development.png (renamed from module/web/media/default/img/head-menu-development.png)bin876 -> 876 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-download.png (renamed from module/web/media/default/img/head-menu-download.png)bin721 -> 721 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-home.png (renamed from module/web/media/default/img/head-menu-home.png)bin920 -> 920 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-index.png (renamed from module/web/media/default/img/head-menu-index.png)bin482 -> 482 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-news.png (renamed from module/web/media/default/img/head-menu-news.png)bin628 -> 628 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-queue.png (renamed from module/web/media/default/img/head-menu-queue.png)bin2629 -> 2629 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-recent.png (renamed from module/web/media/default/img/head-menu-recent.png)bin932 -> 932 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-menu-wiki.png (renamed from module/web/media/default/img/head-menu-wiki.png)bin1204 -> 1204 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-search-noshadow.png (renamed from module/web/media/default/img/head-search-noshadow.png)bin1187 -> 1187 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head_bg1.png (renamed from module/web/media/default/img/head_bg1.png)bin125 -> 125 bytes
-rw-r--r--pyload/webui/themes/Dark/img/images.png (renamed from module/web/media/default/img/images.png)bin661 -> 661 bytes
-rw-r--r--pyload/webui/themes/Dark/img/notice.png (renamed from module/web/media/default/img/notice.png)bin778 -> 778 bytes
-rw-r--r--pyload/webui/themes/Dark/img/package_go.png (renamed from module/web/media/default/img/package_go.png)bin898 -> 898 bytes
-rw-r--r--pyload/webui/themes/Dark/img/page-tools-backlinks.png (renamed from module/web/media/default/img/page-tools-backlinks.png)bin540 -> 540 bytes
-rw-r--r--pyload/webui/themes/Dark/img/page-tools-edit.png (renamed from module/web/media/default/img/page-tools-edit.png)bin574 -> 574 bytes
-rw-r--r--pyload/webui/themes/Dark/img/page-tools-revisions.png (renamed from module/web/media/default/img/page-tools-revisions.png)bin603 -> 603 bytes
-rw-r--r--pyload/webui/themes/Dark/img/parseUri.png (renamed from module/web/media/default/img/parseUri.png)bin666 -> 666 bytes
-rw-r--r--pyload/webui/themes/Dark/img/pencil.png (renamed from module/web/media/default/img/pencil.png)bin450 -> 450 bytes
-rw-r--r--pyload/webui/themes/Dark/img/pyload-logo.pngbin0 -> 6947 bytes
-rw-r--r--pyload/webui/themes/Dark/img/reconnect.png (renamed from module/web/media/default/img/reconnect.png)bin755 -> 755 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_None.png (renamed from module/web/media/default/img/status_None.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_downloading.png (renamed from module/web/media/default/img/status_downloading.png)bin943 -> 943 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_failed.png (renamed from module/web/media/default/img/status_failed.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_finished.png (renamed from module/web/media/default/img/status_finished.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_offline.png (renamed from module/web/media/default/img/status_offline.png)bin700 -> 700 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_proc.png (renamed from module/web/media/default/img/status_proc.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_queue.png (renamed from module/web/media/default/img/status_queue.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_waiting.png (renamed from module/web/media/default/img/status_waiting.png)bin889 -> 889 bytes
-rw-r--r--pyload/webui/themes/Dark/img/success.png (renamed from module/web/media/default/img/success.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/Dark/img/tab-background.pngbin0 -> 3044 bytes
-rw-r--r--pyload/webui/themes/Dark/img/tabs-border-bottom.png (renamed from module/web/media/default/img/tabs-border-bottom.png)bin163 -> 163 bytes
-rw-r--r--pyload/webui/themes/Dark/img/user-actions-logout.png (renamed from module/web/media/default/img/user-actions-logout.png)bin799 -> 799 bytes
-rw-r--r--pyload/webui/themes/Dark/img/user-actions-profile.png (renamed from module/web/media/default/img/user-actions-profile.png)bin628 -> 628 bytes
-rw-r--r--pyload/webui/themes/Dark/img/user-info.png (renamed from module/web/media/default/img/user-info.png)bin3963 -> 3963 bytes
-rw-r--r--pyload/webui/themes/Dark/js/admin.coffee58
-rw-r--r--pyload/webui/themes/Dark/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Dark/js/base.coffee177
-rw-r--r--pyload/webui/themes/Dark/js/base.min.js3
-rw-r--r--pyload/webui/themes/Dark/js/package.js376
-rw-r--r--pyload/webui/themes/Dark/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Dark/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png (renamed from module/web/media/img/dialog-close.png)bin689 -> 689 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png (renamed from module/web/media/img/dialog-question.png)bin2073 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Dark/tml/admin.html98
-rw-r--r--pyload/webui/themes/Dark/tml/base.html177
-rw-r--r--pyload/webui/themes/Dark/tml/captcha.html42
-rw-r--r--pyload/webui/themes/Dark/tml/downloads.html29
-rw-r--r--pyload/webui/themes/Dark/tml/folder.html15
-rw-r--r--pyload/webui/themes/Dark/tml/home.html263
-rw-r--r--pyload/webui/themes/Dark/tml/info.html76
-rw-r--r--pyload/webui/themes/Dark/tml/login.html37
-rw-r--r--pyload/webui/themes/Dark/tml/logout.html9
-rw-r--r--pyload/webui/themes/Dark/tml/logs.html41
-rw-r--r--pyload/webui/themes/Dark/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/Dark/tml/queue.html104
-rw-r--r--pyload/webui/themes/Dark/tml/settings.html204
-rw-r--r--pyload/webui/themes/Dark/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/Dark/tml/window.html52
-rw-r--r--pyload/webui/themes/Default/css/base.css902
-rw-r--r--pyload/webui/themes/Default/css/log.css71
-rw-r--r--pyload/webui/themes/Default/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Default/css/window.css73
-rw-r--r--pyload/webui/themes/Default/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/Default/img/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/Default/img/arrow_refresh.pngbin0 -> 685 bytes
-rw-r--r--pyload/webui/themes/Default/img/arrow_right.pngbin0 -> 349 bytes
-rw-r--r--pyload/webui/themes/Default/img/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/Default/img/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/Default/img/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/Default/img/button.png (renamed from module/web/media/default/img/button.png)bin452 -> 452 bytes
-rw-r--r--pyload/webui/themes/Default/img/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/Default/img/cog.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_add.pngbin0 -> 446 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_add_blue.pngbin0 -> 845 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_cancel.pngbin0 -> 3349 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_cancel_blue.pngbin0 -> 787 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_pause.pngbin0 -> 598 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_pause_blue.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_play.pngbin0 -> 592 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_play_blue.pngbin0 -> 717 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_stop.pngbin0 -> 403 bytes
-rw-r--r--pyload/webui/themes/Default/img/control_stop_blue.pngbin0 -> 695 bytes
-rw-r--r--pyload/webui/themes/Default/img/delete.pngbin0 -> 715 bytes
-rw-r--r--pyload/webui/themes/Default/img/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/Default/img/error.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/Default/img/folder.pngbin0 -> 537 bytes
-rw-r--r--pyload/webui/themes/Default/img/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-login.pngbin0 -> 1288 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-collector.pngbin0 -> 1953 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-config.pngbin0 -> 1802 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-development.pngbin0 -> 876 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-download.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-home.pngbin0 -> 920 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-index.pngbin0 -> 482 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-news.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-queue.pngbin0 -> 2629 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-menu-wiki.pngbin0 -> 1204 bytes
-rw-r--r--pyload/webui/themes/Default/img/head-search-noshadow.pngbin0 -> 1187 bytes
-rw-r--r--pyload/webui/themes/Default/img/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/Default/img/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/Default/img/notice.pngbin0 -> 778 bytes
-rw-r--r--pyload/webui/themes/Default/img/package_go.pngbin0 -> 898 bytes
-rw-r--r--pyload/webui/themes/Default/img/page-tools-backlinks.pngbin0 -> 540 bytes
-rw-r--r--pyload/webui/themes/Default/img/page-tools-edit.pngbin0 -> 574 bytes
-rw-r--r--pyload/webui/themes/Default/img/page-tools-revisions.pngbin0 -> 603 bytes
-rw-r--r--pyload/webui/themes/Default/img/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/Default/img/pencil.pngbin0 -> 450 bytes
-rw-r--r--pyload/webui/themes/Default/img/pyload-logo.png (renamed from module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png)bin8457 -> 8457 bytes
-rw-r--r--pyload/webui/themes/Default/img/reconnect.pngbin0 -> 755 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_None.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_downloading.pngbin0 -> 943 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_failed.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_finished.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_offline.pngbin0 -> 700 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_proc.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_queue.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Default/img/status_waiting.pngbin0 -> 889 bytes
-rw-r--r--pyload/webui/themes/Default/img/success.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/Default/img/tab-background.png (renamed from module/web/media/default/img/tab-background.png)bin179 -> 179 bytes
-rw-r--r--pyload/webui/themes/Default/img/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/Default/img/user-actions-logout.pngbin0 -> 799 bytes
-rw-r--r--pyload/webui/themes/Default/img/user-actions-profile.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/Default/img/user-info.pngbin0 -> 3963 bytes
-rw-r--r--pyload/webui/themes/Default/js/admin.coffee58
-rw-r--r--pyload/webui/themes/Default/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Default/js/base.coffee177
-rw-r--r--pyload/webui/themes/Default/js/base.min.js3
-rw-r--r--pyload/webui/themes/Default/js/package.js376
-rw-r--r--pyload/webui/themes/Default/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Default/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Default/tml/admin.html98
-rw-r--r--pyload/webui/themes/Default/tml/base.html177
-rw-r--r--pyload/webui/themes/Default/tml/captcha.html42
-rw-r--r--pyload/webui/themes/Default/tml/downloads.html29
-rw-r--r--pyload/webui/themes/Default/tml/filemanager.html76
-rw-r--r--pyload/webui/themes/Default/tml/folder.html15
-rw-r--r--pyload/webui/themes/Default/tml/home.html263
-rw-r--r--pyload/webui/themes/Default/tml/info.html81
-rw-r--r--pyload/webui/themes/Default/tml/login.html36
-rw-r--r--pyload/webui/themes/Default/tml/logout.html9
-rw-r--r--pyload/webui/themes/Default/tml/logs.html41
-rw-r--r--pyload/webui/themes/Default/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/Default/tml/queue.html104
-rw-r--r--pyload/webui/themes/Default/tml/settings.html204
-rw-r--r--pyload/webui/themes/Default/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/Default/tml/window.html46
-rw-r--r--pyload/webui/themes/Flat/css/MooDialog.css85
-rw-r--r--pyload/webui/themes/Flat/css/base.css866
-rw-r--r--pyload/webui/themes/Flat/css/log.css72
-rw-r--r--pyload/webui/themes/Flat/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Flat/css/window.css70
-rw-r--r--pyload/webui/themes/Flat/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/Flat/img/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/Flat/img/arrow_refresh.pngbin0 -> 119032 bytes
-rw-r--r--pyload/webui/themes/Flat/img/arrow_right.pngbin0 -> 136967 bytes
-rw-r--r--pyload/webui/themes/Flat/img/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/Flat/img/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/Flat/img/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/Flat/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Flat/img/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/Flat/img/cog.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_add.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_add_blue.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_cancel.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_cancel_blue.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_pause.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_pause_blue.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_play.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_play_blue.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_stop.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/Flat/img/control_stop_blue.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/Flat/img/delete.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/Flat/img/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/Flat/img/error.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/Flat/img/folder.pngbin0 -> 134669 bytes
-rw-r--r--pyload/webui/themes/Flat/img/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-login.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-collector.pngbin0 -> 134985 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-config.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-development.pngbin0 -> 135818 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-download.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-home.pngbin0 -> 139387 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-index.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-news.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-queue.pngbin0 -> 136269 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-menu-wiki.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head-search-noshadow.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/Flat/img/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/Flat/img/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/Flat/img/notice.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/Flat/img/package_go.pngbin0 -> 136299 bytes
-rw-r--r--pyload/webui/themes/Flat/img/page-tools-backlinks.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/page-tools-edit.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/page-tools-revisions.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/Flat/img/pencil.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/Flat/img/reconnect.pngbin0 -> 3063 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_None.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_downloading.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_failed.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_finished.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_offline.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_proc.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_queue.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/status_waiting.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/success.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/Flat/img/tab-background.pngbin0 -> 179 bytes
-rw-r--r--pyload/webui/themes/Flat/img/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/Flat/img/user-actions-logout.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/user-actions-profile.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/user-info.pngbin0 -> 3080 bytes
-rw-r--r--pyload/webui/themes/Flat/js/admin.coffee58
-rw-r--r--pyload/webui/themes/Flat/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Flat/js/base.coffee177
-rw-r--r--pyload/webui/themes/Flat/js/base.min.js3
-rw-r--r--pyload/webui/themes/Flat/js/filemanager.js291
-rw-r--r--pyload/webui/themes/Flat/js/package.js376
-rw-r--r--pyload/webui/themes/Flat/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Flat/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Flat/tml/admin.html98
-rw-r--r--pyload/webui/themes/Flat/tml/base.html180
-rw-r--r--pyload/webui/themes/Flat/tml/captcha.html42
-rw-r--r--pyload/webui/themes/Flat/tml/downloads.html29
-rw-r--r--pyload/webui/themes/Flat/tml/filemanager.html78
-rw-r--r--pyload/webui/themes/Flat/tml/folder.html15
-rw-r--r--pyload/webui/themes/Flat/tml/home.html266
-rw-r--r--pyload/webui/themes/Flat/tml/info.html81
-rw-r--r--pyload/webui/themes/Flat/tml/login.html36
-rw-r--r--pyload/webui/themes/Flat/tml/logout.html9
-rw-r--r--pyload/webui/themes/Flat/tml/logs.html41
-rw-r--r--pyload/webui/themes/Flat/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/Flat/tml/queue.html104
-rw-r--r--pyload/webui/themes/Flat/tml/settings.html204
-rw-r--r--pyload/webui/themes/Flat/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/Flat/tml/window.html46
-rw-r--r--pyload/webui/themes/Next/css/MooDialog.css92
-rw-r--r--pyload/webui/themes/Next/css/log.css112
-rw-r--r--pyload/webui/themes/Next/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Next/css/window.css (renamed from module/web/media/default/css/window.css)0
-rw-r--r--pyload/webui/themes/Next/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/Next/img/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/Next/img/arrow_refresh.pngbin0 -> 685 bytes
-rw-r--r--pyload/webui/themes/Next/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Next/img/control_cancel.pngbin0 -> 3349 bytes
-rw-r--r--pyload/webui/themes/Next/img/delete.pngbin0 -> 715 bytes
-rw-r--r--pyload/webui/themes/Next/img/package_go.pngbin0 -> 898 bytes
-rw-r--r--pyload/webui/themes/Next/img/pencil.pngbin0 -> 450 bytes
-rw-r--r--pyload/webui/themes/Next/img/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/Next/js/admin.coffee (renamed from module/web/media/js/admin.coffee)0
-rw-r--r--pyload/webui/themes/Next/js/admin.js (renamed from module/web/media/js/admin.js)0
-rw-r--r--pyload/webui/themes/Next/js/base.coffee (renamed from module/web/media/js/base.coffee)0
-rw-r--r--pyload/webui/themes/Next/js/base.js (renamed from module/web/media/js/base.js)0
-rw-r--r--pyload/webui/themes/Next/js/filemanager.js (renamed from module/web/templates/default/filemanager_ui.js)0
-rw-r--r--pyload/webui/themes/Next/js/package.js397
-rw-r--r--pyload/webui/themes/Next/js/settings.coffee (renamed from module/web/media/js/settings.coffee)0
-rw-r--r--pyload/webui/themes/Next/js/settings.js3
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css476
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map1
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css5
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css6566
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map1
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css5
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eotbin0 -> 20127 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg288
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttfbin0 -> 45404 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woffbin0 -> 23424 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2bin0 -> 18028 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js2306
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js7
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/npm.js13
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Next/tml/admin.html100
-rw-r--r--pyload/webui/themes/Next/tml/base.html199
-rw-r--r--pyload/webui/themes/Next/tml/captcha.html40
-rw-r--r--pyload/webui/themes/Next/tml/downloads.html29
-rw-r--r--pyload/webui/themes/Next/tml/filemanager.html80
-rw-r--r--pyload/webui/themes/Next/tml/folder.html15
-rw-r--r--pyload/webui/themes/Next/tml/home.html277
-rw-r--r--pyload/webui/themes/Next/tml/info.html81
-rw-r--r--pyload/webui/themes/Next/tml/login.html36
-rw-r--r--pyload/webui/themes/Next/tml/logout.html9
-rw-r--r--pyload/webui/themes/Next/tml/logs.html41
-rw-r--r--pyload/webui/themes/Next/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/Next/tml/queue.html109
-rw-r--r--pyload/webui/themes/Next/tml/settings.html212
-rw-r--r--pyload/webui/themes/Next/tml/settings_item.html50
-rw-r--r--pyload/webui/themes/Next/tml/window.html46
-rw-r--r--scripts/Readme.txt25
-rwxr-xr-xsetup.py106
-rw-r--r--systemCheck.py142
-rw-r--r--testlinks.txt26
-rw-r--r--tests/APIExerciser.py164
-rw-r--r--tests/clonedigger.sh4
-rw-r--r--tests/code_analysis.sh15
-rw-r--r--tests/test_api.py14
-rw-r--r--tests/test_json.py13
1798 files changed, 165637 insertions, 62410 deletions
diff --git a/.gitattributes b/.gitattributes
index e34533a0d..d3aa9bed2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,21 +1,38 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
-# Explicitly declare text files we want to always be normalized and converted
+# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
-*.py text
-*.js text
-*.po text
+*.bat text
+*.c text
*.cfg text
-*.md text
+*.cmd text
+*.coffee text
+*.conf text
+*.htm text
+*.html text
*.in text
-*.txt text
-*.sh text
+*.js text
*.json text
-*.html text
-.htm text
+*.md text
+*.po text
+*.pot text
+*.py text
+*.rst text
+*.sh text
+*.thrift text
+*.txt text
+*.xml text
+*.yaml text
# Denote all files that are truly binary and should not be modified.
-*.png binary
+*.gif binary
+*.ico binary
*.jpg binary
-*.mo binary \ No newline at end of file
+*.mo binary
+*.pyc binary
+*.pyx binary
+*.png binary
+*.rar binary
+*.svg binary
+*.zip binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..14e8aa543
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+# Common packaging directories
+bin/
+build/
+dist/
+env/
+lib64/
+parts/
+sdist/
+var/
+temp/
+
+# Common directories
+.*/
+__pycache__/
+Downloads/
+Logs/
+tmp/
+userplugins/
+
+# Common files
+*.backup
+*.db
+*.pid
+/*.conf
+/*.version
+/*.txt
+/*.xml
+/*.zip
+/pyload/config/core.xml
+/pyload/config/plugin.xml
+
+# Common MacOS & Windows files
+*.DS_Store
+Thumbs.db
+desktop.ini
+
+# Certificate files
+cert.pem
+ssl.crt
+ssl.key
+
+# Common compiled files
+*.mo
+*.so
+*.py[cod]
+
+# Installer files
+.installed.cfg
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Python Eggs
+*.egg
+*.egg-info
+develop-eggs
+eggs
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# PyCharm
+.idea
+
+# Other development stuff
+.svn
+*~
+*.orig
+*.pidaproject
+*.prefs
+*.project
+*.pydevproject
+*.rej
+_build/
+module/
+paver-minilib.zip
+/setup.sh
+/tesseract/
diff --git a/.hgignore b/.hgignore
deleted file mode 100644
index 1215b241d..000000000
--- a/.hgignore
+++ /dev/null
@@ -1,36 +0,0 @@
-# ignoreing unneeded files, using glob syntax
-syntax: glob
-*.pyc
-*~
-*.pidaproject
-.svn
-*.DS_Store
-*.egg-info
-*.project
-*.pydevproject
-Downloads/*
-container/*
-Logs/*
-docs/module/
-docs/_build
-module/plugins/container/DLC_*.py
-failed_links.txt
-module/config/gui.xml
-module/config/core.xml
-module/config/plugin.xml
-links.txt
-ssl.crt
-ssl.key
-cert.pem
-module/web/pyload.db
-*.svg
-*.prefs
-*.po
-*.orig
-*.rej
-pyload/*
-dist/*
-build/*
-setup.py
-paver-minilib.zip
-env/*
diff --git a/CREDITS.md b/CREDITS.md
new file mode 100644
index 000000000..1460141ae
--- /dev/null
+++ b/CREDITS.md
@@ -0,0 +1,50 @@
+Credits
+-------
+
+### pyLoad Team ###
+
+*(alphabetically sorted)*
+
+ - RaNaN <Mast3rRaNaN@hotmail.de>
+ - himbrr <himbrr@himbrr.ws>
+ - mkaay (Marius) <mkaay@mkaay.de>
+ - sebnapi
+ - spoob <spoob@gmx.de>
+ - stickell (Stefano) <l.stickell@yahoo.it>
+ - vuolter (Walter Purcaro) <vuolter@gmail.com>
+ - zoidberg10 <zoidberg@mujmail.cz>
+
+A special thanks to spoob, sebnapi and RaNaN who created pyLoad!
+
+
+### Mantainers ###
+
+*(alphabetically sorted)*
+
+ - stickell (Stefano) <l.stickell@yahoo.it>
+ - vuolter (Walter Purcaro) <vuolter@gmail.com>
+
+
+### Contributors ###
+
+The list of developers who have kindly contributed to the pyLoad project is constantly updated.
+You can found it here: <https://github.com/pyload/pyload/graphs/contributors>.
+
+
+### Translators ###
+
+Many users contribute everyday to translate pyLoad in more and more languages.
+You can see their recent activities here: <http://translate.pyload.org/project/pyload/activity_stream>.
+
+
+### Supporters ###
+
+Sadly, is really hard to list all the users who helped the pyLoad Team in these last years testing code, reporting issues,
+opening topic to share tips and advices, fix troubles or simply discuss about this project...
+
+
+#### The pyLoad Team can be only forever thankful to all of you for your amazing efforts! ####
+
+
+-----------------------------------
+###### pyLoad Team 2008-2015 ######
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index bc08fe2e4..000000000
--- a/LICENSE
+++ /dev/null
@@ -1,619 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 000000000..78f62ade3
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,596 @@
+GNU GENERAL PUBLIC LICENSE
+==========================
+
+Version 3, 29 June 2007
+
+Copyright &copy; 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+## Preamble
+
+The GNU General Public License is a free, copyleft license for software and other
+kinds of works.
+
+The licenses for most software and other practical works are designed to take away
+your freedom to share and change the works. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change all versions of a
+program--to make sure it remains free software for all its users. We, the Free
+Software Foundation, use the GNU General Public License for most of our software; it
+applies also to any other work released this way by its authors. You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our General
+Public Licenses are designed to make sure that you have the freedom to distribute
+copies of free software (and charge for them if you wish), that you receive source
+code or can get it if you want it, that you can change the software or use pieces of
+it in new free programs, and that you know you can do these things.
+
+To protect your rights, we need to prevent others from denying you these rights or
+asking you to surrender the rights. Therefore, you have certain responsibilities if
+you distribute copies of the software, or if you modify it: responsibilities to
+respect the freedom of others.
+
+For example, if you distribute copies of such a program, whether gratis or for a fee,
+you must pass on to the recipients the same freedoms that you received. You must make
+sure that they, too, receive or can get the source code. And you must show them these
+terms so they know their rights.
+
+Developers that use the GNU GPL protect your rights with two steps: (1) assert
+copyright on the software, and (2) offer you this License giving you legal permission
+to copy, distribute and/or modify it.
+
+For the developers' and authors' protection, the GPL clearly explains that there is
+no warranty for this free software. For both users' and authors' sake, the GPL
+requires that modified versions be marked as changed, so that their problems will not
+be attributed erroneously to authors of previous versions.
+
+Some devices are designed to deny users access to install or run modified versions of
+the software inside them, although the manufacturer can do so. This is fundamentally
+incompatible with the aim of protecting users' freedom to change the software. The
+systematic pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we have designed
+this version of the GPL to prohibit the practice for those products. If such problems
+arise substantially in other domains, we stand ready to extend this provision to
+those domains in future versions of the GPL, as needed to protect the freedom of
+users.
+
+Finally, every program is threatened constantly by software patents. States should
+not allow patents to restrict development and use of software on general-purpose
+computers, but in those that do, we wish to avoid the special danger that patents
+applied to a free program could make it effectively proprietary. To prevent this, the
+GPL assures that patents cannot be used to render the program non-free.
+
+The precise terms and conditions for copying, distribution and modification follow.
+
+## TERMS AND CONDITIONS
+
+### 0. Definitions.
+
+&ldquo;This License&rdquo; refers to version 3 of the GNU General Public License.
+
+&ldquo;Copyright&rdquo; also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+&ldquo;The Program&rdquo; refers to any copyrightable work licensed under this
+License. Each licensee is addressed as &ldquo;you&rdquo;. &ldquo;Licensees&rdquo; and
+&ldquo;recipients&rdquo; may be individuals or organizations.
+
+To &ldquo;modify&rdquo; a work means to copy from or adapt all or part of the work in
+a fashion requiring copyright permission, other than the making of an exact copy. The
+resulting work is called a &ldquo;modified version&rdquo; of the earlier work or a
+work &ldquo;based on&rdquo; the earlier work.
+
+A &ldquo;covered work&rdquo; means either the unmodified Program or a work based on
+the Program.
+
+To &ldquo;propagate&rdquo; a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for infringement under
+applicable copyright law, except executing it on a computer or modifying a private
+copy. Propagation includes copying, distribution (with or without modification),
+making available to the public, and in some countries other activities as well.
+
+To &ldquo;convey&rdquo; a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through a computer
+network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays &ldquo;Appropriate Legal Notices&rdquo; to the
+extent that it includes a convenient and prominently visible feature that (1)
+displays an appropriate copyright notice, and (2) tells the user that there is no
+warranty for the work (except to the extent that warranties are provided), that
+licensees may convey the work under this License, and how to view a copy of this
+License. If the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+### 1. Source Code.
+
+The &ldquo;source code&rdquo; for a work means the preferred form of the work for
+making modifications to it. &ldquo;Object code&rdquo; means any non-source form of a
+work.
+
+A &ldquo;Standard Interface&rdquo; means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of interfaces
+specified for a particular programming language, one that is widely used among
+developers working in that language.
+
+The &ldquo;System Libraries&rdquo; of an executable work include anything, other than
+the work as a whole, that (a) is included in the normal form of packaging a Major
+Component, but which is not part of that Major Component, and (b) serves only to
+enable use of the work with that Major Component, or to implement a Standard
+Interface for which an implementation is available to the public in source code form.
+A &ldquo;Major Component&rdquo;, in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system (if any) on which
+the executable work runs, or a compiler used to produce the work, or an object code
+interpreter used to run it.
+
+The &ldquo;Corresponding Source&rdquo; for a work in object code form means all the
+source code needed to generate, install, and (for an executable work) run the object
+code and to modify the work, including scripts to control those activities. However,
+it does not include the work's System Libraries, or general-purpose tools or
+generally available free programs which are used unmodified in performing those
+activities but which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for the work, and
+the source code for shared libraries and dynamically linked subprograms that the work
+is specifically designed to require, such as by intimate data communication or
+control flow between those subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate
+automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+
+### 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of copyright on the
+Program, and are irrevocable provided the stated conditions are met. This License
+explicitly affirms your unlimited permission to run the unmodified Program. The
+output from running a covered work is covered by this License only if the output,
+given its content, constitutes a covered work. This License acknowledges your rights
+of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey, without
+conditions so long as your license otherwise remains in force. You may convey covered
+works to others for the sole purpose of having them make modifications exclusively
+for you, or provide you with facilities for running those works, provided that you
+comply with the terms of this License in conveying all material for which you do not
+control copyright. Those thus making or running the covered works for you must do so
+exclusively on your behalf, under your direction and control, on terms that prohibit
+them from making any copies of your copyrighted material outside their relationship
+with you.
+
+Conveying under any other circumstances is permitted solely under the conditions
+stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+
+### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological measure under any
+applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
+adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
+of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention of
+technological measures to the extent such circumvention is effected by exercising
+rights under this License with respect to the covered work, and you disclaim any
+intention to limit operation or modification of the work as a means of enforcing,
+against the work's users, your or third parties' legal rights to forbid circumvention
+of technological measures.
+
+### 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you receive it, in any
+medium, provided that you conspicuously and appropriately publish on each copy an
+appropriate copyright notice; keep intact all notices stating that this License and
+any non-permissive terms added in accord with section 7 apply to the code; keep
+intact all notices of the absence of any warranty; and give all recipients a copy of
+this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you may offer
+support or warranty protection for a fee.
+
+### 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to produce it from
+the Program, in the form of source code under the terms of section 4, provided that
+you also meet all of these conditions:
+
+* **a)** The work must carry prominent notices stating that you modified it, and giving a
+relevant date.
+* **b)** The work must carry prominent notices stating that it is released under this
+License and any conditions added under section 7. This requirement modifies the
+requirement in section 4 to &ldquo;keep intact all notices&rdquo;.
+* **c)** You must license the entire work, as a whole, under this License to anyone who
+comes into possession of a copy. This License will therefore apply, along with any
+applicable section 7 additional terms, to the whole of the work, and all its parts,
+regardless of how they are packaged. This License gives no permission to license the
+work in any other way, but it does not invalidate such permission if you have
+separately received it.
+* **d)** If the work has interactive user interfaces, each must display Appropriate Legal
+Notices; however, if the Program has interactive interfaces that do not display
+Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works, which are
+not by their nature extensions of the covered work, and which are not combined with
+it such as to form a larger program, in or on a volume of a storage or distribution
+medium, is called an &ldquo;aggregate&rdquo; if the compilation and its resulting
+copyright are not used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work in an aggregate
+does not cause this License to apply to the other parts of the aggregate.
+
+### 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of sections 4 and
+5, provided that you also convey the machine-readable Corresponding Source under the
+terms of this License, in one of these ways:
+
+* **a)** Convey the object code in, or embodied in, a physical product (including a
+physical distribution medium), accompanied by the Corresponding Source fixed on a
+durable physical medium customarily used for software interchange.
+* **b)** Convey the object code in, or embodied in, a physical product (including a
+physical distribution medium), accompanied by a written offer, valid for at least
+three years and valid for as long as you offer spare parts or customer support for
+that product model, to give anyone who possesses the object code either (1) a copy of
+the Corresponding Source for all the software in the product that is covered by this
+License, on a durable physical medium customarily used for software interchange, for
+a price no more than your reasonable cost of physically performing this conveying of
+source, or (2) access to copy the Corresponding Source from a network server at no
+charge.
+* **c)** Convey individual copies of the object code with a copy of the written offer to
+provide the Corresponding Source. This alternative is allowed only occasionally and
+noncommercially, and only if you received the object code with such an offer, in
+accord with subsection 6b.
+* **d)** Convey the object code by offering access from a designated place (gratis or for
+a charge), and offer equivalent access to the Corresponding Source in the same way
+through the same place at no further charge. You need not require recipients to copy
+the Corresponding Source along with the object code. If the place to copy the object
+code is a network server, the Corresponding Source may be on a different server
+(operated by you or a third party) that supports equivalent copying facilities,
+provided you maintain clear directions next to the object code saying where to find
+the Corresponding Source. Regardless of what server hosts the Corresponding Source,
+you remain obligated to ensure that it is available for as long as needed to satisfy
+these requirements.
+* **e)** Convey the object code using peer-to-peer transmission, provided you inform
+other peers where the object code and Corresponding Source of the work are being
+offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from the
+Corresponding Source as a System Library, need not be included in conveying the
+object code work.
+
+A &ldquo;User Product&rdquo; is either (1) a &ldquo;consumer product&rdquo;, which
+means any tangible personal property which is normally used for personal, family, or
+household purposes, or (2) anything designed or sold for incorporation into a
+dwelling. In determining whether a product is a consumer product, doubtful cases
+shall be resolved in favor of coverage. For a particular product received by a
+particular user, &ldquo;normally used&rdquo; refers to a typical or common use of
+that class of product, regardless of the status of the particular user or of the way
+in which the particular user actually uses, or expects or is expected to use, the
+product. A product is a consumer product regardless of whether the product has
+substantial commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+&ldquo;Installation Information&rdquo; for a User Product means any methods,
+procedures, authorization keys, or other information required to install and execute
+modified versions of a covered work in that User Product from a modified version of
+its Corresponding Source. The information must suffice to ensure that the continued
+functioning of the modified object code is in no case prevented or interfered with
+solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically for
+use in, a User Product, and the conveying occurs as part of a transaction in which
+the right of possession and use of the User Product is transferred to the recipient
+in perpetuity or for a fixed term (regardless of how the transaction is
+characterized), the Corresponding Source conveyed under this section must be
+accompanied by the Installation Information. But this requirement does not apply if
+neither you nor any third party retains the ability to install modified object code
+on the User Product (for example, the work has been installed in ROM).
+
+The requirement to provide Installation Information does not include a requirement to
+continue to provide support service, warranty, or updates for a work that has been
+modified or installed by the recipient, or for the User Product in which it has been
+modified or installed. Access to a network may be denied when the modification itself
+materially and adversely affects the operation of the network or violates the rules
+and protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord with
+this section must be in a format that is publicly documented (and with an
+implementation available to the public in source code form), and must require no
+special password or key for unpacking, reading or copying.
+
+### 7. Additional Terms.
+
+&ldquo;Additional permissions&rdquo; are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions. Additional
+permissions that are applicable to the entire Program shall be treated as though they
+were included in this License, to the extent that they are valid under applicable
+law. If additional permissions apply only to part of the Program, that part may be
+used separately under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any
+additional permissions from that copy, or from any part of it. (Additional
+permissions may be written to require their own removal in certain cases when you
+modify the work.) You may place additional permissions on material, added by you to a
+covered work, for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you add to a
+covered work, you may (if authorized by the copyright holders of that material)
+supplement the terms of this License with terms:
+
+* **a)** Disclaiming warranty or limiting liability differently from the terms of
+sections 15 and 16 of this License; or
+* **b)** Requiring preservation of specified reasonable legal notices or author
+attributions in that material or in the Appropriate Legal Notices displayed by works
+containing it; or
+* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
+modified versions of such material be marked in reasonable ways as different from the
+original version; or
+* **d)** Limiting the use for publicity purposes of names of licensors or authors of the
+material; or
+* **e)** Declining to grant rights under trademark law for use of some trade names,
+trademarks, or service marks; or
+* **f)** Requiring indemnification of licensors and authors of that material by anyone
+who conveys the material (or modified versions of it) with contractual assumptions of
+liability to the recipient, for any liability that these contractual assumptions
+directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered &ldquo;further
+restrictions&rdquo; within the meaning of section 10. If the Program as you received
+it, or any part of it, contains a notice stating that it is governed by this License
+along with a term that is a further restriction, you may remove that term. If a
+license document contains a further restriction but permits relicensing or conveying
+under this License, you may add to a covered work material governed by the terms of
+that license document, provided that the further restriction does not survive such
+relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place, in
+the relevant source files, a statement of the additional terms that apply to those
+files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form of a
+separately written license, or stated as exceptions; the above requirements apply
+either way.
+
+### 8. Termination.
+
+You may not propagate or modify a covered work except as expressly provided under
+this License. Any attempt otherwise to propagate or modify it is void, and will
+automatically terminate your rights under this License (including any patent licenses
+granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from a
+particular copyright holder is reinstated (a) provisionally, unless and until the
+copyright holder explicitly and finally terminates your license, and (b) permanently,
+if the copyright holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently
+if the copyright holder notifies you of the violation by some reasonable means, this
+is the first time you have received notice of violation of this License (for any
+work) from that copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses of
+parties who have received copies or rights from you under this License. If your
+rights have been terminated and not permanently reinstated, you do not qualify to
+receive new licenses for the same material under section 10.
+
+### 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run a copy of the
+Program. Ancillary propagation of a covered work occurring solely as a consequence of
+using peer-to-peer transmission to receive a copy likewise does not require
+acceptance. However, nothing other than this License grants you permission to
+propagate or modify any covered work. These actions infringe copyright if you do not
+accept this License. Therefore, by modifying or propagating a covered work, you
+indicate your acceptance of this License to do so.
+
+### 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives a license
+from the original licensors, to run, modify and propagate that work, subject to this
+License. You are not responsible for enforcing compliance by third parties with this
+License.
+
+An &ldquo;entity transaction&rdquo; is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an organization, or
+merging organizations. If propagation of a covered work results from an entity
+transaction, each party to that transaction who receives a copy of the work also
+receives whatever licenses to the work the party's predecessor in interest had or
+could give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if the predecessor
+has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights granted or
+affirmed under this License. For example, you may not impose a license fee, royalty,
+or other charge for exercise of rights granted under this License, and you may not
+initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
+that any patent claim is infringed by making, using, selling, offering for sale, or
+importing the Program or any portion of it.
+
+### 11. Patents.
+
+A &ldquo;contributor&rdquo; is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The work thus
+licensed is called the contributor's &ldquo;contributor version&rdquo;.
+
+A contributor's &ldquo;essential patent claims&rdquo; are all patent claims owned or
+controlled by the contributor, whether already acquired or hereafter acquired, that
+would be infringed by some manner, permitted by this License, of making, using, or
+selling its contributor version, but do not include claims that would be infringed
+only as a consequence of further modification of the contributor version. For
+purposes of this definition, &ldquo;control&rdquo; includes the right to grant patent
+sublicenses in a manner consistent with the requirements of this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
+under the contributor's essential patent claims, to make, use, sell, offer for sale,
+import and otherwise run, modify and propagate the contents of its contributor
+version.
+
+In the following three paragraphs, a &ldquo;patent license&rdquo; is any express
+agreement or commitment, however denominated, not to enforce a patent (such as an
+express permission to practice a patent or covenant not to sue for patent
+infringement). To &ldquo;grant&rdquo; such a patent license to a party means to make
+such an agreement or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the
+Corresponding Source of the work is not available for anyone to copy, free of charge
+and under the terms of this License, through a publicly available network server or
+other readily accessible means, then you must either (1) cause the Corresponding
+Source to be so available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner consistent with
+the requirements of this License, to extend the patent license to downstream
+recipients. &ldquo;Knowingly relying&rdquo; means you have actual knowledge that, but
+for the patent license, your conveying the covered work in a country, or your
+recipient's use of the covered work in a country, would infringe one or more
+identifiable patents in that country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement, you
+convey, or propagate by procuring conveyance of, a covered work, and grant a patent
+license to some of the parties receiving the covered work authorizing them to use,
+propagate, modify or convey a specific copy of the covered work, then the patent
+license you grant is automatically extended to all recipients of the covered work and
+works based on it.
+
+A patent license is &ldquo;discriminatory&rdquo; if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on the
+non-exercise of one or more of the rights that are specifically granted under this
+License. You may not convey a covered work if you are a party to an arrangement with
+a third party that is in the business of distributing software, under which you make
+payment to the third party based on the extent of your activity of conveying the
+work, and under which the third party grants, to any of the parties who would receive
+the covered work from you, a discriminatory patent license (a) in connection with
+copies of the covered work conveyed by you (or copies made from those copies), or (b)
+primarily for and in connection with specific products or compilations that contain
+the covered work, unless you entered into that arrangement, or that patent license
+was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied
+license or other defenses to infringement that may otherwise be available to you
+under applicable patent law.
+
+### 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or otherwise)
+that contradict the conditions of this License, they do not excuse you from the
+conditions of this License. If you cannot convey a covered work so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not convey it at all. For example, if you
+agree to terms that obligate you to collect a royalty for further conveying from
+those to whom you convey the Program, the only way you could satisfy both those terms
+and this License would be to refrain entirely from conveying the Program.
+
+### 13. Use with the GNU Affero General Public License.
+
+Notwithstanding any other provision of this License, you have permission to link or
+combine any covered work with a work licensed under version 3 of the GNU Affero
+General Public License into a single combined work, and to convey the resulting work.
+The terms of this License will continue to apply to the part which is the covered
+work, but the special requirements of the GNU Affero General Public License, section
+13, concerning interaction through a network will apply to the combination as such.
+
+### 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the GNU
+General Public License from time to time. Such new versions will be similar in spirit
+to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies that
+a certain numbered version of the GNU General Public License &ldquo;or any later
+version&rdquo; applies to it, you have the option of following the terms and
+conditions either of that numbered version or of any later version published by the
+Free Software Foundation. If the Program does not specify a version number of the GNU
+General Public License, you may choose any version ever published by the Free
+Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of the GNU
+General Public License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the Program.
+
+Later license versions may give you additional or different permissions. However, no
+additional obligations are imposed on any author or copyright holder as a result of
+your choosing to follow a later version.
+
+### 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM &ldquo;AS IS&rdquo; WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
+QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+### 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
+COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
+INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
+OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
+WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+### 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above cannot be
+given local legal effect according to their terms, reviewing courts shall apply local
+law that most closely approximates an absolute waiver of all civil liability in
+connection with the Program, unless a warranty or assumption of liability accompanies
+a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+## How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible use to
+the public, the best way to achieve this is to make it free software which everyone
+can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach them
+to the start of each source file to most effectively state the exclusion of warranty;
+and each file should have at least the &ldquo;copyright&rdquo; line and a pointer to
+where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program does terminal interaction, make it output a short notice like this
+when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type 'show c' for details.
+
+The hypothetical commands 'show w' and 'show c' should show the appropriate parts of
+the General Public License. Of course, your program's commands might be different;
+for a GUI interface, you would use an &ldquo;about box&rdquo;.
+
+You should also get your employer (if you work as a programmer) or school, if any, to
+sign a &ldquo;copyright disclaimer&rdquo; for the program, if necessary. For more
+information on this, and how to apply and follow the GNU GPL, see
+&lt;<http://www.gnu.org/licenses/>&gt;.
+
+The GNU General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may consider it
+more useful to permit linking proprietary applications with the library. If this is
+what you want to do, use the GNU Lesser General Public License instead of this
+License. But first, please read
+&lt;<http://www.gnu.org/philosophy/why-not-lgpl.html>&gt;.
diff --git a/README b/README
deleted file mode 100644
index be6200ead..000000000
--- a/README
+++ /dev/null
@@ -1,88 +0,0 @@
-Description
-===========
-
-pyLoad is a free and open source downloader for 1-click-hosting sites
-like rapidshare.com or uploaded.to.
-It supports link decryption as well as all important container formats.
-
-pyLoad is written entirely in Python and is currently under heavy development.
-
-For news, downloads, wiki, forum and further information visit http://pyload.org/
-
-To report bugs, suggest features, ask a question, get the developer version
-or help us out, visit http://github.com/pyload/pyload
-
-Documentation about extending pyLoad can be found at http://docs.pyload.org or join us at #pyload on irc.freenode.net
-
-Dependencies
-============
-
-You need at least python 2.5 to run pyLoad and all of these required libaries.
-They should be automatically installed when using pip install.
-The prebuilt pyload packages also install these dependencies or have them included, so manuall install
-is only needed when installing pyLoad from source.
-
-Required
---------
-
-- pycurl a.k.a python-curl
-- jinja2
-- beaker
-- thrift
-- simplejson (for python 2.5)
-
-Some plugins require additional packages, only install these when needed.
-
-Optional
---------
-
-- pycrypto: RSDF/CCF/DLC support
-- tesseract, python-pil a.k.a python-imaging: Automatic captcha recognition for a small amount of plugins
-- jsengine (spidermonkey, ossp-js, pyv8, rhino): Used for several hoster, ClickNLoad
-- feedparser
-- BeautifulSoup
-- pyOpenSSL: For SSL connection
-
-First start
-===========
-
-Note: If you installed pyload via package-manager `python pyLoadCore.py` is probably equivalent to `pyLoadCore`
-
-Run::
-
- python pyLoadCore.py
-
-and follow the instructions of the setup assistent.
-
-For a list of options use::
-
- python pyLoadCore.py -h
-
-Configuration
-=============
-
-After finishing the setup assistent pyLoad is ready to use and more configuration can be done via webinterface.
-Additionally you could simply edit the config files located in your pyLoad home dir (defaults to: ~/.pyload)
-with your favorite editor and edit the appropriate options. For a short description of
-the options take a look at http://pyload.org/configuration.
-
-To restart the configure assistent run::
-
- python pyLoadCore.py -s
-
-Adding downloads
-----------------
-
-To start the CLI and connect to a local server, run::
-
- python pyLoadCli.py -l
-
-for more options refer to::
-
- python pyLoadCli.py -h
-
-The webinterface can be accessed when pointing your webbrowser to the ip and configured port, defaults to http://localhost:8000
-
-Notes
-=====
-For more information, see http://pyload.org/
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..f01737341
--- /dev/null
+++ b/README.md
@@ -0,0 +1,282 @@
+<p align="center"><a href="http://pyload.org/"><img src="/docs/resources/banner.png" alt="pyLoad" /></a></p>
+
+[![Translation Status](http://translate.pyload.org/badges/pyload/localized.png "Translation Status")](http://translate.pyload.org/project/pyload/)
+
+**pyLoad** is a Free and Open Source download manager written in Python and designed to be extremely lightweight.
+
+
+Table of contents
+-----------------
+
+ - [Description](#description)
+ - [Download](#download)
+ - [Installation](#installation)
+ - [Dependencies](#dependencies)
+ - [Required](#required)
+ - [Optional](#optional)
+ - [Usage](#usage)
+ - [First Start](#first-start)
+ - [Web User Interface](#web-user-interface)
+ - [Command Line Interface](#command-line-interface)
+ - [Development](#development)
+ - [Translations](#translations)
+ - [Send a tip for translators](#send-a-tip-for-translators)
+ - [Update templates](#update-templates)
+ - [Retrieve PO files](#retrieve-po-files)
+ - [Compile PO files](#compile-po-files)
+ - [Licensing](#licensing)
+ - [Main program](#main-program)
+ - [Plugins](#plugins)
+ - [Plugin policy](#plugin-policy)
+ - [Credits](#credits)
+
+
+Description
+-----------
+
+**pyLoad** was developed to run on any device able to connect to internet and supporting the Python programming language.
+That's mean it's available for a really wide range of hardware platforms and operating systems.
+You can control it entirely by web using its friendly Web User Interface.
+
+All common video-sites, one-click-hosters, container formats and well known web standards are supported to allow you to download your files.
+Additionaly, pyLoad has a great variety of plugins to automate common tasks and make unattended running possible.
+
+pyLoad has a fully featured and well documented Application Programming Interface, easily extendable and accessible by external tools, cross-platform apps or other softwares.
+Just take a look to the [Development section](#development) for more info about that.
+
+For news, wiki, forum and further info visit the pyLoad website: <http://pyload.org/>.
+
+Or joining us at `#pyload` on `irc.freenode.net`.
+
+
+Download
+--------
+
+> **Note:**
+> If you wanna use pyLoad on Windows, it's hightly recommented to install the latest **official** pre-build package for that platform.
+
+Releases | Download
+----------------------------------------------------- | -----------------------------------------------------
+Pre-build packages with changelog | <https://github.com/pyload/pyload/releases>
+
+Pre-build packages are provided with all the software dependencies required to run pyLoad flawlessly on the referenced platform.
+If you choose a source code, at least you need to have the proper Python version installed on your platform before launch pyLoad.
+
+Source code | Download
+----------------------------------------------------- | -----------------------------------------------------
+Latest stable version | <https://github.com/pyload/pyload/archive/stable.zip>
+Latest development version | <https://github.com/pyload/pyload/archive/master.zip>
+
+
+Installation
+------------
+
+pyLoad currently works under:
+
+ - [x] **Python 2.5**
+ - [x] **Python 2.6**
+ - [x] **Python 2.7**
+ - [ ] Python 3
+ - [ ] PyPy
+
+You can install any missing software package from the *Python Package Index* typing:
+
+ pip install <package-name>
+
+
+Dependencies
+------------
+
+### Required ###
+
+ - **Beaker**
+ - **Getch**
+ - **MultipartPostHandler**
+ - **SafeEval**
+ - **bottle**
+ - **colorama**
+ - **jinja2**
+ - **markupsafe**
+ - **pycurl** (python-curl)
+ - **rename_process**
+ - **setuptools**
+ - **thrift**
+ - **wsgiserver**
+
+
+Some extra features require additional software packages. See below:
+
+### Optional ###
+
+ - **BeautifulSoup** *Few plugins support*
+ - **PIL** (python-imaging) *Captcha recognition*
+ - **Send2Trash** *Trash support*
+ - **colorlog** *Colored log*
+ - **bjoern** (<https://github.com/jonashaag/bjoern>) *More responsive web interface*
+ - **feedparser** *RSS parsing*
+ - **node.js** *ClickNLoad and other plugins*
+ - or **ossp-js**
+ - or **pyv8**
+ - or **rhino**
+ - or **spidermonkey**
+ - **pyOpenSSL** *SSL support*
+ - **pycrypto** *RSDF/CCF/DLC decrypting*
+ - **simplejson** *JSON speedup*
+ - **tesseract** *Captcha OCR support*
+
+
+Usage
+-----
+
+### First Start ###
+
+Run:
+
+ python pyload.py
+
+and follow the setup assistant instructions.
+
+> **Note:**
+> If you have installed pyLoad by a package-manager, command `python pyload.py` might be equivalent to `pyload`.
+
+If something go wrong, you can restart the setup assistant typing:
+
+ python pyload.py -s
+
+Or you can even edit the configuration files located in your pyLoad home directory
+(usually `%userprofile%/pyload` on Windows or `~/.pyload` otherwise) with your favorite editor.
+
+For a short description of all the configuration options available visit: <http://pyload.org/configuration>.
+
+
+### Web User Interface ###
+
+Run:
+
+ python pyload.py
+
+So, to retrieve it point your browser to the socket address configured by setup (default to `http://localhost:8000`).
+
+You can get a list of accepted arguments typing:
+
+ python pyload.py -h
+
+
+### Command Line Interface ###
+
+Run:
+
+ python pyload-cli.py -l
+
+You can get a list of accepted arguments typing:
+
+ python pyload-cli.py -h
+
+
+Development
+-----------
+
+ * pyLoad roadmap: <https://github.com/pyload/pyload/milestones>.
+ * To report bugs, suggest features, ask for a question or help us out, visit: <https://github.com/pyload/pyload/issues>.
+ * To request the merge of your code in the pyLoad repository, open a new *pull request* here: <https://github.com/pyload/pyload/pulls>.
+ * Documentation about how extending pyLoad can be found at: <http://docs.pyload.org>.
+
+
+Translations
+------------
+
+The localization process take place on Crowdin: <http://crowdin.net/project/pyload>.
+
+
+### Send a tip for translators ###
+
+If you want to explain a translatable string to make the translation process easier you can do that using comment block starting with `L10N:`. For example:
+
+ python
+ # L10N: Here the tip for translators
+ # Thanks
+ print _("A translatable string")
+
+Translators will see:
+
+ L10N: Here the tip for translators
+ Thanks
+
+
+### Update templates ###
+
+To update POT files type:
+
+ paver generate_locale
+
+To automatically upload the updated POTs on Crowdin for the localization process just type:
+
+ paver upload_translations -k [api_key]
+
+the API Key can be retrieved in the Settings panel of the project on Crowdin. This is allowed only to the administrators.
+
+
+### Retrieve PO files ###
+
+Updated PO files can be automatically download from Crowdin typing:
+
+ paver download_translations -k [api_key]
+
+This is allowed only to administrators, **users can download the last version of the translations using the Crowdin web interface**.
+
+
+### Compile PO files ###
+
+MO files can be generated typing:
+
+ paver compile_translations
+
+To compile a single file just use command `msgfmt`.
+For example to compile a core.po file type:
+
+ msgfmt -o core.mo core.po
+
+
+Licensing
+---------
+
+### Main program ###
+
+Please refer to the attached [LICENSE.md](/LICENSE.md) for the extended license.
+
+
+### Plugins ###
+
+According to the terms of the pyLoad license, official plugins must be treated as an extension of the main program
+and released under the same license of pyLoad or a compatible one:
+
+ * Any plugin published **without the \_\_license\_\_ attribute** is implied published under the same license of pyLoad.
+ * Only plugins published **with a compatible license** will be accepted as official and included in the pyLoad repository.
+ * **Un-official plugins are not supported**, so any issue report or feature request regarding this kind of plugin will be rejected.
+ * Is recommended to put a **shorten** license notice only if nedfull to avoid misunderstandings about the adopted license.
+
+
+Plugin policy
+-------------
+
+ - No cracking website or service
+ - No drugs website or service
+ - No e-commerce website or service
+ - No government website or service
+ - No illegal website or service in the country where its servers are located
+ - No pedopornography website or service
+ - No private website or service
+ - No racist website or service
+ - No warez website or service
+ - No weapon website or service
+ - ...
+
+
+Credits
+-------
+
+Please refer to the attached [CREDITS.md](/CREDITS.md) for the extended credits.
+
+
+-----------------------------------
+###### pyLoad Team 2008-2015 ######
diff --git a/docs/Makefile b/docs/Makefile
index 2082e6ef3..01a0119fd 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -15,116 +15,116 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
- -rm -rf $(BUILDDIR)/*
+ -rm -rf $(BUILDDIR)/*
html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyLoad.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyLoad.qhc"
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyLoad.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyLoad.qhc"
devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/pyLoad"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyLoad"
- @echo "# devhelp"
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/pyLoad"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyLoad"
+ @echo "# devhelp"
epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/access_api.rst b/docs/access_api.rst
index df69da8b2..938b73d04 100644
--- a/docs/access_api.rst
+++ b/docs/access_api.rst
@@ -13,12 +13,12 @@ First of all, you need to know what you can do with our API. It lets you do all
retrieving download status, manage queue, manage accounts, modify config and so on.
This document is not intended to explain every function in detail, for a complete listing
-see :class:`Api <module.Api.Api>`.
+see :class:`Api <pyload.api.Api>`.
-Of course its possible to access the ``core.api`` attribute in plugins and hooks, but much more
+Of course its possible to access the ``core.api`` attribute in plugins and addons, but much more
interesting is the possibillity to call function from different programs written in many different languages.
-pyLoad uses thrift as backend and provides its :class:`Api <module.Api.Api>` as service.
+pyLoad uses thrift as backend and provides its :class:`Api <pyload.api.Api>` as service.
More information about thrift can be found here http://wiki.apache.org/thrift/.
@@ -26,7 +26,7 @@ Using Thrift
------------
Every thrift service has to define all data structures and declare every method which should be usable via rpc.
-This file is located :file:`module/remote/thriftbackend/pyload.thrift`, its very helpful to inform about
+This file is located :file:`pyload/remote/thriftbackend/pyload.thrift`, its very helpful to inform about
arguments and detailed structure of return types. However it does not contain any information about what the functions does.
Assuming you want to use the API in any other language than python than check if it is
@@ -48,11 +48,11 @@ In case you want to use python, pyload has already all files included to access
A basic script that prints out some information: ::
- from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
+ from pyload.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
try:
client = ThriftClient(host="127.0.0.1", port=7227, user="User", password="yourpw")
- except:
+ except Exception:
print "Login was wrong"
exit()
@@ -92,14 +92,14 @@ so pyLoad can authenticate you.
Calling Methods
===============
-In general you can use any method listed at the :class:`Api <module.Api.Api>` documentation, which is also available to
+In general you can use any method listed at the :class:`Api <pyload.api.Api>` documentation, which is also available to
the thriftbackend.
Access works simply via ``http://pyload-core/api/methodName``, where ``pyload-core`` is the ip address
or hostname including the webinterface port. By default on local access this would be `localhost:8000`.
The return value will be formatted in JSON, complex data types as dictionaries.
-As mentionted above for a documentation about the return types look at the thrift specification file :file:`module/remote/thriftbackend/pyload.thrift`.
+As mentionted above for a documentation about the return types look at the thrift specification file :file:`pyload/remote/thriftbackend/pyload.thrift`.
==================
Passing parameters
@@ -107,7 +107,7 @@ Passing parameters
To pass arguments you have two choices.
Either use positional arguments, eg ``http://pyload-core/api/getFileData/1``, where 1 is the FileID, or use keyword arguments
-supplied via GET or POST ``http://pyload-core/api/getFileData?fid=1``. You can find the argument names in the :class:`Api <module.Api.Api>`
+supplied via GET or POST ``http://pyload-core/api/getFileData?fid=1``. You can find the argument names in the :class:`Api <pyload.api.Api>`
documentation.
It is important that *all* arguments are in JSON format. So ``http://pyload-core/api/getFileData/1`` is valid because
@@ -118,4 +118,4 @@ Strings are wrapped in double qoutes, because `"username"` represents a string i
every container type like lists and dicts are possible. You usually don't have to convert them. just use a json encoder before using them
in the HTTP request.
-Please note that the data have to be urlencoded at last. (Most libaries will do that automatically) \ No newline at end of file
+Please note that the data have to be urlencoded at last. (Most libaries will do that automatically)
diff --git a/docs/build_docs.py b/docs/build_docs.py
new file mode 100644
index 000000000..05f34be44
--- /dev/null
+++ b/docs/build_docs.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# pyLoad documentation build configuration file, created by
+# sphinx-quickstart on Sat Jun 4 11:54:34 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import os
+import platform
+import sys
+
+
+dir_name = os.path.join(os.path.dirname(os.path.abspath("")))
+
+sys.path.append(dir_name)
+
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest',
+ 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig',
+ 'sphinx.ext.viewcode']
+
+autosummary_generate = True
+autodoc_default_flags = ['members']
+autoclass_content = 'both'
+autodoc_member_order = 'bysource'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'pyLoad'
+copyright = u'2008-2015, pyLoad Team'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The full version, including alpha/beta/rc tags.
+"""
+[[[cog
+from pavement import options
+v = options.version.split(".")
+cog.outl("version = '%s'" % ".".join(v[:2]))
+cog.outl("release = '%s'" % ".".join(v))
+]]]
+"""
+version = '0.4'
+release = '0.4.10'
+# [[[end]]]
+
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = os.path.join(dir_name, "pyload", "web", "media", "default", "img", "pyload-logo-edited3.5-new-font-small.png")
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+html_favicon = os.path.join(dir_name, "docs", "resources", "icon.ico")
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyLoaddoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+# latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+# latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'pyLoad.tex', u'pyLoad Documentation',
+ u'pyLoad Team', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+# latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'pyload', u'pyLoad Documentation',
+ [u'pyLoad Team'], 1)
+]
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index 9d2cf98f9..000000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pyLoad documentation build configuration file, created by
-# sphinx-quickstart on Sat Jun 4 11:54:34 2011.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-from os.path import dirname, join, abspath
-
-dir_name = join(dirname(abspath("")))
-sys.path.append(dir_name)
-sys.path.append(join(dir_name, "module", "lib"))
-
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
-
-autosummary_generate = True
-autodoc_default_flags = ['members']
-autoclass_content = 'both'
-autodoc_member_order = 'bysource'
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'pyLoad'
-copyright = u'2011, pyLoad Team'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The full version, including alpha/beta/rc tags.
-""" [[[cog
-from pavement import options
-v = options.version.split(".")
-cog.outl("version = '%s'" % ".".join(v[:2]))
-cog.outl("release = '%s'" % ".".join(v))
-]]]"""
-version = '0.4'
-release = '0.4.9'
-# [[[end]]]
-
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = join(dir_name, "module", "web", "media", "default", "img", "pyload-logo-edited3.5-new-font-small.png")
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-html_favicon = join(dir_name, "icons", "pyload2.ico")
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'pyLoaddoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'pyLoad.tex', u'pyLoad Documentation',
- u'pyLoad Team', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pyload', u'pyLoad Documentation',
- [u'pyLoad Team'], 1)
-]
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/docs.conf b/docs/docs.conf
index e197cfa43..c5e619fd6 100644
--- a/docs/docs.conf
+++ b/docs/docs.conf
@@ -1,14 +1,15 @@
-# usage: epydoc --conf docs.conf , results goes to ~/.pyload/docs
+#@NOTE: usage: epydoc --conf docs.conf
+# results goes to ~/.pyload/docs
[epydoc]
-modules: pyLoadCore.py, pyLoadCli.py, pyloadGui.py, module
+modules: pyload.py, pyload-cli.py, pyload
output: html
target: docs
docformat: restructuredtext
-exclude: module\.lib|module\.remote\.thriftbackend\.thriftgen|PyQt4|\.pyc|\.pyo|module\.plugins\.(accounts|captcha|container|crypter|hooks|hoster)
+exclude: remote\thriftbackend\thriftgen|plugin\(account|addon|container|crypter|hook|hoster|internal|ocr)|.pyc|.pyo
name: pyLoad Documentation
url: http://docs.pyload.org
diff --git a/docs/extend_pyload.rst b/docs/extend_pyload.rst
index 337cb6854..ccd4ac203 100755
--- a/docs/extend_pyload.rst
+++ b/docs/extend_pyload.rst
@@ -1,7 +1,7 @@
.. _extend_pyload:
********************
-How to extend pyLoad
+How to extend pyLoad
********************
In general there a two different plugin types. These allow everybody to write powerful, modular plugins without knowing
@@ -9,5 +9,6 @@ every detail of the pyLoad core. However you should have some basic knowledge of
.. toctree::
- write_hooks.rst
- write_plugins.rst \ No newline at end of file
+ write_addons.rst
+ write_plugins.rst
+ write_scripts.rst
diff --git a/docs/index.rst b/docs/index.rst
index 757fd7537..ff40ff3cb 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -20,7 +20,7 @@ Contents:
extend_pyload.rst
module_overview.rst
-.. currentmodule:: module
+.. currentmodule:: pyload
==================
diff --git a/docs/module_overview.rst b/docs/module_overview.rst
index d51202c88..9a2d445b6 100644
--- a/docs/module_overview.rst
+++ b/docs/module_overview.rst
@@ -1,17 +1,19 @@
+.. _module_overview:
+
Module Overview
===============
You can find an overview of some important classes here:
.. autosummary::
- :toctree: module
+ :toctree: pyload
- module.Api.Api
- module.plugins.Plugin.Base
- module.plugins.Plugin.Plugin
- module.plugins.Crypter.Crypter
- module.plugins.Account.Account
- module.plugins.Hook.Hook
- module.HookManager.HookManager
- module.PyFile.PyFile
- module.PyPackage.PyPackage
+ pyload.api.Api
+ pyload.plugin.Plugin.Base
+ pyload.plugin.Plugin.Plugin
+ pyload.plugin.Crypter.Crypter
+ pyload.plugin.Account.Account
+ pyload.plugin.Addon.Addon
+ pyload.manager.Addon.AddonManager
+ pyload.datatypes.PyFile.PyFile
+ pyload.datatypes.PyPackage.PyPackage
diff --git a/docs/plugin_licensing.rst b/docs/plugin_licensing.rst
new file mode 100644
index 000000000..0e8590d62
--- /dev/null
+++ b/docs/plugin_licensing.rst
@@ -0,0 +1,18 @@
+.. _plugin_licensing:
+
+Plugin Licensing
+================
+
+According to the terms of the GNU General Public License,
+pyload's plugins must be treated as an extension of the main program.
+This means the plugins must be released under the GPL or a GPL-compatible
+free software license, and that the terms of the GPL must be followed when
+those plugins are distributed.
+
+ * Any plugin published **without a license notice** is intend published under the **GNU GPLv3**.
+ * A different license can be used but it **must be GPL-compatible** and the license notice must be put in the plugin
+ file.
+ * Any plugin published **with a GPL incompatible license** will be rejected.
+ This includes *copyright all right reserved*.
+ * Is recommended to put the license notice at the top of the plugin file.
+ * Is recommended to **not** put the license notice when plugin is published under the GNU GPLv3.
diff --git a/docs/resources/banner.png b/docs/resources/banner.png
new file mode 100644
index 000000000..4e2d718f7
--- /dev/null
+++ b/docs/resources/banner.png
Binary files differ
diff --git a/docs/resources/icon.ico b/docs/resources/icon.ico
new file mode 100644
index 000000000..b5fb55686
--- /dev/null
+++ b/docs/resources/icon.ico
Binary files differ
diff --git a/docs/resources/logo.png b/docs/resources/logo.png
new file mode 100644
index 000000000..d20084847
--- /dev/null
+++ b/docs/resources/logo.png
Binary files differ
diff --git a/docs/resources/logo.svg b/docs/resources/logo.svg
new file mode 100644
index 000000000..f67815678
--- /dev/null
+++ b/docs/resources/logo.svg
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="svg2"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:version="0.32"
+ sodipodi:docname="pyload_logo.svg"
+ x="0px"
+ y="0px"
+ width="1000"
+ height="1000"
+ viewBox="0 0 1000 1000"
+ enable-background="new 0 0 1000 1000"
+ xml:space="preserve"
+ inkscape:export-filename="/Users/christian/Development/pyload/docs/resources/pyload_logo.png"
+ inkscape:export-xdpi="9.2647057"
+ inkscape:export-ydpi="9.2647057"><title
+ id="title3794">pyLoad Logo</title><metadata
+ id="metadata33"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>pyLoad Logo</dc:title><dc:creator><cc:Agent><dc:title>pyLoad Team</dc:title></cc:Agent></dc:creator><cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-nc-sa/3.0/" /><dc:subject><rdf:Bag><rdf:li>pyload logo</rdf:li></rdf:Bag></dc:subject><dc:description>Official pyLoad logo with some optional filters</dc:description></cc:Work><cc:License
+ rdf:about="http://creativecommons.org/licenses/by-nc-sa/3.0/"><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
+ rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><defs
+ id="defs31"><color-profile
+ name="Generic-RGB-Profile"
+ xlink:href="/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
+ id="color-profile3792" /><linearGradient
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"
+ y2="752.55798"
+ x2="783.30792"
+ y1="134.5405"
+ x1="1220.123"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3778">
+ <stop
+ id="stop3780"
+ style="stop-color:#ffd856;stop-opacity:1;"
+ offset="0" />
+ <stop
+ id="stop3782"
+ style="stop-color:#ffc836;stop-opacity:1;"
+ offset="1" />
+ </linearGradient><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3778"
+ id="linearGradient3005"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0266298,0,0,1,-7.2192914,0)"
+ spreadMethod="pad"
+ x1="271.09799"
+ y1="499.06601"
+ x2="1001.7615"
+ y2="499.06601" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#path1948_3_"
+ id="linearGradient3783"
+ x1="25.991835"
+ y1="239.76074"
+ x2="763.24133"
+ y2="239.76074"
+ gradientUnits="userSpaceOnUse" /><filter
+ id="filter3020"
+ inkscape:label="Drop shadow"
+ width="1.5"
+ height="1.5"
+ x="-.25"
+ y="-.25"><feGaussianBlur
+ id="feGaussianBlur3022"
+ in="SourceAlpha"
+ stdDeviation="25"
+ result="blur" /><feColorMatrix
+ id="feColorMatrix3024"
+ result="bluralpha"
+ type="matrix"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /><feOffset
+ id="feOffset3026"
+ in="bluralpha"
+ dx="15"
+ dy="30"
+ result="offsetBlur" /><feMerge
+ id="feMerge3028"><feMergeNode
+ id="feMergeNode3030"
+ in="offsetBlur" /><feMergeNode
+ id="feMergeNode3032"
+ in="SourceGraphic" /></feMerge></filter><filter
+ id="filter3023"
+ inkscape:label="Outline"
+ inkscape:menu="Morphology"
+ inkscape:menu-tooltip="Draws a colored outline around"
+ width="1.5"
+ height="1.5"
+ x="-0.25"
+ y="-0.25"
+ color-interpolation-filters="sRGB"><feGaussianBlur
+ id="feGaussianBlur3025"
+ in="SourceAlpha"
+ stdDeviation="10"
+ result="blur" /><feColorMatrix
+ id="feColorMatrix3027"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1 "
+ result="result4" /><feFlood
+ id="feFlood3029"
+ result="result1"
+ flood-color="rgb(0,0,0)"
+ in="blur"
+ flood-opacity="1" /><feComposite
+ id="feComposite3031"
+ in2="result4"
+ result="result2"
+ in="result1"
+ operator="atop" /><feComposite
+ id="feComposite3033"
+ in2="result2"
+ in="SourceGraphic"
+ result="result3"
+ operator="atop" /></filter><filter
+ id="filter3140"
+ inkscape:label="3D Effect"
+ inkscape:menu="Bevels"
+ inkscape:menu-tooltip="Basic specular bevel to use for building textures"
+ color-interpolation-filters="sRGB"><feGaussianBlur
+ id="feGaussianBlur3142"
+ stdDeviation="6"
+ in="SourceGraphic"
+ result="result0" /><feDiffuseLighting
+ id="feDiffuseLighting3144"
+ lighting-color="rgb(255,255,255)"
+ diffuseConstant="1"
+ surfaceScale="4"
+ result="result5"><feDistantLight
+ id="feDistantLight3146"
+ elevation="45"
+ azimuth="235" /></feDiffuseLighting><feComposite
+ id="feComposite3148"
+ in2="SourceGraphic"
+ k1="1.4"
+ in="result5"
+ result="fbSourceGraphic"
+ operator="arithmetic" /><feGaussianBlur
+ id="feGaussianBlur3150"
+ result="result0"
+ in="fbSourceGraphic"
+ stdDeviation="6" /><feSpecularLighting
+ id="feSpecularLighting3152"
+ specularExponent="25"
+ specularConstant="1"
+ surfaceScale="4"
+ lighting-color="rgb(255,255,255)"
+ result="result1"
+ in="result0"><feDistantLight
+ id="feDistantLight3154"
+ azimuth="235"
+ elevation="45" /></feSpecularLighting><feComposite
+ id="feComposite3156"
+ in2="result91"
+ k3="1"
+ k2="1"
+ operator="arithmetic"
+ result="result4"
+ in="fbSourceGraphic" /><feComposite
+ id="feComposite3158"
+ in2="SourceGraphic"
+ operator="in"
+ result="result2"
+ in="result4" /></filter></defs><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1352"
+ inkscape:window-height="766"
+ id="namedview29"
+ showgrid="false"
+ inkscape:zoom="0.5"
+ inkscape:cx="556.17192"
+ inkscape:cy="487.74566"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g2303_1_"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ showborder="true"
+ borderlayer="false"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+<g
+ id="g2303"
+ display="none"
+ style="display:none"
+ transform="translate(-21.799529,1.0000288)">
+
+ <linearGradient
+ id="path1948_2_"
+ gradientUnits="userSpaceOnUse"
+ x1="-338.45169"
+ y1="1559.3135"
+ x2="627.55139"
+ y2="728.19678"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#5A9FD4"
+ id="stop5" />
+ <stop
+ offset="1"
+ style="stop-color:#306998"
+ id="stop7" />
+ </linearGradient>
+ <path
+ id="path1948"
+ display="inline"
+ d="M 493.674,-0.868 H 256.098 166.937 c -69.042,0 -129.5,41.498 -148.412,120.447 -21.813,90.495 -22.783,146.964 0,241.457 16.887,70.339 57.219,120.447 126.26,120.447 h 81.686 V 372.939 c 0,-78.413 67.856,-147.585 148.416,-147.585 h 237.298 c 66.057,0 118.787,-54.385 118.787,-120.727 L 730.846,-0.89"
+ inkscape:connector-curvature="0"
+ style="fill:url(#path1948_2_);display:inline" />
+
+ <linearGradient
+ id="path1950_2_"
+ gradientUnits="userSpaceOnUse"
+ x1="1186.2559"
+ y1="133.9731"
+ x2="749.44067"
+ y2="751.99072"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#FFD43B"
+ id="stop11" />
+ <stop
+ offset="1"
+ style="stop-color:#FFE873"
+ id="stop13" />
+ </linearGradient>
+ <path
+ id="path1950"
+ display="inline"
+ d="m 755.602,0.132 v 105.496 c 0,81.799 -69.346,150.631 -148.418,150.631 H 369.886 c -65.002,0 -118.788,55.632 -118.788,120.727 v 226.221 c 0,64.39 55.985,102.262 118.788,120.727 C 412.959,736.601 500,1000 500,1000 c 0,0 68.736,-264.907 107.184,-276.066 59.809,-17.313 118.787,-52.168 118.787,-120.727 V 512.666 H 488.673 V 482.483 H 725.97 844.761 c 69.049,0 94.783,-48.163 118.787,-120.447 24.809,-74.417 23.752,-145.985 0,-241.457 C 946.486,51.85 913.898,0.132 844.762,0.132 h -89.16 z M 622.137,573.026 c 24.627,0 44.578,20.179 44.578,45.137 0,25.041 -19.951,45.409 -44.578,45.409 -24.541,0 -44.58,-20.368 -44.58,-45.409 0,-24.958 20.039,-45.137 44.58,-45.137 z"
+ inkscape:connector-curvature="0"
+ style="fill:url(#path1950_2_);display:inline" />
+</g>
+<g
+ id="g2303_1_"
+ transform="translate(-21.799529,1.0000288)">
+
+ <linearGradient
+ id="path1948_3_"
+ gradientUnits="userSpaceOnUse"
+ x1="-303.33109"
+ y1="1560.5684"
+ x2="662.672"
+ y2="729.45172"
+ gradientTransform="matrix(0.57252813,0,0,-0.568,127.65196,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#3778b0;stop-opacity:1;"
+ id="stop18" />
+ <stop
+ offset="1"
+ style="stop-color:#356b97;stop-opacity:1;"
+ id="stop20" />
+ </linearGradient>
+ <path
+ style="fill:url(#linearGradient3783);fill-opacity:1;"
+ d="M 187.6338,-0.828786 C 142.54691,-0.4970408 97.509189,20.00164 69.766894,55.506872 50.404869,79.267481 40.287236,108.86774 34.426911,138.63795 22.416163,189.64033 17.78885,242.92961 25.780677,294.93728 c 5.106096,32.23417 11.057028,64.60794 22.392916,95.27781 13.479342,35.90012 38.974607,69.46226 76.144837,82.91501 27.93718,10.93473 54.60541,6.72414 83.9641,7.53695 13.63487,0 24.96913,0 38.604,0 0.2713,-39.75457 -0.23921,-79.53817 0.7,-119.27522 4.44911,-60.56565 57.97765,-113.83582 116.49553,-129.10577 28.59598,-7.98504 58.75298,-3.97369 88.12613,-4.95196 65.78932,-0.1699 131.59413,0.33658 197.37399,-0.24727 46.56452,-2.36951 89.41253,-33.74388 105.96031,-76.7788 8.4927,-20.25194 9.04154,-42.50123 8.36849,-64.136866 -0.0327,-28.519022 -0.0719,-58.538168 -0.10062,-87.0571928 -160.2708,-0.8222611 -320.54247,-0.1692627 -480.8154,0.027 -31.78694,0.021047 -63.57472,-0.0424765 -95.36116,0.0302428 z"
+ id="path1948_1_"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+
+ <linearGradient
+ id="path1950_3_"
+ gradientUnits="userSpaceOnUse"
+ x1="1220.123"
+ y1="134.5405"
+ x2="783.30792"
+ y2="752.55798"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"
+ xlink:href="#linearGradient3778">
+ <stop
+ offset="0"
+ style="stop-color:#fae13f;stop-opacity:1;"
+ id="stop24" />
+ <stop
+ offset="1"
+ style="stop-color:#facc2b;stop-opacity:1;"
+ id="stop26" />
+ </linearGradient>
+ <path
+ id="path1950_1_"
+ d="m 789.03685,-0.868 v 105.496 c 0,81.799 -71.19267,150.631 -152.37034,150.631 H 393.0493 c -66.73298,0 -121.9513,55.632 -121.9513,120.727 v 226.221 c 0,64.39 255.53022,396.793 255.53022,396.793 0,0 231.98858,-328.234 231.98858,-396.793 V 511.666 H 515.00061 V 481.483 H 758.6168 880.57116 c 70.88777,0 97.30706,-48.163 121.95034,-120.447 25.4697,-74.417 24.3844,-145.985 0,-241.457 C 985.00406,50.85 951.54825,-0.868 880.57116,-0.868 H 789.03685 z M 652.0177,572.026 c 25.28282,0 45.76511,20.179 45.76511,45.137 0,25.041 -20.48229,45.409 -45.76511,45.409 -25.19452,0 -45.76716,-20.368 -45.76716,-45.409 0,-24.958 20.57264,-45.137 45.76716,-45.137 z"
+ style="fill:url(#linearGradient3005);fill-opacity:1;"
+ inkscape:connector-curvature="0" />
+</g>
+</svg> \ No newline at end of file
diff --git a/setup.cfg b/docs/setup.cfg
index 51c054a75..51c054a75 100644
--- a/setup.cfg
+++ b/docs/setup.cfg
diff --git a/docs/write_addons.rst b/docs/write_addons.rst
new file mode 100644
index 000000000..58e73e675
--- /dev/null
+++ b/docs/write_addons.rst
@@ -0,0 +1,172 @@
+.. _write_addons:
+
+Addons
+======
+
+A Addon is a python file which is located at :file:`pyload/plugin/addon`.
+The :class:`AddonManager <pyload.manager.Addon.AddonManager>` will load it automatically on startup. Only one instance exists
+over the complete lifetime of pyload. Your addon can interact on various events called by the :class:`AddonManager <pyload.manager.Addon.AddonManager>`,
+do something complete autonomic and has full access to the :class:`Api <pyload.api.Api>` and every detail of pyLoad.
+The UpdateManager, CaptchaTrader, UnRar and many more are realised as addon.
+
+Addon header
+-----------
+
+Your addon needs to subclass :class:`Addon <pyload.plugin.Addon.Addon>` and will inherit all of its method, make sure to check its documentation!
+
+All addons should start with something like this: ::
+
+ from pyload.plugin.Addon import Addon
+
+
+ class YourAddon(Addon):
+ __name = "YourAddon"
+ __tupe = "addon"
+ __version = "0.1"
+
+ __config = [("activated", "bool", "Activated", "True")]
+
+ __description = "Does really cool stuff"
+ __license = "Your license short name"
+ __authors = [("Me", "me@has-no-mail.com")]
+
+All meta-data is defined in the header, you need at least one option at ``__config`` so the user can toggle your
+addon on and off. Dont't overwrite the ``init`` method if not neccesary, use ``setup`` instead.
+
+Using the Config
+----------------
+
+We are taking a closer look at the ``__config`` parameter.
+You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``.
+When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name, value``.
+
+
+Interacting on Events
+---------------------
+
+The next step is to think about where your Addon action takes places.
+
+The easiest way is to overwrite specific methods defined by the :class:`Addon <pyload.plugin.Addon.Addon>` base class.
+The name is indicating when the function gets called.
+See :class:`Addon <pyload.plugin.Addon.Addon>` page for a complete listing.
+
+You should be aware of the arguments the Addon is called with, whether its a :class:`PyFile <pyload.datatype.File.PyFile>`
+or :class:`PyPackage <pyload.datatype.Package.PyPackage>` you should read its related documentation to know how to access her great power and manipulate them.
+
+A basic excerpt would look like: ::
+
+ from pyload.plugin.Addon import Addon
+
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ def activate(self):
+ print "Yay, the core is ready let's do some work."
+
+
+ def downloadFinished(self, pyfile):
+ print "A Download just finished."
+
+Another and more flexible and powerful way is to use event listener.
+All addon methods exists as event and very useful additional events are dispatched by the core. For a little overview look
+at :class:`AddonManager <pyload.manager.Addon.AddonManager>`. Keep in mind that you can define own events and other people may listen on them.
+
+For your convenience it's possible to register listeners automatical via the ``event_map`` attribute.
+It requires a `dict` that maps event names to function names or a `list` of function names. It's important that all names are strings ::
+
+ from pyload.plugin.Addon import Addon
+
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ event_map = {'downloadFinished': "doSomeWork",
+ 'allDownloadsFnished': "someMethod",
+ 'activate': "initialize"}
+
+
+ def initialize(self):
+ print "Initialized."
+
+
+ def doSomeWork(self, pyfile):
+ print "This is equivalent to the above example."
+
+
+ def someMethod(self):
+ print "The underlying event (allDownloadsFinished) for this method is not available through the base class"
+
+An advantage of the event listener is that you are able to register and remove the listeners at runtime.
+Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name", function)` and see doc for
+:class:`AddonManager <pyload.manager.Addon.AddonManager>`. Contrary to ``event_map``, ``function`` has to be a reference
+and **not** a `string`.
+
+We introduced events because it scales better if there a a huge amount of events and addons. So all future interaction will be exclusive
+available as event and not accessible through overwriting addon methods. However you can safely do this, it will not be removed and is easier to implement.
+
+
+Providing RPC services
+----------------------
+
+You may noticed that pyLoad has an :class:`Api <pyload.api.Api>`, which can be used internal or called by clients via RPC.
+So probably clients want to be able to interact with your addon to request it's state or invoke some action.
+
+Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: ::
+
+ from pyload.plugin.Addon import Addon, Expose
+
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ @Expose
+ def invoke(self, arg):
+ print "Invoked with", arg
+
+Thats all, it's available via the :class:`Api <pyload.api.Api>` now. If you want to use it read :ref:`access_api`.
+Here is a basic example: ::
+
+ # Assuming client is a ThriftClient or Api object
+
+ print client.getServices()
+ print client.call(ServiceCall("YourAddon", "invoke", "an argument"))
+
+Providing status information
+----------------------------
+Your addon can store information in a ``dict`` that can easily be retrievied via the :class:`Api <pyload.api.Api>`.
+
+Just store everything in ``self.info``. ::
+
+ from pyload.plugin.Addon import Addon
+
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ def setup(self):
+ self.info = {'running': False}
+
+
+ def activate(self):
+ self.info['running'] = True
+
+Usable with: ::
+
+ # Assuming client is a ThriftClient or Api object
+
+ print client.getAllInfo()
+
+Example
+-------
+ Sorry but you won't find an example here ;-)
+
+ Look at :file:`pyload/plugin/addon` and you will find plenty examples there.
diff --git a/docs/write_hooks.rst b/docs/write_hooks.rst
deleted file mode 100644
index dd60367b7..000000000
--- a/docs/write_hooks.rst
+++ /dev/null
@@ -1,162 +0,0 @@
-.. _write_hooks:
-
-Hooks
-=====
-
-A Hook is a python file which is located at :file:`module/plugins/hooks`.
-The :class:`HookManager <module.HookManager.HookManager>` will load it automatically on startup. Only one instance exists
-over the complete lifetime of pyload. Your hook can interact on various events called by the :class:`HookManager <module.HookManager.HookManager>`,
-do something complete autonomic and has full access to the :class:`Api <module.Api.Api>` and every detail of pyLoad.
-The UpdateManager, CaptchaTrader, UnRar and many more are realised as hooks.
-
-Hook header
------------
-
-Your hook needs to subclass :class:`Hook <module.plugins.Hook.Hook>` and will inherit all of its method, make sure to check its documentation!
-
-All Hooks should start with something like this: ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- __name__ = "YourHook"
- __version__ = "0.1"
- __description__ = "Does really cool stuff"
- __config__ = [ ("activated" , "bool" , "Activated" , "True" ) ]
- __threaded__ = ["downloadFinished"]
- __author_name__ = ("Me")
- __author_mail__ = ("me@has-no-mail.com")
-
-All meta-data is defined in the header, you need at least one option at ``__config__`` so the user can toggle your
-hook on and off. Dont't overwrite the ``init`` method if not neccesary, use ``setup`` instead.
-
-Using the Config
-----------------
-
-We are taking a closer look at the ``__config__`` parameter.
-You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``.
-When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name,value``.
-
-
-Interacting on Events
----------------------
-
-The next step is to think about where your Hook action takes places.
-
-The easiest way is to overwrite specific methods defined by the :class:`Hook <module.plugins.Hook.Hook>` base class.
-The name is indicating when the function gets called.
-See :class:`Hook <module.plugins.Hook.Hook>` page for a complete listing.
-
-You should be aware of the arguments the Hooks are called with, whether its a :class:`PyFile <module.PyFile.PyFile>`
-or :class:`PyPackage <module.PyPackage.PyPackage>` you should read its related documentation to know how to access her great power and manipulate them.
-
-A basic excerpt would look like: ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- def coreReady(self):
- print "Yay, the core is ready let's do some work."
-
- def downloadFinished(self, pyfile):
- print "A Download just finished."
-
-Another important feature to mention can be seen at the ``__threaded__`` parameter. Function names listed will be executed
-in a thread, in order to not block the main thread. This should be used for all kind of longer processing tasks.
-
-Another and more flexible and powerful way is to use event listener.
-All hook methods exists as event and very useful additional events are dispatched by the core. For a little overview look
-at :class:`HookManager <module.HookManager.HookManager>`. Keep in mind that you can define own events and other people may listen on them.
-
-For your convenience it's possible to register listeners automatical via the ``event_map`` attribute.
-It requires a `dict` that maps event names to function names or a `list` of function names. It's important that all names are strings ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
- event_map = {"downloadFinished" : "doSomeWork",
- "allDownloadsFnished": "someMethod",
- "coreReady": "initialize"}
-
- def initialize(self):
- print "Initialized."
-
- def doSomeWork(self, pyfile):
- print "This is equivalent to the above example."
-
- def someMethod(self):
- print "The underlying event (allDownloadsFinished) for this method is not available through the base class"
-
-An advantage of the event listener is that you are able to register and remove the listeners at runtime.
-Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name", function)` and see doc for
-:class:`HookManager <module.HookManager.HookManager>`. Contrary to ``event_map``, ``function`` has to be a reference
-and **not** a `string`.
-
-We introduced events because it scales better if there a a huge amount of events and hooks. So all future interaction will be exclusive
-available as event and not accessible through overwriting hook methods. However you can safely do this, it will not be removed and is easier to implement.
-
-
-Providing RPC services
-----------------------
-
-You may noticed that pyLoad has an :class:`Api <module.Api.Api>`, which can be used internal or called by clients via RPC.
-So probably clients want to be able to interact with your hook to request it's state or invoke some action.
-
-Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: ::
-
- from module.plugins.Hook import Hook, Expose
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- @Expose
- def invoke(self, arg):
- print "Invoked with", arg
-
-Thats all, it's available via the :class:`Api <module.Api.Api>` now. If you want to use it read :ref:`access_api`.
-Here is a basic example: ::
-
- #Assuming client is a ThriftClient or Api object
-
- print client.getServices()
- print client.call(ServiceCall("YourHook", "invoke", "an argument"))
-
-Providing status information
-----------------------------
-Your hook can store information in a ``dict`` that can easily be retrievied via the :class:`Api <module.Api.Api>`.
-
-Just store everything in ``self.info``. ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- def setup(self):
- self.info = {"running": False}
-
- def coreReady(self):
- self.info["running"] = True
-
-Usable with: ::
-
- #Assuming client is a ThriftClient or Api object
-
- print client.getAllInfo()
-
-Example
--------
- Sorry but you won't find an example here ;-)
-
- Look at :file:`module/plugins/hooks` and you will find plenty examples there.
diff --git a/docs/write_plugins.rst b/docs/write_plugins.rst
index b513a5978..8d42aea7f 100644
--- a/docs/write_plugins.rst
+++ b/docs/write_plugins.rst
@@ -3,15 +3,15 @@
Plugins
=======
-A Plugin is a python file located at one of the subfolders in :file:`module/plugins/`. Either :file:`hoster`, :file:`crypter`
+A Plugin is a python file located at one of the subfolders in :file:`pyload/plugin/`. Either :file:`hoster`, :file:`crypter`
or :file:`container`, depending of it's type.
There are three kinds of different plugins: **Hoster**, **Crypter**, **Container**.
-All kind of plugins inherit from the base :class:`Plugin <module.plugins.Plugin.Plugin>`. You should know its
+All kind of plugins inherit from the base :class:`Plugin <pyload.plugin.Plugin.Plugin>`. You should know its
convenient methods, they make your work easier ;-)
-Every plugin defines a ``__pattern__`` and when the user adds urls, every url is matched against the pattern defined in
-the plugin. In case the ``__pattern__`` matched on the url the plugin will be assigned to handle it and instanciated when
+Every plugin defines a ``__pattern`` and when the user adds urls, every url is matched against the pattern defined in
+the plugin. In case the ``__pattern`` matched on the url the plugin will be assigned to handle it and instanciated when
pyLoad begins to download/decrypt the url.
Plugin header
@@ -19,18 +19,19 @@ Plugin header
How basic hoster plugin header could look like: ::
- from module.plugin.Hoster import Hoster
+ from pyload.plugin.Hoster import Hoster
+
class MyFileHoster(Hoster):
- __name__ = "MyFileHoster"
- __version__ = "0.1"
- __pattern__ = r"http://myfilehoster.example.com/file_id/[0-9]+"
- __config__ = []
+ __name = "MyFileHoster"
+ __version = "0.1"
+ __pattern = r"http://myfilehoster.example.com/file_id/[0-9]+"
+ __config = []
-You have to define these meta-data, ``__pattern__`` has to be a regexp that sucessfully compiles with
-``re.compile(__pattern__)``.
+You have to define these meta-data, ``__pattern`` has to be a regexp that sucessfully compiles with
+``re.compile(__pattern)``.
-Just like :ref:`write_hooks` you can add and use config values exatly the same way.
+Just like :ref:`write_addons` you can add and use config values exatly the same way.
If you want a Crypter or Container plugin, just replace the word Hoster with your desired plugin type.
@@ -41,15 +42,16 @@ We head to the next important section, the ``process`` method of your plugin.
In fact the ``process`` method is the only functionality your plugin has to provide, but its always a good idea to split up tasks to not produce spaghetti code.
An example ``process`` function could look like this ::
- from module.plugin.Hoster import Hoster
+ from pyload.plugin.Hoster import Hoster
+
class MyFileHoster(Hoster):
"""
plugin code
"""
-
+
def process(self, pyfile):
- html = self.load(pyfile.url) # load the content of the orginal pyfile.url to html
+ html = self.load(pyfile.url) #: load the content of the orginal pyfile.url to html
# parse the name from the site and set attribute in pyfile
pyfile.name = self.myFunctionToParseTheName(html)
@@ -58,7 +60,7 @@ An example ``process`` function could look like this ::
# download the file, destination is determined by pyLoad
self.download(parsed_url)
-You need to know about the :class:`PyFile <module.PyFile.PyFile>` class, since an instance of it is given as parameter to every pyfile.
+You need to know about the :class:`PyFile <pyload.datatype.File.PyFile>` class, since an instance of it is given as parameter to every pyfile.
Some tasks your plugin should handle: proof if file is online, get filename, wait if needed, download the file, etc..
Wait times
@@ -71,7 +73,7 @@ Captcha decrypting
__________________
To handle captcha input just use ``self.decryptCaptcha(url, ...)``, it will be send to clients
-or handled by :class:`Hook <module.plugins.Hook.Hook>` plugins
+or handled by :class:`Addon <pyload.plugin.Addon.Addon>` plugins
Crypter
-------
@@ -81,23 +83,25 @@ Well, they work nearly the same, only that the function they have to provide is
Example: ::
- from module.plugin.Crypter import Crypter
+ from pyload.plugin.Crypter import Crypter
+
class MyFileCrypter(Crypter):
"""
plugin code
"""
+
def decrypt(self, pyfile):
urls = ["http://get.pyload.org/src", "http://get.pyload.org/debian", "http://get.pyload.org/win"]
- self.packages.append(("pyLoad packages", urls, "pyLoad packages")) # urls list of urls
+ self.packages.append(("pyLoad packages", urls, "pyLoad packages")) #: urls list of urls
-They can access all the methods from :class:`Plugin <module.plugins.Plugin.Plugin>`, but the important thing is they
+They can access all the methods from :class:`Plugin <pyload.plugin.Plugin.Plugin>`, but the important thing is they
have to append all packages they parsed to the `self.packages` list. Simply append tuples with `(name, urls, folder)`,
where urls is the list of urls contained in the packages. Thats all of your work, pyLoad will know what to do with them.
Examples
--------
-Best examples are already existing plugins in :file:`module/plugins/`. \ No newline at end of file
+Best examples are already existing plugins in :file:`pyload/plugin/`.
diff --git a/docs/write_scripts.rst b/docs/write_scripts.rst
new file mode 100644
index 000000000..55c0e1665
--- /dev/null
+++ b/docs/write_scripts.rst
@@ -0,0 +1,25 @@
+.. _write_scripts:
+
+Scripts
+=======
+
+pyLoad is able to start any kind of scripts at given events.
+Simply put your script in a suitable folder and pyLoad will execute it at the given events and pass some arguments to them.
+
+
+***Note:***
+**Every script starting with symbol `#` will be ignored!** (ex.: `#converter.sh`)
+
+***Note:***
+You have to restart pyload when you change script names or locations.
+
+
+Below you see the list of arguments, which are passed to the scripts.
+
+### Argument list ###
+
+ - `download_preparing`: **`pluginname` `url`**
+ - `download_finished`: **`pluginname` `url` `filename` `filelocation`**
+ - `package_finshed`: **`packagename` `packagelocation`**
+ - `before_reconnect`: **`oldip`**
+ - `after_reconnect`: **`newip`**
diff --git a/icons/abort.png b/icons/abort.png
deleted file mode 100644
index 66170aae7..000000000
--- a/icons/abort.png
+++ /dev/null
Binary files differ
diff --git a/icons/add_small.png b/icons/add_small.png
deleted file mode 100644
index 4dc88b09b..000000000
--- a/icons/add_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/clipboard.png b/icons/clipboard.png
deleted file mode 100644
index 9ba608eba..000000000
--- a/icons/clipboard.png
+++ /dev/null
Binary files differ
diff --git a/icons/close.png b/icons/close.png
deleted file mode 100644
index 66170aae7..000000000
--- a/icons/close.png
+++ /dev/null
Binary files differ
diff --git a/icons/edit_small.png b/icons/edit_small.png
deleted file mode 100644
index eb76e21b4..000000000
--- a/icons/edit_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/logo-gui.png b/icons/logo-gui.png
deleted file mode 100644
index 5994b274d..000000000
--- a/icons/logo-gui.png
+++ /dev/null
Binary files differ
diff --git a/icons/logo.png b/icons/logo.png
deleted file mode 100644
index 72a95b740..000000000
--- a/icons/logo.png
+++ /dev/null
Binary files differ
diff --git a/icons/pull_small.png b/icons/pull_small.png
deleted file mode 100644
index 432ad321f..000000000
--- a/icons/pull_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/push_small.png b/icons/push_small.png
deleted file mode 100644
index 701fc69e3..000000000
--- a/icons/push_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/pyload-gui.ico b/icons/pyload-gui.ico
deleted file mode 100644
index 00a1a53ff..000000000
--- a/icons/pyload-gui.ico
+++ /dev/null
Binary files differ
diff --git a/icons/pyload.ico b/icons/pyload.ico
deleted file mode 100644
index 58b1f4b89..000000000
--- a/icons/pyload.ico
+++ /dev/null
Binary files differ
diff --git a/icons/pyload2.ico b/icons/pyload2.ico
deleted file mode 100644
index c2b497986..000000000
--- a/icons/pyload2.ico
+++ /dev/null
Binary files differ
diff --git a/icons/refresh1_small.png b/icons/refresh1_small.png
deleted file mode 100644
index ce4f24efc..000000000
--- a/icons/refresh1_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/refresh_small.png b/icons/refresh_small.png
deleted file mode 100644
index 1ffd18d97..000000000
--- a/icons/refresh_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/remove_small.png b/icons/remove_small.png
deleted file mode 100644
index bf99763e8..000000000
--- a/icons/remove_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_add.png b/icons/toolbar_add.png
deleted file mode 100644
index 17003e9f0..000000000
--- a/icons/toolbar_add.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_pause.png b/icons/toolbar_pause.png
deleted file mode 100644
index b7a727b71..000000000
--- a/icons/toolbar_pause.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_remove.png b/icons/toolbar_remove.png
deleted file mode 100644
index 1e9c00e16..000000000
--- a/icons/toolbar_remove.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_start.png b/icons/toolbar_start.png
deleted file mode 100644
index 1123266e6..000000000
--- a/icons/toolbar_start.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_stop.png b/icons/toolbar_stop.png
deleted file mode 100644
index b388e3d72..000000000
--- a/icons/toolbar_stop.png
+++ /dev/null
Binary files differ
diff --git a/module/lib/BeautifulSoup.py b/lib/Python/Lib/BeautifulSoup.py
index 55567f588..55567f588 100644
--- a/module/lib/BeautifulSoup.py
+++ b/lib/Python/Lib/BeautifulSoup.py
diff --git a/module/lib/Getch.py b/lib/Python/Lib/Getch.py
index 22b7ea7f8..22b7ea7f8 100644
--- a/module/lib/Getch.py
+++ b/lib/Python/Lib/Getch.py
diff --git a/module/lib/MultipartPostHandler.py b/lib/Python/Lib/MultipartPostHandler.py
index 94aee0193..94aee0193 100644
--- a/module/lib/MultipartPostHandler.py
+++ b/lib/Python/Lib/MultipartPostHandler.py
diff --git a/module/lib/SafeEval.py b/lib/Python/Lib/SafeEval.py
index 8fc57f261..8fc57f261 100644
--- a/module/lib/SafeEval.py
+++ b/lib/Python/Lib/SafeEval.py
diff --git a/module/lib/Unzip.py b/lib/Python/Lib/Unzip.py
index f56fbe751..f56fbe751 100644
--- a/module/lib/Unzip.py
+++ b/lib/Python/Lib/Unzip.py
diff --git a/module/__init__.py b/lib/Python/Lib/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/__init__.py
+++ b/lib/Python/Lib/__init__.py
diff --git a/module/lib/beaker/__init__.py b/lib/Python/Lib/beaker/__init__.py
index 792d60054..792d60054 100644
--- a/module/lib/beaker/__init__.py
+++ b/lib/Python/Lib/beaker/__init__.py
diff --git a/module/lib/beaker/cache.py b/lib/Python/Lib/beaker/cache.py
index 4a96537ff..4a96537ff 100644
--- a/module/lib/beaker/cache.py
+++ b/lib/Python/Lib/beaker/cache.py
diff --git a/module/lib/beaker/container.py b/lib/Python/Lib/beaker/container.py
index 515e97af6..515e97af6 100644
--- a/module/lib/beaker/container.py
+++ b/lib/Python/Lib/beaker/container.py
diff --git a/module/lib/beaker/converters.py b/lib/Python/Lib/beaker/converters.py
index f0ad34963..f0ad34963 100644
--- a/module/lib/beaker/converters.py
+++ b/lib/Python/Lib/beaker/converters.py
diff --git a/module/lib/beaker/crypto/__init__.py b/lib/Python/Lib/beaker/crypto/__init__.py
index 3e26b0c13..3e26b0c13 100644
--- a/module/lib/beaker/crypto/__init__.py
+++ b/lib/Python/Lib/beaker/crypto/__init__.py
diff --git a/module/lib/beaker/crypto/jcecrypto.py b/lib/Python/Lib/beaker/crypto/jcecrypto.py
index 4062d513e..4062d513e 100644
--- a/module/lib/beaker/crypto/jcecrypto.py
+++ b/lib/Python/Lib/beaker/crypto/jcecrypto.py
diff --git a/module/lib/beaker/crypto/pbkdf2.py b/lib/Python/Lib/beaker/crypto/pbkdf2.py
index 96dc5fbb2..96dc5fbb2 100644
--- a/module/lib/beaker/crypto/pbkdf2.py
+++ b/lib/Python/Lib/beaker/crypto/pbkdf2.py
diff --git a/module/lib/beaker/crypto/pycrypto.py b/lib/Python/Lib/beaker/crypto/pycrypto.py
index a3eb4d9db..a3eb4d9db 100644
--- a/module/lib/beaker/crypto/pycrypto.py
+++ b/lib/Python/Lib/beaker/crypto/pycrypto.py
diff --git a/module/lib/beaker/crypto/util.py b/lib/Python/Lib/beaker/crypto/util.py
index d97e8ce6f..d97e8ce6f 100644
--- a/module/lib/beaker/crypto/util.py
+++ b/lib/Python/Lib/beaker/crypto/util.py
diff --git a/module/lib/beaker/exceptions.py b/lib/Python/Lib/beaker/exceptions.py
index cc0eed286..cc0eed286 100644
--- a/module/lib/beaker/exceptions.py
+++ b/lib/Python/Lib/beaker/exceptions.py
diff --git a/module/lib/__init__.py b/lib/Python/Lib/beaker/ext/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/lib/__init__.py
+++ b/lib/Python/Lib/beaker/ext/__init__.py
diff --git a/module/lib/beaker/ext/database.py b/lib/Python/Lib/beaker/ext/database.py
index 701e6f7d2..701e6f7d2 100644
--- a/module/lib/beaker/ext/database.py
+++ b/lib/Python/Lib/beaker/ext/database.py
diff --git a/module/lib/beaker/ext/google.py b/lib/Python/Lib/beaker/ext/google.py
index dd8380d7f..dd8380d7f 100644
--- a/module/lib/beaker/ext/google.py
+++ b/lib/Python/Lib/beaker/ext/google.py
diff --git a/module/lib/beaker/ext/memcached.py b/lib/Python/Lib/beaker/ext/memcached.py
index 96516953f..96516953f 100644
--- a/module/lib/beaker/ext/memcached.py
+++ b/lib/Python/Lib/beaker/ext/memcached.py
diff --git a/module/lib/beaker/ext/sqla.py b/lib/Python/Lib/beaker/ext/sqla.py
index 8c79633c1..8c79633c1 100644
--- a/module/lib/beaker/ext/sqla.py
+++ b/lib/Python/Lib/beaker/ext/sqla.py
diff --git a/module/lib/beaker/middleware.py b/lib/Python/Lib/beaker/middleware.py
index 7ba88b37d..7ba88b37d 100644
--- a/module/lib/beaker/middleware.py
+++ b/lib/Python/Lib/beaker/middleware.py
diff --git a/module/lib/beaker/session.py b/lib/Python/Lib/beaker/session.py
index 7d465530b..7d465530b 100644
--- a/module/lib/beaker/session.py
+++ b/lib/Python/Lib/beaker/session.py
diff --git a/module/lib/beaker/synchronization.py b/lib/Python/Lib/beaker/synchronization.py
index 761303707..761303707 100644
--- a/module/lib/beaker/synchronization.py
+++ b/lib/Python/Lib/beaker/synchronization.py
diff --git a/module/lib/beaker/util.py b/lib/Python/Lib/beaker/util.py
index 04c9617c5..04c9617c5 100644
--- a/module/lib/beaker/util.py
+++ b/lib/Python/Lib/beaker/util.py
diff --git a/lib/Python/Lib/bitmath/__init__.py b/lib/Python/Lib/bitmath/__init__.py
new file mode 100644
index 000000000..1a8283655
--- /dev/null
+++ b/lib/Python/Lib/bitmath/__init__.py
@@ -0,0 +1,1261 @@
+# -*- coding: utf-8 -*-
+# The MIT License (MIT)
+#
+# Copyright © 2014 Tim Bielawa <timbielawa@gmail.com>
+# See GitHub Contributors Graph for more information
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sub-license, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Reference material:
+
+Prefixes for binary multiples:
+http://physics.nist.gov/cuu/Units/binary.html
+
+decimal and binary prefixes:
+man 7 units (from the Linux Documentation Project 'man-pages' package)
+
+
+BEFORE YOU GET HASTY WITH EXCLUDING CODE FROM COVERAGE: If you
+absolutely need to skip code coverage because of a strange Python 2.x
+vs 3.x thing, use the fancy environment substitution stuff from the
+.coverage RC file. In review:
+
+* If you *NEED* to skip a statement because of Python 2.x issues add the following:
+
+ # pragma: PY2X no cover
+
+* If you *NEED* to skip a statement because of Python 3.x issues add the following:
+
+ # pragma: PY3X no cover
+
+In this configuration, statements which are skipped in 2.x are still
+covered in 3.x, and the reverse holds true for tests skipped in 3.x.
+"""
+
+from __future__ import print_function
+
+import argparse
+import contextlib
+import fnmatch
+import math
+import numbers
+import os
+import os.path
+import sys
+
+__all__ = ['Bit', 'Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB',
+ 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'Kib',
+ 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'kb', 'Mb', 'Gb', 'Tb',
+ 'Pb', 'Eb', 'Zb', 'Yb', 'getsize', 'listdir', 'format',
+ 'format_string', 'format_plural', 'parse_string']
+
+# Python 3.x compat
+if sys.version > '3':
+ long = int # pragma: PY2X no cover
+
+# Constants for referring to prefix systems
+NIST = int(2)
+SI = int(10)
+
+SI_PREFIXES = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
+SI_STEPS = {
+ 'Bit': 1 / 8.0,
+ 'Byte': 1,
+ 'k': 1000,
+ 'M': 1000000,
+ 'G': 1000000000,
+ 'T': 1000000000000,
+ 'P': 1000000000000000,
+ 'E': 1000000000000000000,
+ 'Z': 1000000000000000000000,
+ 'Y': 1000000000000000000000000
+}
+
+NIST_PREFIXES = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei']
+NIST_STEPS = {
+ 'Bit': 1 / 8.0,
+ 'Byte': 1,
+ 'Ki': 1024,
+ 'Mi': 1048576,
+ 'Gi': 1073741824,
+ 'Ti': 1099511627776,
+ 'Pi': 1125899906842624,
+ 'Ei': 1152921504606846976
+}
+
+# A list of all the valid prefix unit types. Mostly for reference,
+# also used by the CLI tool as valid types
+
+ALL_UNIT_TYPES = ['Bit', 'Byte', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', 'Tb',
+ 'TB', 'Pb', 'PB', 'Eb', 'EB', 'Zb', 'ZB', 'Yb',
+ 'YB', 'Kib', 'KiB', 'Mib', 'MiB', 'Gib', 'GiB',
+ 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', 'EiB']
+
+######################################################################
+# Set up our module variables/constants
+
+###################################
+# Internal:
+
+# Console repr(), ex: MiB(13.37), or kB(42.0)
+_FORMAT_REPR = '{unit_singular}({value})'
+
+###################################
+# Exposed:
+
+# String representation, ex:13.37 MiB, or 42.0 kB
+format_string = "{value} {unit}"
+
+# Pluralization behavior
+format_plural = False
+
+
+######################################################################
+# Base class for everything else
+class Bitmath(object):
+ """The base class for all the other prefix classes"""
+
+ def __init__(self, value=0, bytes=None, bits=None):
+ """Instantiate with `value` by the unit, in plain bytes, or
+bits. Don't supply more than one keyword.
+
+default behavior: initialize with value of 0
+only setting value: assert bytes is None and bits is None
+only setting bytes: assert value == 0 and bits is None
+only setting bits: assert value == 0 and bytes is None
+ """
+ _raise = False
+ if (value == 0) and (bytes is None) and (bits is None):
+ pass
+ # Setting by bytes
+ elif bytes is not None:
+ if (value == 0) and (bits is None):
+ pass
+ else:
+ _raise = True
+ # setting by bits
+ elif bits is not None:
+ if (value == 0) and (bytes is None):
+ pass
+ else:
+ _raise = True
+
+ if _raise:
+ raise ValueError("Only one parameter of: value, bytes, or bits is allowed")
+
+ self._do_setup()
+ if bytes:
+ # We were provided with the fundamental base unit, no need
+ # to normalize
+ self._byte_value = bytes
+ self._bit_value = bytes * 8.0
+ elif bits:
+ # We were *ALMOST* given the fundamental base
+ # unit. Translate it into the fundamental unit then
+ # normalize.
+ self._byte_value = bits / 8.0
+ self._bit_value = bits
+ else:
+ # We were given a value representative of this *prefix
+ # unit*. We need to normalize it into the number of bytes
+ # it represents.
+ self._norm(value)
+
+ # We have the fundamental unit figured out. Set the 'pretty' unit
+ self._set_prefix_value()
+
+ def _set_prefix_value(self):
+ self.prefix_value = self._to_prefix_value(self._byte_value)
+
+ def _to_prefix_value(self, value):
+ """Return the number of bits/bytes as they would look like if we
+converted *to* this unit"""
+ return value / float(self._unit_value)
+
+ def _setup(self):
+ raise NotImplementedError("The base 'bitmath.Bitmath' class can not be used directly")
+
+ def _do_setup(self):
+ """Setup basic parameters for this class.
+
+`base` is the numeric base which when raised to `power` is equivalent
+to 1 unit of the corresponding prefix. I.e., base=2, power=10
+represents 2^10, which is the NIST Binary Prefix for 1 Kibibyte.
+
+Likewise, for the SI prefix classes `base` will be 10, and the `power`
+for the Kilobyte is 3.
+"""
+ (self._base, self._power, self._name_singular, self._name_plural) = self._setup()
+ self._unit_value = self._base ** self._power
+
+ def _norm(self, value):
+ """Normalize the input value into the fundamental unit for this prefix
+type"""
+ self._byte_value = value * self._unit_value
+ self._bit_value = self._byte_value * 8.0
+
+ @property
+ def base(self):
+ """Return the mathematical base of an instance"""
+ return self._base
+
+ @property
+ def binary(self):
+ """Returns the binary representation of an instance in binary 1s and
+0s. Note that for very large numbers this will mean a lot of 1s and
+0s. For example, GiB(100) would be represented as:
+
+0b1100100000000000000000000000000000000000
+
+That leading '0b' is normal. That's how Python represents binary."""
+ return bin(int(self.bits))
+
+ @property
+ def bin(self):
+ """Alias for instance.binary. Returns the binary representation of an
+instance in binary 1s and 0s."""
+ return self.binary
+
+ @property
+ def bits(self):
+ """Return the number of bits in an instance"""
+ return self._bit_value
+
+ @property
+ def bytes(self):
+ """Return the number of bytes in an instance"""
+ return self._byte_value
+
+ @property
+ def power(self):
+ """Return the mathematical power of an instance"""
+ return self._power
+
+ @property
+ def system(self):
+ """Return the system of units used to measure this instance"""
+ if self._base == 2:
+ return "NIST"
+ elif self._base == 10:
+ return "SI"
+ else:
+ # I don't expect to ever encounter this logic branch, but
+ # hey, it's better to have extra test coverage than
+ # insufficient test coverage.
+ raise ValueError("Instances mathematical base is an unsupported value: %s" % (
+ str(self._base)))
+
+ @property
+ def unit(self):
+ """Return the string that is this instances prefix unit name
+in agreement with this instance value (singular or plural). Following
+the convention that only 1 is singular. This method will always return
+the singular form when bitmath.format_plural is False (default value).
+
+For instance, when plural form is enabled, KiB(1).unit == 'KiB',
+Byte(0).unit == 'Bytes', Byte(1).unit == 'Byte', Byte(1.1).unit == 'Bytes'
+and Gb(2).unit == 'Gbs'"""
+ global format_plural
+
+ if self.prefix_value == 1:
+ # If it's a '1', return it singular, no matter what
+ return self._name_singular
+ elif format_plural:
+ # Pluralization requested
+ return self._name_plural
+ else:
+ # Pluralization NOT requested, and the value is not 1
+ return self._name_singular
+
+ @property
+ def unit_plural(self):
+ """Return the string that is this instances prefix unit name in the
+plural form.
+
+For instance, KiB(1).unit_plural == 'KiB', Byte(1024).unit_plural == 'Bytes',
+and Gb(1).unit_plural == 'Gb'"""
+ return self._name_plural
+
+ @property
+ def unit_singular(self):
+ """Return the string that is this instances prefix unit name in the
+singular form.
+
+For instance, KiB(1).unit_singular == 'KiB', Byte(1024).unit == 'B', and
+Gb(1).unit_singular == 'Gb'"""
+ return self._name_singular
+
+ @property
+ def value(self):
+ """Returns the "prefix" value of an instance"""
+ return self.prefix_value
+
+ @classmethod
+ def from_other(cls, item):
+ """Factory function to return instances of `item` converted in a new
+instance of `cls`. Because this is a class method, it may be called
+from any bitmath class object without the need to explicitly
+instantiate the class ahead of time.
+
+*Implicit Parameter:*
+- `cls` A bitmath class, implicitly set to the class of the class
+ object it is called on
+
+*User Supplied Parameter:*
+- `item` A bitmath class instance
+
+*Example:*
+>>> import bitmath
+>>> kib = bitmath.KiB.from_other(bitmath.MiB(1))
+>>> print kib
+KiB(1024.0)
+"""
+ if isinstance(item, Bitmath):
+ return cls(bits=item.bits)
+ else:
+ raise ValueError("The provided items must be a valid bitmath class: %s" %
+ str(item.__class__))
+
+ ######################################################################
+ # The following implement the Python datamodel customization methods
+ #
+ # Reference: http://docs.python.org/2.7/reference/datamodel.html#basic-customization
+
+ def __repr__(self):
+ """Representation of this object as you would expect to see in an
+interpreter"""
+ global _FORMAT_REPR
+ return self.format(_FORMAT_REPR)
+
+ def __str__(self):
+ """String representation of this object"""
+ global format_string
+ return self.format(format_string)
+
+ def format(self, fmt):
+ """Return a representation of this instance formatted with user
+supplied syntax"""
+ _fmt_params = {
+ 'base': self.base,
+ 'bin': self.bin,
+ 'binary': self.binary,
+ 'bits': self.bits,
+ 'bytes': self.bytes,
+ 'power': self.power,
+ 'system': self.system,
+ 'unit': self.unit,
+ 'unit_plural': self.unit_plural,
+ 'unit_singular': self.unit_singular,
+ 'value': self.value
+ }
+
+ return fmt.format(**_fmt_params)
+
+ ##################################################################
+ # Guess the best human-readable prefix unit for representation
+ ##################################################################
+
+ def best_prefix(self, system=None):
+ """Optional parameter, `system`, allows you to prefer NIST or SI in
+the results. By default, the current system is used (Bit/Byte default
+to NIST).
+
+Logic discussion/notes:
+
+Base-case, does it need converting?
+
+If the instance is less than one Byte, return the instance as a Bit
+instance.
+
+Else, begin by recording the unit system the instance is defined
+by. This determines which steps (NIST_STEPS/SI_STEPS) we iterate over.
+
+If the instance is not already a ``Byte`` instance, convert it to one.
+
+NIST units step up by powers of 1024, SI units step up by powers of
+1000.
+
+Take integer value of the log(base=STEP_POWER) of the instance's byte
+value. E.g.:
+
+ >>> int(math.log(Gb(100).bytes, 1000))
+ 3
+
+This will return a value >= 0. The following determines the 'best
+prefix unit' for representation:
+
+* result == 0, best represented as a Byte
+* result >= len(SYSTEM_STEPS), best represented as an Exbi/Exabyte
+* 0 < result < len(SYSTEM_STEPS), best represented as SYSTEM_PREFIXES[result-1]
+
+ """
+ if self < Byte(1):
+ return Bit.from_other(self)
+ else:
+ if not type(self) == Byte:
+ _inst = Byte.from_other(self)
+ else:
+ _inst = self
+
+ # Which table to consult? Was a preferred system provided?
+ if system is None:
+ # No preference. Use existing system
+ if self.system == 'NIST':
+ _STEPS = NIST_PREFIXES
+ _BASE = 1024
+ elif self.system == 'SI':
+ _STEPS = SI_PREFIXES
+ _BASE = 1000
+ # Anything else would have raised by now
+ else:
+ # Preferred system provided.
+ if system == NIST:
+ _STEPS = NIST_PREFIXES
+ _BASE = 1024
+ elif system == SI:
+ _STEPS = SI_PREFIXES
+ _BASE = 1000
+ else:
+ raise ValueError("Invalid value given for 'system' parameter."
+ " Must be one of NIST or SI")
+
+ # Index of the string of the best prefix in the STEPS list
+ _index = int(math.log(_inst.bytes, _BASE))
+
+ # Recall that the log() function returns >= 0. This doesn't
+ # map to the STEPS list 1:1. That is to say, 0 is handled with
+ # special care. So if the _index is 1, we actually want item 0
+ # in the list.
+
+ if _index == 0:
+ # Already a Byte() type, so return it.
+ return _inst
+ elif _index >= len(_STEPS):
+ # This is a really big number. Use the biggest prefix we've got
+ _best_prefix = _STEPS[-1]
+ elif 0 < _index < len(_STEPS):
+ # There is an appropriate prefix unit to represent this
+ _best_prefix = _STEPS[_index - 1]
+
+ _conversion_method = getattr(
+ self,
+ 'to_%sB' % _best_prefix)
+
+ return _conversion_method()
+
+ ##################################################################
+
+ def to_Bit(self):
+ return Bit(self._bit_value)
+
+ def to_Byte(self):
+ return Byte(self._byte_value / float(NIST_STEPS['Byte']))
+
+ # Properties
+ Bit = property(lambda s: s.to_Bit())
+ Byte = property(lambda s: s.to_Byte())
+
+ ##################################################################
+
+ def to_KiB(self):
+ return KiB(bits=self._bit_value)
+
+ def to_Kib(self):
+ return Kib(bits=self._bit_value)
+
+ def to_kB(self):
+ return kB(bits=self._bit_value)
+
+ def to_kb(self):
+ return kb(bits=self._bit_value)
+
+ # Properties
+ KiB = property(lambda s: s.to_KiB())
+ Kib = property(lambda s: s.to_Kib())
+ kB = property(lambda s: s.to_kB())
+ kb = property(lambda s: s.to_kb())
+
+ ##################################################################
+
+ def to_MiB(self):
+ return MiB(bits=self._bit_value)
+
+ def to_Mib(self):
+ return Mib(bits=self._bit_value)
+
+ def to_MB(self):
+ return MB(bits=self._bit_value)
+
+ def to_Mb(self):
+ return Mb(bits=self._bit_value)
+
+ # Properties
+ MiB = property(lambda s: s.to_MiB())
+ Mib = property(lambda s: s.to_Mib())
+ MB = property(lambda s: s.to_MB())
+ Mb = property(lambda s: s.to_Mb())
+
+ ##################################################################
+
+ def to_GiB(self):
+ return GiB(bits=self._bit_value)
+
+ def to_Gib(self):
+ return Gib(bits=self._bit_value)
+
+ def to_GB(self):
+ return GB(bits=self._bit_value)
+
+ def to_Gb(self):
+ return Gb(bits=self._bit_value)
+
+ # Properties
+ GiB = property(lambda s: s.to_GiB())
+ Gib = property(lambda s: s.to_Gib())
+ GB = property(lambda s: s.to_GB())
+ Gb = property(lambda s: s.to_Gb())
+
+ ##################################################################
+
+ def to_TiB(self):
+ return TiB(bits=self._bit_value)
+
+ def to_Tib(self):
+ return Tib(bits=self._bit_value)
+
+ def to_TB(self):
+ return TB(bits=self._bit_value)
+
+ def to_Tb(self):
+ return Tb(bits=self._bit_value)
+
+ # Properties
+ TiB = property(lambda s: s.to_TiB())
+ Tib = property(lambda s: s.to_Tib())
+ TB = property(lambda s: s.to_TB())
+ Tb = property(lambda s: s.to_Tb())
+
+ ##################################################################
+
+ def to_PiB(self):
+ return PiB(bits=self._bit_value)
+
+ def to_Pib(self):
+ return Pib(bits=self._bit_value)
+
+ def to_PB(self):
+ return PB(bits=self._bit_value)
+
+ def to_Pb(self):
+ return Pb(bits=self._bit_value)
+
+ # Properties
+ PiB = property(lambda s: s.to_PiB())
+ Pib = property(lambda s: s.to_Pib())
+ PB = property(lambda s: s.to_PB())
+ Pb = property(lambda s: s.to_Pb())
+
+ ##################################################################
+
+ def to_EiB(self):
+ return EiB(bits=self._bit_value)
+
+ def to_Eib(self):
+ return Eib(bits=self._bit_value)
+
+ def to_EB(self):
+ return EB(bits=self._bit_value)
+
+ def to_Eb(self):
+ return Eb(bits=self._bit_value)
+
+ # Properties
+ EiB = property(lambda s: s.to_EiB())
+ Eib = property(lambda s: s.to_Eib())
+ EB = property(lambda s: s.to_EB())
+ Eb = property(lambda s: s.to_Eb())
+
+ ##################################################################
+ # The SI units go beyond the NIST units. They also have the Zetta
+ # and Yotta prefixes.
+
+ def to_ZB(self):
+ return ZB(bits=self._bit_value)
+
+ def to_Zb(self):
+ return Zb(bits=self._bit_value)
+
+ # Properties
+ ZB = property(lambda s: s.to_ZB())
+ Zb = property(lambda s: s.to_Zb())
+
+ ##################################################################
+
+ def to_YB(self):
+ return YB(bits=self._bit_value)
+
+ def to_Yb(self):
+ return Yb(bits=self._bit_value)
+
+ # Properties
+ YB = property(lambda s: s.to_YB())
+ Yb = property(lambda s: s.to_Yb())
+
+ ##################################################################
+ # Rich comparison operations
+ ##################################################################
+
+ def __lt__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value < other
+ else:
+ return self._byte_value < other.bytes
+
+ def __le__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value <= other
+ else:
+ return self._byte_value <= other.bytes
+
+ def __eq__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value == other
+ else:
+ return self._byte_value == other.bytes
+
+ def __ne__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value != other
+ else:
+ return self._byte_value != other.bytes
+
+ def __gt__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value > other
+ else:
+ return self._byte_value > other.bytes
+
+ def __ge__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value >= other
+ else:
+ return self._byte_value >= other.bytes
+
+ ##################################################################
+ # Basic math operations
+ ##################################################################
+
+ # Reference: http://docs.python.org/2.7/reference/datamodel.html#emulating-numeric-types
+
+ """These methods are called to implement the binary arithmetic
+operations (+, -, *, //, %, divmod(), pow(), **, <<, >>, &, ^, |). For
+instance, to evaluate the expression x + y, where x is an instance of
+a class that has an __add__() method, x.__add__(y) is called. The
+__divmod__() method should be the equivalent to using __floordiv__()
+and __mod__(); it should not be related to __truediv__() (described
+below). Note that __pow__() should be defined to accept an optional
+third argument if the ternary version of the built-in pow() function
+is to be supported.object.__complex__(self)
+"""
+
+ def __add__(self, other):
+ """Supported operations with result types:
+
+- bm + bm = bm
+- bm + num = num
+- num + bm = num (see radd)
+"""
+ if isinstance(other, numbers.Number):
+ # bm + num
+ return other + self.value
+ else:
+ # bm + bm
+ total_bytes = self._byte_value + other.bytes
+ return (type(self))(bytes=total_bytes)
+
+ def __sub__(self, other):
+ """Subtraction: Supported operations with result types:
+
+- bm - bm = bm
+- bm - num = num
+- num - bm = num (see rsub)
+"""
+ if isinstance(other, numbers.Number):
+ # bm - num
+ return self.value - other
+ else:
+ # bm - bm
+ total_bytes = self._byte_value - other.bytes
+ return (type(self))(bytes=total_bytes)
+
+ def __mul__(self, other):
+ """Multiplication: Supported operations with result types:
+
+- bm1 * bm2 = bm1
+- bm * num = bm
+- num * bm = num (see rmul)
+"""
+ if isinstance(other, numbers.Number):
+ # bm * num
+ result = self._byte_value * other
+ return (type(self))(bytes=result)
+ else:
+ # bm1 * bm2
+ _other = other.value * other.base ** other.power
+ _self = self.prefix_value * self._base ** self._power
+ return (type(self))(bytes=_other * _self)
+
+ """The division operator (/) is implemented by these methods. The
+__truediv__() method is used when __future__.division is in effect,
+otherwise __div__() is used. If only one of these two methods is
+defined, the object will not support division in the alternate
+context; TypeError will be raised instead."""
+
+ def __div__(self, other):
+ """Division: Supported operations with result types:
+
+- bm1 / bm2 = num
+- bm / num = bm
+- num / bm = num (see rdiv)
+"""
+ if isinstance(other, numbers.Number):
+ # bm / num
+ result = self._byte_value / other
+ return (type(self))(bytes=result)
+ else:
+ # bm1 / bm2
+ return self._byte_value / float(other.bytes)
+
+ def __truediv__(self, other):
+ # num / bm
+ return self.__div__(other)
+
+ # def __floordiv__(self, other):
+ # return NotImplemented
+
+ # def __mod__(self, other):
+ # return NotImplemented
+
+ # def __divmod__(self, other):
+ # return NotImplemented
+
+ # def __pow__(self, other, modulo=None):
+ # return NotImplemented
+
+ ##################################################################
+
+ """These methods are called to implement the binary arithmetic
+operations (+, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |) with
+reflected (swapped) operands. These functions are only called if the
+left operand does not support the corresponding operation and the
+operands are of different types. [2] For instance, to evaluate the
+expression x - y, where y is an instance of a class that has an
+__rsub__() method, y.__rsub__(x) is called if x.__sub__(y) returns
+NotImplemented.
+
+These are the add/sub/mul/div methods for syntax where a number type
+is given for the LTYPE and a bitmath object is given for the
+RTYPE. E.g., 3 * MiB(3), or 10 / GB(42)
+"""
+
+ def __radd__(self, other):
+ # num + bm = num
+ return other + self.value
+
+ def __rsub__(self, other):
+ # num - bm = num
+ return other - self.value
+
+ def __rmul__(self, other):
+ # num * bm = bm
+ return self * other
+
+ def __rdiv__(self, other):
+ # num / bm = num
+ return other / float(self.value)
+
+ def __rtruediv__(self, other):
+ # num / bm = num
+ return other / float(self.value)
+
+ """Called to implement the built-in functions complex(), int(),
+long(), and float(). Should return a value of the appropriate type.
+
+If one of those methods does not support the operation with the
+supplied arguments, it should return NotImplemented.
+
+For bitmath purposes, these methods return the int/long/float
+equivalent of the this instances prefix Unix value. That is to say:
+
+ - int(KiB(3.336)) would return 3
+ - long(KiB(3.336)) would return 3L
+ - float(KiB(3.336)) would return 3.336
+"""
+
+ def __int__(self):
+ """Return this instances prefix unit as an integer"""
+ return int(self.prefix_value)
+
+ def __long__(self):
+ """Return this instances prefix unit as a long integer"""
+ return long(self.prefix_value) # pragma: PY3X no cover
+
+ def __float__(self):
+ """Return this instances prefix unit as a floating point number"""
+ return float(self.prefix_value)
+
+ ##################################################################
+ # Bitwise operations
+ ##################################################################
+
+ def __lshift__(self, other):
+ """Left shift, ex: 100 << 2
+
+A left shift by n bits is equivalent to multiplication by pow(2,
+n). A long integer is returned if the result exceeds the range of
+plain integers."""
+ shifted = int(self.bits) << other
+ return type(self)(bits=shifted)
+
+ def __rshift__(self, other):
+ """Right shift, ex: 100 >> 2
+
+A right shift by n bits is equivalent to division by pow(2, n)."""
+ shifted = int(self.bits) >> other
+ return type(self)(bits=shifted)
+
+ def __and__(self, other):
+ """"Bitwise and, ex: 100 & 2
+
+bitwise and". Each bit of the output is 1 if the corresponding bit
+of x AND of y is 1, otherwise it's 0."""
+ andd = int(self.bits) & other
+ return type(self)(bits=andd)
+
+ def __xor__(self, other):
+ """Bitwise xor, ex: 100 ^ 2
+
+Does a "bitwise exclusive or". Each bit of the output is the same
+as the corresponding bit in x if that bit in y is 0, and it's the
+complement of the bit in x if that bit in y is 1."""
+ xord = int(self.bits) ^ other
+ return type(self)(bits=xord)
+
+ def __or__(self, other):
+ """Bitwise or, ex: 100 | 2
+
+Does a "bitwise or". Each bit of the output is 0 if the corresponding
+bit of x AND of y is 0, otherwise it's 1."""
+ ord = int(self.bits) | other
+ return type(self)(bits=ord)
+
+ ##################################################################
+
+ def __neg__(self):
+ """The negative version of this instance"""
+ return (type(self))(-abs(self.prefix_value))
+
+ def __pos__(self):
+ return (type(self))(abs(self.prefix_value))
+
+ def __abs__(self):
+ return (type(self))(abs(self.prefix_value))
+
+ # def __invert__(self):
+ # """Called to implement the unary arithmetic operations (-, +, abs()
+ # and ~)."""
+ # return NotImplemented
+
+
+######################################################################
+# First, the bytes...
+
+class Byte(Bitmath):
+ """Byte based types fundamentally operate on self._bit_value"""
+ def _setup(self):
+ return (2, 0, 'Byte', 'Bytes')
+
+######################################################################
+# NIST Prefixes for Byte based types
+
+
+class KiB(Byte):
+ def _setup(self):
+ return (2, 10, 'KiB', 'KiBs')
+
+
+class MiB(Byte):
+ def _setup(self):
+ return (2, 20, 'MiB', 'MiBs')
+
+
+class GiB(Byte):
+ def _setup(self):
+ return (2, 30, 'GiB', 'GiBs')
+
+
+class TiB(Byte):
+ def _setup(self):
+ return (2, 40, 'TiB', 'TiBs')
+
+
+class PiB(Byte):
+ def _setup(self):
+ return (2, 50, 'PiB', 'PiBs')
+
+
+class EiB(Byte):
+ def _setup(self):
+ return (2, 60, 'EiB', 'EiBs')
+
+
+######################################################################
+# SI Prefixes for Byte based types
+class kB(Byte):
+ def _setup(self):
+ return (10, 3, 'kB', 'kBs')
+
+
+class MB(Byte):
+ def _setup(self):
+ return (10, 6, 'MB', 'MBs')
+
+
+class GB(Byte):
+ def _setup(self):
+ return (10, 9, 'GB', 'GBs')
+
+
+class TB(Byte):
+ def _setup(self):
+ return (10, 12, 'TB', 'TBs')
+
+
+class PB(Byte):
+ def _setup(self):
+ return (10, 15, 'PB', 'PBs')
+
+
+class EB(Byte):
+ def _setup(self):
+ return (10, 18, 'EB', 'EBs')
+
+
+class ZB(Byte):
+ def _setup(self):
+ return (10, 21, 'ZB', 'ZBs')
+
+
+class YB(Byte):
+ def _setup(self):
+ return (10, 24, 'YB', 'YBs')
+
+
+######################################################################
+# And now the bit types
+class Bit(Bitmath):
+ """Bit based types fundamentally operate on self._bit_value"""
+
+ def _set_prefix_value(self):
+ self.prefix_value = self._to_prefix_value(self._bit_value)
+
+ def _setup(self):
+ return (2, 0, 'Bit', 'Bits')
+
+ def _norm(self, value):
+ """Normalize the input value into the fundamental unit for this prefix
+type"""
+ self._bit_value = value * self._unit_value
+ self._byte_value = self._bit_value / 8.0
+
+
+######################################################################
+# NIST Prefixes for Bit based types
+class Kib(Bit):
+ def _setup(self):
+ return (2, 10, 'Kib', 'Kibs')
+
+
+class Mib(Bit):
+ def _setup(self):
+ return (2, 20, 'Mib', 'Mibs')
+
+
+class Gib(Bit):
+ def _setup(self):
+ return (2, 30, 'Gib', 'Gibs')
+
+
+class Tib(Bit):
+ def _setup(self):
+ return (2, 40, 'Tib', 'Tibs')
+
+
+class Pib(Bit):
+ def _setup(self):
+ return (2, 50, 'Pib', 'Pibs')
+
+
+class Eib(Bit):
+ def _setup(self):
+ return (2, 60, 'Eib', 'Eibs')
+
+
+######################################################################
+# SI Prefixes for Bit based types
+class kb(Bit):
+ def _setup(self):
+ return (10, 3, 'kb', 'kbs')
+
+
+class Mb(Bit):
+ def _setup(self):
+ return (10, 6, 'Mb', 'Mbs')
+
+
+class Gb(Bit):
+ def _setup(self):
+ return (10, 9, 'Gb', 'Gbs')
+
+
+class Tb(Bit):
+ def _setup(self):
+ return (10, 12, 'Tb', 'Tbs')
+
+
+class Pb(Bit):
+ def _setup(self):
+ return (10, 15, 'Pb', 'Pbs')
+
+
+class Eb(Bit):
+ def _setup(self):
+ return (10, 18, 'Eb', 'Ebs')
+
+
+class Zb(Bit):
+ def _setup(self):
+ return (10, 21, 'Zb', 'Zbs')
+
+
+class Yb(Bit):
+ def _setup(self):
+ return (10, 24, 'Yb', 'Ybs')
+
+
+######################################################################
+# Utility functions
+def getsize(path, bestprefix=True, system=NIST):
+ """Return a bitmath instance in the best human-readable representation
+of the file size at `path`. Optionally, provide a preferred unit
+system by setting `system` to either `bitmath.NIST` (default) or
+`bitmath.SI`.
+
+Optionally, set ``bestprefix`` to ``False`` to get ``bitmath.Byte``
+instances back.
+ """
+ _path = os.path.realpath(path)
+ size_bytes = os.path.getsize(_path)
+ if bestprefix:
+ return Byte(size_bytes).best_prefix(system=system)
+ else:
+ return Byte(size_bytes)
+
+
+def listdir(search_base, followlinks=False, filter='*',
+ relpath=False, bestprefix=False, system=NIST):
+ """This is a generator which recurses the directory tree
+`search_base`, yielding 2-tuples of:
+
+* The absolute/relative path to a discovered file
+* A bitmath instance representing the "apparent size" of the file.
+
+ - `search_base` - The directory to begin walking down.
+ - `followlinks` - Whether or not to follow symbolic links to directories
+ - `filter` - A glob (see :py:mod:`fnmatch`) to filter results with
+ (default: ``*``, everything)
+ - `relpath` - ``True`` to return the relative path from `pwd` or
+ ``False`` (default) to return the fully qualified path
+ - ``bestprefix`` - set to ``False`` to get ``bitmath.Byte``
+ instances back instead.
+ - `system` - Provide a preferred unit system by setting `system`
+ to either ``bitmath.NIST`` (default) or ``bitmath.SI``.
+
+.. note:: This function does NOT return tuples for directory entities.
+
+.. note:: Symlinks to **files** are followed automatically
+
+ """
+ for root, dirs, files in os.walk(search_base, followlinks=followlinks):
+ for name in fnmatch.filter(files, filter):
+ _path = os.path.join(root, name)
+ if relpath:
+ # RELATIVE path
+ _return_path = os.path.relpath(_path, '.')
+ else:
+ # REAL path
+ _return_path = os.path.realpath(_path)
+
+ if followlinks:
+ yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system))
+ else:
+ if os.path.isdir(_path) or os.path.islink(_path):
+ pass
+ else:
+ yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system))
+
+
+def parse_string(s):
+ """Parse a string with units and try to make a bitmath object out of
+it.
+
+String inputs may include whitespace characters between the value and
+the unit.
+ """
+ # Strings only please
+ if type(s) is not str:
+ raise ValueError("parse_string only accepts string inputs but a %s was given" %
+ type(s))
+
+ # get the index of the first alphabetic character
+ try:
+ index = list(map(str.isalpha, s)).index(True)
+ except ValueError:
+ # If there's no alphabetic characters we won't be able to .index(True)
+ raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s)
+
+ # split the string into the value and the unit
+ val, unit = s[:index], s[index:]
+
+ # see if the unit exists as a type in our namespace
+
+ if unit == "b":
+ unit_class = Bit
+ elif unit == "B":
+ unit_class = Byte
+ else:
+ if not (hasattr(sys.modules[__name__], unit)
+ and isinstance(getattr(sys.modules[__name__], unit), type)):
+ raise ValueError("The unit %s is not a valid bitmath unit" % unit)
+ unit_class = globals()[unit]
+
+ try:
+ val = float(val)
+ except ValueError:
+ raise
+ try:
+ return unit_class(val)
+ except: # pragma: no cover
+ raise ValueError("Can't parse string %s into a bitmath object" % s)
+
+
+######################################################################
+# Contxt Managers
+@contextlib.contextmanager
+def format(fmt_str=None, plural=False, bestprefix=False):
+ """Context manager for printing bitmath instances.
+
+``fmt_str`` - a formatting mini-language compat formatting string. See
+the @properties (above) for a list of available items.
+
+``plural`` - True enables printing instances with 's's if they're
+plural. False (default) prints them as singular (no trailing 's').
+
+``bestprefix`` - True enables printing instances in their best
+human-readable representation. False, the default, prints instances
+using their current prefix unit.
+ """
+ if 'bitmath' not in globals():
+ import bitmath
+
+ if plural:
+ orig_fmt_plural = bitmath.format_plural
+ bitmath.format_plural = True
+
+ if fmt_str:
+ orig_fmt_str = bitmath.format_string
+ bitmath.format_string = fmt_str
+
+ yield
+
+ if plural:
+ bitmath.format_plural = orig_fmt_plural
+
+ if fmt_str:
+ bitmath.format_string = orig_fmt_str
+
+
+def cli_script_main(cli_args):
+ """
+ A command line interface to basic bitmath operations.
+ """
+ choices = ALL_UNIT_TYPES
+
+ parser = argparse.ArgumentParser(
+ description='Converts from one type of size to another.')
+ parser.add_argument('--from-stdin', default=False, action='store_true',
+ help='Reads number from stdin rather than the cli')
+ parser.add_argument(
+ '-f', '--from', choices=choices, nargs=1,
+ type=str, dest='fromunit', default=['Byte'],
+ help='Input type you are converting from. Defaultes to Byte.')
+ parser.add_argument(
+ '-t', '--to', choices=choices, required=False, nargs=1, type=str,
+ help=('Input type you are converting to. '
+ 'Attempts to detect best result if omitted.'), dest='tounit')
+ parser.add_argument(
+ 'size', nargs='*', type=float,
+ help='The number to convert.')
+
+ args = parser.parse_args(cli_args)
+
+ # Not sure how to cover this with tests, or if the functionality
+ # will remain in this form long enough for it to make writing a
+ # test worth the effort.
+ if args.from_stdin: # pragma: no cover
+ args.size = [float(sys.stdin.readline()[:-1])]
+
+ results = []
+
+ for size in args.size:
+ instance = getattr(__import__(
+ 'bitmath', fromlist=['True']), args.fromunit[0])(size)
+
+ # If we have a unit provided then use it
+ if args.tounit:
+ result = getattr(instance, args.tounit[0])
+ # Otherwise use the best_prefix call
+ else:
+ result = instance.best_prefix()
+
+ results.append(result)
+
+ return results
+
+
+def cli_script(): # pragma: no cover
+ # Wrapper around cli_script_main so we can unittest the command
+ # line functionality
+ for result in cli_script_main(sys.argv[1:]):
+ print(result)
+
+if __name__ == '__main__':
+ cli_script()
diff --git a/lib/Python/Lib/bitmath/integrations.py b/lib/Python/Lib/bitmath/integrations.py
new file mode 100644
index 000000000..e598ea1e9
--- /dev/null
+++ b/lib/Python/Lib/bitmath/integrations.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# The MIT License (MIT)
+#
+# Copyright © 2014 Tim Bielawa <timbielawa@gmail.com>
+# See GitHub Contributors Graph for more information
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sub-license, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import bitmath
+import argparse
+import progressbar.widgets
+
+######################################################################
+# Integrations with 3rd party modules
+def BitmathType(bmstring):
+ """An 'argument type' for integrations with the argparse module.
+
+For more information, see
+https://docs.python.org/2/library/argparse.html#type Of particular
+interest to us is this bit:
+
+ ``type=`` can take any callable that takes a single string
+ argument and returns the converted value
+
+I.e., ``type`` can be a function (such as this function) or a class
+which implements the ``__call__`` method.
+
+Example usage of the bitmath.BitmathType argparser type:
+
+ >>> import bitmath
+ >>> import argparse
+ >>> parser = argparse.ArgumentParser()
+ >>> parser.add_argument("--file-size", type=bitmath.BitmathType)
+ >>> parser.parse_args("--file-size 1337MiB".split())
+ Namespace(file_size=MiB(1337.0))
+
+Invalid usage includes any input that the bitmath.parse_string
+function already rejects. Additionally, **UNQUOTED** arguments with
+spaces in them are rejected (shlex.split used in the following
+examples to conserve single quotes in the parse_args call):
+
+ >>> parser = argparse.ArgumentParser()
+ >>> parser.add_argument("--file-size", type=bitmath.BitmathType)
+ >>> import shlex
+
+ >>> # The following is ACCEPTABLE USAGE:
+ ...
+ >>> parser.parse_args(shlex.split("--file-size '1337 MiB'"))
+ Namespace(file_size=MiB(1337.0))
+
+ >>> # The following is INCORRECT USAGE because the string "1337 MiB" is not quoted!
+ ...
+ >>> parser.parse_args(shlex.split("--file-size 1337 MiB"))
+ error: argument --file-size: 1337 can not be parsed into a valid bitmath object
+"""
+ try:
+ argvalue = bitmath.parse_string(bmstring)
+ except ValueError:
+ raise argparse.ArgumentTypeError("'%s' can not be parsed into a valid bitmath object" %
+ bmstring)
+ else:
+ return argvalue
+
+######################################################################
+# Speed widget for integration with the Progress bar module
+class BitmathFileTransferSpeed(progressbar.widgets.Widget):
+ """Widget for showing the transfer speed (useful for file transfers)."""
+ __slots__ = ('system', 'format')
+
+ def __init__(self, system=bitmath.NIST, format="{value:.2f} {unit}/s"):
+ self.system = system
+ self.format = format
+
+ def update(self, pbar):
+ """Updates the widget with the current NIST/SI speed.
+
+Basically, this calculates the average rate of update and figures out
+how to make a "pretty" prefix unit"""
+
+ if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6:
+ scaled = bitmath.Byte()
+ else:
+ speed = pbar.currval / pbar.seconds_elapsed
+ scaled = bitmath.Byte(speed).best_prefix(system=self.system)
+
+ return scaled.format(self.format)
diff --git a/module/lib/bottle.py b/lib/Python/Lib/bottle.py
index 2c243278e..2c243278e 100644
--- a/module/lib/bottle.py
+++ b/lib/Python/Lib/bottle.py
diff --git a/lib/Python/Lib/colorama/__init__.py b/lib/Python/Lib/colorama/__init__.py
new file mode 100644
index 000000000..4af0c1e11
--- /dev/null
+++ b/lib/Python/Lib/colorama/__init__.py
@@ -0,0 +1,7 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from .initialise import init, deinit, reinit
+from .ansi import Fore, Back, Style, Cursor
+from .ansitowin32 import AnsiToWin32
+
+__version__ = '0.3.3'
+
diff --git a/lib/Python/Lib/colorama/ansi.py b/lib/Python/Lib/colorama/ansi.py
new file mode 100644
index 000000000..1cc722500
--- /dev/null
+++ b/lib/Python/Lib/colorama/ansi.py
@@ -0,0 +1,99 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+'''
+This module generates ANSI character codes to printing colors to terminals.
+See: http://en.wikipedia.org/wiki/ANSI_escape_code
+'''
+
+CSI = '\033['
+OSC = '\033]'
+BEL = '\007'
+
+
+def code_to_chars(code):
+ return CSI + str(code) + 'm'
+
+
+class AnsiCodes(object):
+ def __init__(self, codes):
+ for name in dir(codes):
+ if not name.startswith('_'):
+ value = getattr(codes, name)
+ setattr(self, name, code_to_chars(value))
+
+
+class AnsiCursor(object):
+ def UP(self, n=1):
+ return CSI + str(n) + "A"
+ def DOWN(self, n=1):
+ return CSI + str(n) + "B"
+ def FORWARD(self, n=1):
+ return CSI + str(n) + "C"
+ def BACK(self, n=1):
+ return CSI + str(n) + "D"
+ def POS(self, x=1, y=1):
+ return CSI + str(y) + ";" + str(x) + "H"
+
+def set_title(title):
+ return OSC + "2;" + title + BEL
+
+def clear_screen(mode=2):
+ return CSI + str(mode) + "J"
+
+def clear_line(mode=2):
+ return CSI + str(mode) + "K"
+
+
+class AnsiFore:
+ BLACK = 30
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+ WHITE = 37
+ RESET = 39
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 90
+ LIGHTRED_EX = 91
+ LIGHTGREEN_EX = 92
+ LIGHTYELLOW_EX = 93
+ LIGHTBLUE_EX = 94
+ LIGHTMAGENTA_EX = 95
+ LIGHTCYAN_EX = 96
+ LIGHTWHITE_EX = 97
+
+
+class AnsiBack:
+ BLACK = 40
+ RED = 41
+ GREEN = 42
+ YELLOW = 43
+ BLUE = 44
+ MAGENTA = 45
+ CYAN = 46
+ WHITE = 47
+ RESET = 49
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 100
+ LIGHTRED_EX = 101
+ LIGHTGREEN_EX = 102
+ LIGHTYELLOW_EX = 103
+ LIGHTBLUE_EX = 104
+ LIGHTMAGENTA_EX = 105
+ LIGHTCYAN_EX = 106
+ LIGHTWHITE_EX = 107
+
+
+class AnsiStyle:
+ BRIGHT = 1
+ DIM = 2
+ NORMAL = 22
+ RESET_ALL = 0
+
+Fore = AnsiCodes( AnsiFore )
+Back = AnsiCodes( AnsiBack )
+Style = AnsiCodes( AnsiStyle )
+Cursor = AnsiCursor()
diff --git a/lib/Python/Lib/colorama/ansitowin32.py b/lib/Python/Lib/colorama/ansitowin32.py
new file mode 100644
index 000000000..62e770c86
--- /dev/null
+++ b/lib/Python/Lib/colorama/ansitowin32.py
@@ -0,0 +1,228 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import re
+import sys
+import os
+
+from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
+from .winterm import WinTerm, WinColor, WinStyle
+from .win32 import windll
+
+
+winterm = None
+if windll is not None:
+ winterm = WinTerm()
+
+
+def is_a_tty(stream):
+ return hasattr(stream, 'isatty') and stream.isatty()
+
+
+class StreamWrapper(object):
+ '''
+ Wraps a stream (such as stdout), acting as a transparent proxy for all
+ attribute access apart from method 'write()', which is delegated to our
+ Converter instance.
+ '''
+ def __init__(self, wrapped, converter):
+ # double-underscore everything to prevent clashes with names of
+ # attributes on the wrapped stream object.
+ self.__wrapped = wrapped
+ self.__convertor = converter
+
+ def __getattr__(self, name):
+ return getattr(self.__wrapped, name)
+
+ def write(self, text):
+ self.__convertor.write(text)
+
+
+class AnsiToWin32(object):
+ '''
+ Implements a 'write()' method which, on Windows, will strip ANSI character
+ sequences from the text, and if outputting to a tty, will convert them into
+ win32 function calls.
+ '''
+ ANSI_CSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') # Control Sequence Introducer
+ ANSI_OSC_RE = re.compile('\033\]((?:.|;)*?)(\x07)') # Operating System Command
+
+ def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
+ # The wrapped stream (normally sys.stdout or sys.stderr)
+ self.wrapped = wrapped
+
+ # should we reset colors to defaults after every .write()
+ self.autoreset = autoreset
+
+ # create the proxy wrapping our output stream
+ self.stream = StreamWrapper(wrapped, self)
+
+ on_windows = os.name == 'nt'
+ on_emulated_windows = on_windows and 'TERM' in os.environ
+
+ # should we strip ANSI sequences from our output?
+ if strip is None:
+ strip = on_windows and not on_emulated_windows
+ self.strip = strip
+
+ # should we should convert ANSI sequences into win32 calls?
+ if convert is None:
+ convert = on_windows and not wrapped.closed and not on_emulated_windows and is_a_tty(wrapped)
+ self.convert = convert
+
+ # dict of ansi codes to win32 functions and parameters
+ self.win32_calls = self.get_win32_calls()
+
+ # are we wrapping stderr?
+ self.on_stderr = self.wrapped is sys.stderr
+
+ def should_wrap(self):
+ '''
+ True if this class is actually needed. If false, then the output
+ stream will not be affected, nor will win32 calls be issued, so
+ wrapping stdout is not actually required. This will generally be
+ False on non-Windows platforms, unless optional functionality like
+ autoreset has been requested using kwargs to init()
+ '''
+ return self.convert or self.strip or self.autoreset
+
+ def get_win32_calls(self):
+ if self.convert and winterm:
+ return {
+ AnsiStyle.RESET_ALL: (winterm.reset_all, ),
+ AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
+ AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
+ AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
+ AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
+ AnsiFore.RED: (winterm.fore, WinColor.RED),
+ AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
+ AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
+ AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
+ AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
+ AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
+ AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
+ AnsiFore.RESET: (winterm.fore, ),
+ AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
+ AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
+ AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
+ AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
+ AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
+ AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
+ AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
+ AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
+ AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
+ AnsiBack.RED: (winterm.back, WinColor.RED),
+ AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
+ AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
+ AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
+ AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
+ AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
+ AnsiBack.WHITE: (winterm.back, WinColor.GREY),
+ AnsiBack.RESET: (winterm.back, ),
+ AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
+ AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
+ AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
+ AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
+ AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
+ AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
+ AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
+ AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
+ }
+ return dict()
+
+ def write(self, text):
+ if self.strip or self.convert:
+ self.write_and_convert(text)
+ else:
+ self.wrapped.write(text)
+ self.wrapped.flush()
+ if self.autoreset:
+ self.reset_all()
+
+
+ def reset_all(self):
+ if self.convert:
+ self.call_win32('m', (0,))
+ elif not self.wrapped.closed and is_a_tty(self.wrapped):
+ self.wrapped.write(Style.RESET_ALL)
+
+
+ def write_and_convert(self, text):
+ '''
+ Write the given text to our wrapped stream, stripping any ANSI
+ sequences from the text, and optionally converting them into win32
+ calls.
+ '''
+ cursor = 0
+ text = self.convert_osc(text)
+ for match in self.ANSI_CSI_RE.finditer(text):
+ start, end = match.span()
+ self.write_plain_text(text, cursor, start)
+ self.convert_ansi(*match.groups())
+ cursor = end
+ self.write_plain_text(text, cursor, len(text))
+
+
+ def write_plain_text(self, text, start, end):
+ if start < end:
+ self.wrapped.write(text[start:end])
+ self.wrapped.flush()
+
+
+ def convert_ansi(self, paramstring, command):
+ if self.convert:
+ params = self.extract_params(command, paramstring)
+ self.call_win32(command, params)
+
+
+ def extract_params(self, command, paramstring):
+ if command in 'Hf':
+ params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
+ while len(params) < 2:
+ # defaults:
+ params = params + (1,)
+ else:
+ params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
+ if len(params) == 0:
+ # defaults:
+ if command in 'JKm':
+ params = (0,)
+ elif command in 'ABCD':
+ params = (1,)
+
+ return params
+
+
+ def call_win32(self, command, params):
+ if command == 'm':
+ for param in params:
+ if param in self.win32_calls:
+ func_args = self.win32_calls[param]
+ func = func_args[0]
+ args = func_args[1:]
+ kwargs = dict(on_stderr=self.on_stderr)
+ func(*args, **kwargs)
+ elif command in 'J':
+ winterm.erase_screen(params[0], on_stderr=self.on_stderr)
+ elif command in 'K':
+ winterm.erase_line(params[0], on_stderr=self.on_stderr)
+ elif command in 'Hf': # cursor position - absolute
+ winterm.set_cursor_position(params, on_stderr=self.on_stderr)
+ elif command in 'ABCD': # cursor position - relative
+ n = params[0]
+ # A - up, B - down, C - forward, D - back
+ x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
+ winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
+
+
+ def convert_osc(self, text):
+ for match in self.ANSI_OSC_RE.finditer(text):
+ start, end = match.span()
+ text = text[:start] + text[end:]
+ paramstring, command = match.groups()
+ if command in '\x07': # \x07 = BEL
+ params = paramstring.split(";")
+ # 0 - change title and icon (we will only change title)
+ # 1 - change icon (we don't support this)
+ # 2 - change title
+ if params[0] in '02':
+ winterm.set_title(params[1])
+ return text
diff --git a/lib/Python/Lib/colorama/initialise.py b/lib/Python/Lib/colorama/initialise.py
new file mode 100644
index 000000000..7e27f84f8
--- /dev/null
+++ b/lib/Python/Lib/colorama/initialise.py
@@ -0,0 +1,66 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import atexit
+import sys
+
+from .ansitowin32 import AnsiToWin32
+
+
+orig_stdout = sys.stdout
+orig_stderr = sys.stderr
+
+wrapped_stdout = sys.stdout
+wrapped_stderr = sys.stderr
+
+atexit_done = False
+
+
+def reset_all():
+ AnsiToWin32(orig_stdout).reset_all()
+
+
+def init(autoreset=False, convert=None, strip=None, wrap=True):
+
+ if not wrap and any([autoreset, convert, strip]):
+ raise ValueError('wrap=False conflicts with any other arg=True')
+
+ global wrapped_stdout, wrapped_stderr
+ if sys.stdout is None:
+ wrapped_stdout = None
+ else:
+ sys.stdout = wrapped_stdout = \
+ wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
+ if sys.stderr is None:
+ wrapped_stderr = None
+ else:
+ sys.stderr = wrapped_stderr = \
+ wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
+
+ global atexit_done
+ if not atexit_done:
+ atexit.register(reset_all)
+ atexit_done = True
+
+
+def deinit():
+ if orig_stdout is not None:
+ sys.stdout = orig_stdout
+ if orig_stderr is not None:
+ sys.stderr = orig_stderr
+
+
+def reinit():
+ if wrapped_stdout is not None:
+ sys.stdout = wrapped_stdout
+ if wrapped_stderr is not None:
+ sys.stderr = wrapped_stderr
+
+
+def wrap_stream(stream, convert, strip, autoreset, wrap):
+ if wrap:
+ wrapper = AnsiToWin32(stream,
+ convert=convert, strip=strip, autoreset=autoreset)
+ if wrapper.should_wrap():
+ stream = wrapper.stream
+ return stream
+
+
diff --git a/lib/Python/Lib/colorama/win32.py b/lib/Python/Lib/colorama/win32.py
new file mode 100644
index 000000000..c604f3721
--- /dev/null
+++ b/lib/Python/Lib/colorama/win32.py
@@ -0,0 +1,146 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+
+# from winbase.h
+STDOUT = -11
+STDERR = -12
+
+try:
+ import ctypes
+ from ctypes import LibraryLoader
+ windll = LibraryLoader(ctypes.WinDLL)
+ from ctypes import wintypes
+except (AttributeError, ImportError):
+ windll = None
+ SetConsoleTextAttribute = lambda *_: None
+else:
+ from ctypes import byref, Structure, c_char, POINTER
+
+ COORD = wintypes._COORD
+
+ class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ """struct in wincon.h."""
+ _fields_ = [
+ ("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", COORD),
+ ]
+ def __str__(self):
+ return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
+ self.dwSize.Y, self.dwSize.X
+ , self.dwCursorPosition.Y, self.dwCursorPosition.X
+ , self.wAttributes
+ , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
+ , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
+ )
+
+ _GetStdHandle = windll.kernel32.GetStdHandle
+ _GetStdHandle.argtypes = [
+ wintypes.DWORD,
+ ]
+ _GetStdHandle.restype = wintypes.HANDLE
+
+ _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+ _GetConsoleScreenBufferInfo.argtypes = [
+ wintypes.HANDLE,
+ POINTER(CONSOLE_SCREEN_BUFFER_INFO),
+ ]
+ _GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+
+ _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+ _SetConsoleTextAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ ]
+ _SetConsoleTextAttribute.restype = wintypes.BOOL
+
+ _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
+ _SetConsoleCursorPosition.argtypes = [
+ wintypes.HANDLE,
+ COORD,
+ ]
+ _SetConsoleCursorPosition.restype = wintypes.BOOL
+
+ _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
+ _FillConsoleOutputCharacterA.argtypes = [
+ wintypes.HANDLE,
+ c_char,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputCharacterA.restype = wintypes.BOOL
+
+ _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+ _FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+ _SetConsoleTitleW = windll.kernel32.SetConsoleTitleA
+ _SetConsoleTitleW.argtypes = [
+ wintypes.LPCSTR
+ ]
+ _SetConsoleTitleW.restype = wintypes.BOOL
+
+ handles = {
+ STDOUT: _GetStdHandle(STDOUT),
+ STDERR: _GetStdHandle(STDERR),
+ }
+
+ def GetConsoleScreenBufferInfo(stream_id=STDOUT):
+ handle = handles[stream_id]
+ csbi = CONSOLE_SCREEN_BUFFER_INFO()
+ success = _GetConsoleScreenBufferInfo(
+ handle, byref(csbi))
+ return csbi
+
+ def SetConsoleTextAttribute(stream_id, attrs):
+ handle = handles[stream_id]
+ return _SetConsoleTextAttribute(handle, attrs)
+
+ def SetConsoleCursorPosition(stream_id, position, adjust=True):
+ position = COORD(*position)
+ # If the position is out of range, do nothing.
+ if position.Y <= 0 or position.X <= 0:
+ return
+ # Adjust for Windows' SetConsoleCursorPosition:
+ # 1. being 0-based, while ANSI is 1-based.
+ # 2. expecting (x,y), while ANSI uses (y,x).
+ adjusted_position = COORD(position.Y - 1, position.X - 1)
+ if adjust:
+ # Adjust for viewport's scroll position
+ sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
+ adjusted_position.Y += sr.Top
+ adjusted_position.X += sr.Left
+ # Resume normal processing
+ handle = handles[stream_id]
+ return _SetConsoleCursorPosition(handle, adjusted_position)
+
+ def FillConsoleOutputCharacter(stream_id, char, length, start):
+ handle = handles[stream_id]
+ char = c_char(char.encode())
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ success = _FillConsoleOutputCharacterA(
+ handle, char, length, start, byref(num_written))
+ return num_written.value
+
+ def FillConsoleOutputAttribute(stream_id, attr, length, start):
+ ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
+ handle = handles[stream_id]
+ attribute = wintypes.WORD(attr)
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ return _FillConsoleOutputAttribute(
+ handle, attribute, length, start, byref(num_written))
+
+ def SetConsoleTitle(title):
+ return _SetConsoleTitleW(title)
diff --git a/lib/Python/Lib/colorama/winterm.py b/lib/Python/Lib/colorama/winterm.py
new file mode 100644
index 000000000..fcc774ffa
--- /dev/null
+++ b/lib/Python/Lib/colorama/winterm.py
@@ -0,0 +1,151 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from . import win32
+
+
+# from wincon.h
+class WinColor(object):
+ BLACK = 0
+ BLUE = 1
+ GREEN = 2
+ CYAN = 3
+ RED = 4
+ MAGENTA = 5
+ YELLOW = 6
+ GREY = 7
+
+# from wincon.h
+class WinStyle(object):
+ NORMAL = 0x00 # dim text, dim background
+ BRIGHT = 0x08 # bright text, dim background
+ BRIGHT_BACKGROUND = 0x80 # dim text, bright background
+
+class WinTerm(object):
+
+ def __init__(self):
+ self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
+ self.set_attrs(self._default)
+ self._default_fore = self._fore
+ self._default_back = self._back
+ self._default_style = self._style
+
+ def get_attrs(self):
+ return self._fore + self._back * 16 + self._style
+
+ def set_attrs(self, value):
+ self._fore = value & 7
+ self._back = (value >> 4) & 7
+ self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
+
+ def reset_all(self, on_stderr=None):
+ self.set_attrs(self._default)
+ self.set_console(attrs=self._default)
+
+ def fore(self, fore=None, light=False, on_stderr=False):
+ if fore is None:
+ fore = self._default_fore
+ self._fore = fore
+ if light:
+ self._style |= WinStyle.BRIGHT
+ self.set_console(on_stderr=on_stderr)
+
+ def back(self, back=None, light=False, on_stderr=False):
+ if back is None:
+ back = self._default_back
+ self._back = back
+ if light:
+ self._style |= WinStyle.BRIGHT_BACKGROUND
+ self.set_console(on_stderr=on_stderr)
+
+ def style(self, style=None, on_stderr=False):
+ if style is None:
+ style = self._default_style
+ self._style = style
+ self.set_console(on_stderr=on_stderr)
+
+ def set_console(self, attrs=None, on_stderr=False):
+ if attrs is None:
+ attrs = self.get_attrs()
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleTextAttribute(handle, attrs)
+
+ def get_position(self, handle):
+ position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
+ # Because Windows coordinates are 0-based,
+ # and win32.SetConsoleCursorPosition expects 1-based.
+ position.X += 1
+ position.Y += 1
+ return position
+
+ def set_cursor_position(self, position=None, on_stderr=False):
+ if position is None:
+ #I'm not currently tracking the position, so there is no default.
+ #position = self.get_position()
+ return
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleCursorPosition(handle, position)
+
+ def cursor_adjust(self, x, y, on_stderr=False):
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ position = self.get_position(handle)
+ adjusted_position = (position.Y + y, position.X + x)
+ win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
+
+ def erase_screen(self, mode=0, on_stderr=False):
+ # 0 should clear from the cursor to the end of the screen.
+ # 1 should clear from the cursor to the beginning of the screen.
+ # 2 should clear the entire screen, and move cursor to (1,1)
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ # get the number of character cells in the current buffer
+ cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
+ # get number of character cells before current cursor position
+ cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = cells_in_screen - cells_before_cursor
+ if mode == 1:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_before_cursor
+ elif mode == 2:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_in_screen
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+ if mode == 2:
+ # put the cursor where needed
+ win32.SetConsoleCursorPosition(handle, (1, 1))
+
+ def erase_line(self, mode=0, on_stderr=False):
+ # 0 should clear from the cursor to the end of the line.
+ # 1 should clear from the cursor to the beginning of the line.
+ # 2 should clear the entire line.
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
+ if mode == 1:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwCursorPosition.X
+ elif mode == 2:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwSize.X
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+
+ def set_title(self, title):
+ win32.SetConsoleTitle(title)
diff --git a/lib/Python/Lib/colorlog/__init__.py b/lib/Python/Lib/colorlog/__init__.py
new file mode 100644
index 000000000..1d57f586d
--- /dev/null
+++ b/lib/Python/Lib/colorlog/__init__.py
@@ -0,0 +1,14 @@
+"""A logging formatter for colored output."""
+
+from __future__ import absolute_import
+
+from colorlog.colorlog import (
+ ColoredFormatter, escape_codes, default_log_colors)
+
+from colorlog.logging import (
+ basicConfig, root, getLogger, log,
+ debug, info, warning, error, exception, critical)
+
+__all__ = ('ColoredFormatter', 'default_log_colors', 'escape_codes',
+ 'basicConfig', 'root', 'getLogger', 'debug', 'info', 'warning',
+ 'error', 'exception', 'critical', 'log', 'exception')
diff --git a/lib/Python/Lib/colorlog/colorlog.py b/lib/Python/Lib/colorlog/colorlog.py
new file mode 100644
index 000000000..49860dd50
--- /dev/null
+++ b/lib/Python/Lib/colorlog/colorlog.py
@@ -0,0 +1,137 @@
+"""The ColoredFormatter class."""
+
+from __future__ import absolute_import
+
+import logging
+import collections
+import sys
+
+from colorlog.escape_codes import escape_codes, parse_colors
+
+__all__ = ('escape_codes', 'default_log_colors', 'ColoredFormatter')
+
+# The default colors to use for the debug levels
+default_log_colors = {
+ 'DEBUG': 'white',
+ 'INFO': 'green',
+ 'WARNING': 'yellow',
+ 'ERROR': 'red',
+ 'CRITICAL': 'bold_red',
+}
+
+# The default format to use for each style
+default_formats = {
+ '%': '%(log_color)s%(levelname)s:%(name)s:%(message)s',
+ '{': '{log_color}{levelname}:{name}:{message}',
+ '$': '${log_color}${levelname}:${name}:${message}'
+}
+
+
+class ColoredRecord(object):
+ """
+ Wraps a LogRecord and attempts to parse missing keys as escape codes.
+
+ When the record is formatted, the logging library uses ``record.__dict__``
+ directly - so this class replaced the dict with a ``defaultdict`` that
+ checks if a missing key is an escape code.
+ """
+
+ class __dict(collections.defaultdict):
+ def __missing__(self, name):
+ try:
+ return parse_colors(name)
+ except Exception:
+ raise KeyError("{} is not a valid record attribute "
+ "or color sequence".format(name))
+
+ def __init__(self, record):
+ # Replace the internal dict with one that can handle missing keys
+ self.__dict__ = self.__dict()
+ self.__dict__.update(record.__dict__)
+
+ # Keep a refrence to the original refrence so ``__getattr__`` can
+ # access functions that are not in ``__dict__``
+ self.__record = record
+
+ def __getattr__(self, name):
+ return getattr(self.__record, name)
+
+
+class ColoredFormatter(logging.Formatter):
+ """
+ A formatter that allows colors to be placed in the format string.
+
+ Intended to help in creating more readable logging output.
+ """
+
+ def __init__(self, fmt=None, datefmt=None,
+ log_colors=None, reset=True, style='%',
+ secondary_log_colors=None):
+ """
+ Set the format and colors the ColoredFormatter will use.
+
+ The ``fmt``, ``datefmt`` and ``style`` args are passed on to the
+ ``logging.Formatter`` constructor.
+
+ The ``secondary_log_colors`` argument can be used to create additional
+ ``log_color`` attributes. Each key in the dictionary will set
+ ``log_color_{key}``, using the value to select from a different
+ ``log_colors`` set.
+
+ :Parameters:
+ - fmt (str): The format string to use
+ - datefmt (str): A format string for the date
+ - log_colors (dict):
+ A mapping of log level names to color names
+ - reset (bool):
+ Implictly append a color reset to all records unless False
+ - style ('%' or '{' or '$'):
+ The format style to use. (*No meaning prior to Python 3.2.*)
+ - secondary_log_colors (dict):
+ Map secondary ``log_color`` attributes. (*New in version 2.6.*)
+ """
+ if fmt is None:
+ if sys.version_info > (3, 2):
+ fmt = default_formats[style]
+ else:
+ fmt = default_formats['%']
+
+ if sys.version_info > (3, 2):
+ super(ColoredFormatter, self).__init__(fmt, datefmt, style)
+ elif sys.version_info > (2, 7):
+ super(ColoredFormatter, self).__init__(fmt, datefmt)
+ else:
+ logging.Formatter.__init__(self, fmt, datefmt)
+
+ self.log_colors = (
+ log_colors if log_colors is not None else default_log_colors)
+ self.secondary_log_colors = secondary_log_colors
+ self.reset = reset
+
+ def color(self, log_colors, name):
+ """Return escape codes from a ``log_colors`` dict."""
+ return parse_colors(log_colors.get(name, ""))
+
+ def format(self, record):
+ """Format a message from a record object."""
+ record = ColoredRecord(record)
+ record.log_color = self.color(self.log_colors, record.levelname)
+
+ # Set secondary log colors
+ if self.secondary_log_colors:
+ for name, log_colors in self.secondary_log_colors.items():
+ color = self.color(log_colors, record.levelname)
+ setattr(record, name + '_log_color', color)
+
+ # Format the message
+ if sys.version_info > (2, 7):
+ message = super(ColoredFormatter, self).format(record)
+ else:
+ message = logging.Formatter.format(self, record)
+
+ # Add a reset code to the end of the message
+ # (if it wasn't explicitly added in format str)
+ if self.reset and not message.endswith(escape_codes['reset']):
+ message += escape_codes['reset']
+
+ return message
diff --git a/lib/Python/Lib/colorlog/escape_codes.py b/lib/Python/Lib/colorlog/escape_codes.py
new file mode 100644
index 000000000..848eb6489
--- /dev/null
+++ b/lib/Python/Lib/colorlog/escape_codes.py
@@ -0,0 +1,57 @@
+"""
+Generates a dictionary of ANSI escape codes.
+
+http://en.wikipedia.org/wiki/ANSI_escape_code
+
+Uses colorama as an optional dependancy to support color on Windows
+"""
+
+try:
+ import colorama
+except ImportError:
+ pass
+else:
+ colorama.init()
+
+__all__ = ('escape_codes', 'parse_colors')
+
+# Returns escape codes from format codes
+esc = lambda *x: '\033[' + ';'.join(x) + 'm'
+
+# The initial list of escape codes
+escape_codes = {
+ 'reset': esc('0'),
+ 'bold': esc('01'),
+}
+
+# The color names
+COLORS = [
+ 'black',
+ 'red',
+ 'green',
+ 'yellow',
+ 'blue',
+ 'purple',
+ 'cyan',
+ 'white'
+]
+
+PREFIXES = [
+ # Foreground without prefix
+ ('3', ''), ('01;3', 'bold_'),
+
+ # Foreground with fg_ prefix
+ ('3', 'fg_'), ('01;3', 'fg_bold_'),
+
+ # Background with bg_ prefix - bold/light works differently
+ ('4', 'bg_'), ('10', 'bg_bold_'),
+]
+
+for prefix, prefix_name in PREFIXES:
+ for code, name in enumerate(COLORS):
+ escape_codes[prefix_name + name] = esc(prefix + str(code))
+
+
+def parse_colors(sequence):
+ """Return escape codes from a color sequence."""
+ return ''.join(escape_codes[n] for n in sequence.split(',') if n)
diff --git a/lib/Python/Lib/colorlog/logging.py b/lib/Python/Lib/colorlog/logging.py
new file mode 100644
index 000000000..13f0c4ffb
--- /dev/null
+++ b/lib/Python/Lib/colorlog/logging.py
@@ -0,0 +1,44 @@
+"""Wrappers around the logging module."""
+
+from __future__ import absolute_import
+
+import functools
+import logging
+
+from colorlog.colorlog import ColoredFormatter
+
+BASIC_FORMAT = "%(log_color)s%(levelname)s%(reset)s:%(name)s:%(message)s"
+
+
+def basicConfig(**kwargs):
+ """Call ``logging.basicConfig`` and override the formatter it creates."""
+ logging.basicConfig(**kwargs)
+ logging._acquireLock()
+ try:
+ stream = logging.root.handlers[0]
+ stream.setFormatter(
+ ColoredFormatter(
+ fmt=kwargs.get('format', BASIC_FORMAT),
+ datefmt=kwargs.get('datefmt', None)))
+ finally:
+ logging._releaseLock()
+
+
+def ensure_configured(func):
+ """Modify a function to call ``basicConfig`` first if no handlers exist."""
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ if len(logging.root.handlers) == 0:
+ basicConfig()
+ return func(*args, **kwargs)
+ return wrapper
+
+root = logging.root
+getLogger = logging.getLogger
+debug = ensure_configured(logging.debug)
+info = ensure_configured(logging.info)
+warning = ensure_configured(logging.warning)
+error = ensure_configured(logging.error)
+critical = ensure_configured(logging.critical)
+log = ensure_configured(logging.log)
+exception = ensure_configured(logging.exception)
diff --git a/module/lib/feedparser.py b/lib/Python/Lib/feedparser.py
index a746ed8f5..a746ed8f5 100644
--- a/module/lib/feedparser.py
+++ b/lib/Python/Lib/feedparser.py
diff --git a/module/lib/jinja2/__init__.py b/lib/Python/Lib/jinja2/__init__.py
index f944e11b6..f944e11b6 100644
--- a/module/lib/jinja2/__init__.py
+++ b/lib/Python/Lib/jinja2/__init__.py
diff --git a/module/lib/jinja2/_markupsafe/__init__.py b/lib/Python/Lib/jinja2/_markupsafe/__init__.py
index ec7bd572d..ec7bd572d 100644
--- a/module/lib/jinja2/_markupsafe/__init__.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/__init__.py
diff --git a/module/lib/jinja2/_markupsafe/_bundle.py b/lib/Python/Lib/jinja2/_markupsafe/_bundle.py
index e694faf23..e694faf23 100644
--- a/module/lib/jinja2/_markupsafe/_bundle.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_bundle.py
diff --git a/module/lib/jinja2/_markupsafe/_constants.py b/lib/Python/Lib/jinja2/_markupsafe/_constants.py
index 919bf03c5..919bf03c5 100644
--- a/module/lib/jinja2/_markupsafe/_constants.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_constants.py
diff --git a/module/lib/jinja2/_markupsafe/_native.py b/lib/Python/Lib/jinja2/_markupsafe/_native.py
index 7b95828ec..7b95828ec 100644
--- a/module/lib/jinja2/_markupsafe/_native.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_native.py
diff --git a/module/lib/jinja2/_markupsafe/tests.py b/lib/Python/Lib/jinja2/_markupsafe/tests.py
index c1ce3943a..c1ce3943a 100644
--- a/module/lib/jinja2/_markupsafe/tests.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/tests.py
diff --git a/module/lib/jinja2/_stringdefs.py b/lib/Python/Lib/jinja2/_stringdefs.py
index 1161b7f4a..1161b7f4a 100644
--- a/module/lib/jinja2/_stringdefs.py
+++ b/lib/Python/Lib/jinja2/_stringdefs.py
diff --git a/module/lib/jinja2/bccache.py b/lib/Python/Lib/jinja2/bccache.py
index 1e2236c3a..1e2236c3a 100644
--- a/module/lib/jinja2/bccache.py
+++ b/lib/Python/Lib/jinja2/bccache.py
diff --git a/module/lib/jinja2/compiler.py b/lib/Python/Lib/jinja2/compiler.py
index 57641596a..57641596a 100644
--- a/module/lib/jinja2/compiler.py
+++ b/lib/Python/Lib/jinja2/compiler.py
diff --git a/module/lib/jinja2/constants.py b/lib/Python/Lib/jinja2/constants.py
index cab203cc7..cab203cc7 100644
--- a/module/lib/jinja2/constants.py
+++ b/lib/Python/Lib/jinja2/constants.py
diff --git a/module/lib/jinja2/debug.py b/lib/Python/Lib/jinja2/debug.py
index eb15456d1..eb15456d1 100644
--- a/module/lib/jinja2/debug.py
+++ b/lib/Python/Lib/jinja2/debug.py
diff --git a/module/lib/jinja2/defaults.py b/lib/Python/Lib/jinja2/defaults.py
index d2d45443a..d2d45443a 100644
--- a/module/lib/jinja2/defaults.py
+++ b/lib/Python/Lib/jinja2/defaults.py
diff --git a/module/lib/jinja2/environment.py b/lib/Python/Lib/jinja2/environment.py
index ac74a5c68..ac74a5c68 100644
--- a/module/lib/jinja2/environment.py
+++ b/lib/Python/Lib/jinja2/environment.py
diff --git a/module/lib/jinja2/exceptions.py b/lib/Python/Lib/jinja2/exceptions.py
index 771f6a8d7..771f6a8d7 100644
--- a/module/lib/jinja2/exceptions.py
+++ b/lib/Python/Lib/jinja2/exceptions.py
diff --git a/module/lib/jinja2/ext.py b/lib/Python/Lib/jinja2/ext.py
index ceb38953a..ceb38953a 100644
--- a/module/lib/jinja2/ext.py
+++ b/lib/Python/Lib/jinja2/ext.py
diff --git a/module/lib/jinja2/filters.py b/lib/Python/Lib/jinja2/filters.py
index d1848e434..d1848e434 100644
--- a/module/lib/jinja2/filters.py
+++ b/lib/Python/Lib/jinja2/filters.py
diff --git a/module/lib/jinja2/lexer.py b/lib/Python/Lib/jinja2/lexer.py
index 0d3f69617..0d3f69617 100644
--- a/module/lib/jinja2/lexer.py
+++ b/lib/Python/Lib/jinja2/lexer.py
diff --git a/module/lib/jinja2/loaders.py b/lib/Python/Lib/jinja2/loaders.py
index bd435e8b0..bd435e8b0 100644
--- a/module/lib/jinja2/loaders.py
+++ b/lib/Python/Lib/jinja2/loaders.py
diff --git a/module/lib/jinja2/meta.py b/lib/Python/Lib/jinja2/meta.py
index 3a779a5e9..3a779a5e9 100644
--- a/module/lib/jinja2/meta.py
+++ b/lib/Python/Lib/jinja2/meta.py
diff --git a/module/lib/jinja2/nodes.py b/lib/Python/Lib/jinja2/nodes.py
index 6446c70ea..6446c70ea 100644
--- a/module/lib/jinja2/nodes.py
+++ b/lib/Python/Lib/jinja2/nodes.py
diff --git a/module/lib/jinja2/optimizer.py b/lib/Python/Lib/jinja2/optimizer.py
index 00eab115e..00eab115e 100644
--- a/module/lib/jinja2/optimizer.py
+++ b/lib/Python/Lib/jinja2/optimizer.py
diff --git a/module/lib/jinja2/parser.py b/lib/Python/Lib/jinja2/parser.py
index d44229ad0..d44229ad0 100644
--- a/module/lib/jinja2/parser.py
+++ b/lib/Python/Lib/jinja2/parser.py
diff --git a/module/lib/jinja2/runtime.py b/lib/Python/Lib/jinja2/runtime.py
index 6fea3aa4f..6fea3aa4f 100644
--- a/module/lib/jinja2/runtime.py
+++ b/lib/Python/Lib/jinja2/runtime.py
diff --git a/module/lib/jinja2/sandbox.py b/lib/Python/Lib/jinja2/sandbox.py
index 749719548..749719548 100644
--- a/module/lib/jinja2/sandbox.py
+++ b/lib/Python/Lib/jinja2/sandbox.py
diff --git a/module/lib/jinja2/tests.py b/lib/Python/Lib/jinja2/tests.py
index d257eca0a..d257eca0a 100644
--- a/module/lib/jinja2/tests.py
+++ b/lib/Python/Lib/jinja2/tests.py
diff --git a/module/lib/jinja2/utils.py b/lib/Python/Lib/jinja2/utils.py
index 7b77b8eb7..7b77b8eb7 100644
--- a/module/lib/jinja2/utils.py
+++ b/lib/Python/Lib/jinja2/utils.py
diff --git a/module/lib/jinja2/visitor.py b/lib/Python/Lib/jinja2/visitor.py
index 413e7c309..413e7c309 100644
--- a/module/lib/jinja2/visitor.py
+++ b/lib/Python/Lib/jinja2/visitor.py
diff --git a/module/lib/rename_process.py b/lib/Python/Lib/rename_process.py
index 2527cef39..2527cef39 100644
--- a/module/lib/rename_process.py
+++ b/lib/Python/Lib/rename_process.py
diff --git a/lib/Python/Lib/send2trash/__init__.py b/lib/Python/Lib/send2trash/__init__.py
new file mode 100644
index 000000000..8a059a058
--- /dev/null
+++ b/lib/Python/Lib/send2trash/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+import sys
+
+if sys.platform == 'darwin':
+ from .plat_osx import send2trash
+elif sys.platform == 'win32':
+ from .plat_win import send2trash
+else:
+ try:
+ # If we can use gio, let's use it
+ from .plat_gio import send2trash
+ except ImportError:
+ # Oh well, let's fallback to our own Freedesktop trash implementation
+ from .plat_other import send2trash
diff --git a/lib/Python/Lib/send2trash/compat.py b/lib/Python/Lib/send2trash/compat.py
new file mode 100644
index 000000000..0f3a48972
--- /dev/null
+++ b/lib/Python/Lib/send2trash/compat.py
@@ -0,0 +1,13 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+import sys
+if sys.version < '3':
+ text_type = unicode
+ binary_type = str
+else:
+ text_type = str
+ binary_type = bytes
diff --git a/lib/Python/Lib/send2trash/plat_gio.py b/lib/Python/Lib/send2trash/plat_gio.py
new file mode 100644
index 000000000..9282d90a3
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_gio.py
@@ -0,0 +1,14 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from gi.repository import GObject, Gio
+
+def send2trash(path):
+ try:
+ f = Gio.File.new_for_path(path)
+ f.trash(cancellable=None)
+ except GObject.GError as e:
+ raise OSError(e.message)
diff --git a/lib/Python/Lib/send2trash/plat_osx.py b/lib/Python/Lib/send2trash/plat_osx.py
new file mode 100644
index 000000000..fa2c2c85c
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_osx.py
@@ -0,0 +1,48 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from __future__ import unicode_literals
+
+from ctypes import cdll, byref, Structure, c_char, c_char_p
+from ctypes.util import find_library
+
+from .compat import binary_type
+
+Foundation = cdll.LoadLibrary(find_library('Foundation'))
+CoreServices = cdll.LoadLibrary(find_library('CoreServices'))
+
+GetMacOSStatusCommentString = Foundation.GetMacOSStatusCommentString
+GetMacOSStatusCommentString.restype = c_char_p
+FSPathMakeRefWithOptions = CoreServices.FSPathMakeRefWithOptions
+FSMoveObjectToTrashSync = CoreServices.FSMoveObjectToTrashSync
+
+kFSPathMakeRefDefaultOptions = 0
+kFSPathMakeRefDoNotFollowLeafSymlink = 0x01
+
+kFSFileOperationDefaultOptions = 0
+kFSFileOperationOverwrite = 0x01
+kFSFileOperationSkipSourcePermissionErrors = 0x02
+kFSFileOperationDoNotMoveAcrossVolumes = 0x04
+kFSFileOperationSkipPreflight = 0x08
+
+class FSRef(Structure):
+ _fields_ = [('hidden', c_char * 80)]
+
+def check_op_result(op_result):
+ if op_result:
+ msg = GetMacOSStatusCommentString(op_result).decode('utf-8')
+ raise OSError(msg)
+
+def send2trash(path):
+ if not isinstance(path, binary_type):
+ path = path.encode('utf-8')
+ fp = FSRef()
+ opts = kFSPathMakeRefDoNotFollowLeafSymlink
+ op_result = FSPathMakeRefWithOptions(path, opts, byref(fp), None)
+ check_op_result(op_result)
+ opts = kFSFileOperationDefaultOptions
+ op_result = FSMoveObjectToTrashSync(byref(fp), None, opts)
+ check_op_result(op_result)
diff --git a/lib/Python/Lib/send2trash/plat_other.py b/lib/Python/Lib/send2trash/plat_other.py
new file mode 100644
index 000000000..59000eca9
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_other.py
@@ -0,0 +1,160 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+# This is a reimplementation of plat_other.py with reference to the
+# freedesktop.org trash specification:
+# [1] http://www.freedesktop.org/wiki/Specifications/trash-spec
+# [2] http://www.ramendik.ru/docs/trashspec.html
+# See also:
+# [3] http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+#
+# For external volumes this implementation will raise an exception if it can't
+# find or create the user's trash directory.
+
+from __future__ import unicode_literals
+
+import sys
+import os
+import os.path as op
+from datetime import datetime
+import stat
+try:
+ from urllib.parse import quote
+except ImportError:
+ # Python 2
+ from urllib import quote
+
+FILES_DIR = 'files'
+INFO_DIR = 'info'
+INFO_SUFFIX = '.trashinfo'
+
+# Default of ~/.local/share [3]
+XDG_DATA_HOME = op.expanduser(os.environ.get('XDG_DATA_HOME', '~/.local/share'))
+HOMETRASH = op.join(XDG_DATA_HOME, 'Trash')
+
+uid = os.getuid()
+TOPDIR_TRASH = '.Trash'
+TOPDIR_FALLBACK = '.Trash-' + str(uid)
+
+def is_parent(parent, path):
+ path = op.realpath(path) # In case it's a symlink
+ parent = op.realpath(parent)
+ return path.startswith(parent)
+
+def format_date(date):
+ return date.strftime("%Y-%m-%dT%H:%M:%S")
+
+def info_for(src, topdir):
+ # ...it MUST not include a ".."" directory, and for files not "under" that
+ # directory, absolute pathnames must be used. [2]
+ if topdir is None or not is_parent(topdir, src):
+ src = op.abspath(src)
+ else:
+ src = op.relpath(src, topdir)
+
+ info = "[Trash Info]\n"
+ info += "Path=" + quote(src) + "\n"
+ info += "DeletionDate=" + format_date(datetime.now()) + "\n"
+ return info
+
+def check_create(dir):
+ # use 0700 for paths [3]
+ if not op.exists(dir):
+ os.makedirs(dir, 0o700)
+
+def trash_move(src, dst, topdir=None):
+ filename = op.basename(src)
+ filespath = op.join(dst, FILES_DIR)
+ infopath = op.join(dst, INFO_DIR)
+ base_name, ext = op.splitext(filename)
+
+ counter = 0
+ destname = filename
+ while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)):
+ counter += 1
+ destname = '%s %s%s' % (base_name, counter, ext)
+
+ check_create(filespath)
+ check_create(infopath)
+
+ os.rename(src, op.join(filespath, destname))
+ f = open(op.join(infopath, destname + INFO_SUFFIX), 'w')
+ f.write(info_for(src, topdir))
+ f.close()
+
+def find_mount_point(path):
+ # Even if something's wrong, "/" is a mount point, so the loop will exit.
+ # Use realpath in case it's a symlink
+ path = op.realpath(path) # Required to avoid infinite loop
+ while not op.ismount(path):
+ path = op.split(path)[0]
+ return path
+
+def find_ext_volume_global_trash(volume_root):
+ # from [2] Trash directories (1) check for a .Trash dir with the right
+ # permissions set.
+ trash_dir = op.join(volume_root, TOPDIR_TRASH)
+ if not op.exists(trash_dir):
+ return None
+
+ mode = os.lstat(trash_dir).st_mode
+ # vol/.Trash must be a directory, cannot be a symlink, and must have the
+ # sticky bit set.
+ if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX):
+ return None
+
+ trash_dir = op.join(trash_dir, str(uid))
+ try:
+ check_create(trash_dir)
+ except OSError:
+ return None
+ return trash_dir
+
+def find_ext_volume_fallback_trash(volume_root):
+ # from [2] Trash directories (1) create a .Trash-$uid dir.
+ trash_dir = op.join(volume_root, TOPDIR_FALLBACK)
+ # Try to make the directory, if we can't the OSError exception will escape
+ # be thrown out of send2trash.
+ check_create(trash_dir)
+ return trash_dir
+
+def find_ext_volume_trash(volume_root):
+ trash_dir = find_ext_volume_global_trash(volume_root)
+ if trash_dir is None:
+ trash_dir = find_ext_volume_fallback_trash(volume_root)
+ return trash_dir
+
+# Pull this out so it's easy to stub (to avoid stubbing lstat itself)
+def get_dev(path):
+ return os.lstat(path).st_dev
+
+def send2trash(path):
+ if not isinstance(path, str):
+ path = str(path, sys.getfilesystemencoding())
+ if not op.exists(path):
+ raise OSError("File not found: %s" % path)
+ # ...should check whether the user has the necessary permissions to delete
+ # it, before starting the trashing operation itself. [2]
+ if not os.access(path, os.W_OK):
+ raise OSError("Permission denied: %s" % path)
+ # if the file to be trashed is on the same device as HOMETRASH we
+ # want to move it there.
+ path_dev = get_dev(path)
+
+ # If XDG_DATA_HOME or HOMETRASH do not yet exist we need to stat the
+ # home directory, and these paths will be created further on if needed.
+ trash_dev = get_dev(op.expanduser('~'))
+
+ if path_dev == trash_dev:
+ topdir = XDG_DATA_HOME
+ dest_trash = HOMETRASH
+ else:
+ topdir = find_mount_point(path)
+ trash_dev = get_dev(topdir)
+ if trash_dev != path_dev:
+ raise OSError("Couldn't find mount point for %s" % path)
+ dest_trash = find_ext_volume_trash(topdir)
+ trash_move(path, dest_trash, topdir)
diff --git a/lib/Python/Lib/send2trash/plat_win.py b/lib/Python/Lib/send2trash/plat_win.py
new file mode 100644
index 000000000..3a55b9d3b
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_win.py
@@ -0,0 +1,59 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from __future__ import unicode_literals
+
+from ctypes import windll, Structure, byref, c_uint
+from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
+import os.path as op
+
+from .compat import text_type
+
+shell32 = windll.shell32
+SHFileOperationW = shell32.SHFileOperationW
+
+class SHFILEOPSTRUCTW(Structure):
+ _fields_ = [
+ ("hwnd", HWND),
+ ("wFunc", UINT),
+ ("pFrom", LPCWSTR),
+ ("pTo", LPCWSTR),
+ ("fFlags", c_uint),
+ ("fAnyOperationsAborted", BOOL),
+ ("hNameMappings", c_uint),
+ ("lpszProgressTitle", LPCWSTR),
+ ]
+
+FO_MOVE = 1
+FO_COPY = 2
+FO_DELETE = 3
+FO_RENAME = 4
+
+FOF_MULTIDESTFILES = 1
+FOF_SILENT = 4
+FOF_NOCONFIRMATION = 16
+FOF_ALLOWUNDO = 64
+FOF_NOERRORUI = 1024
+
+def send2trash(path):
+ if not isinstance(path, text_type):
+ path = text_type(path, 'mbcs')
+ if not op.isabs(path):
+ path = op.abspath(path)
+ fileop = SHFILEOPSTRUCTW()
+ fileop.hwnd = 0
+ fileop.wFunc = FO_DELETE
+ fileop.pFrom = LPCWSTR(path + '\0')
+ fileop.pTo = None
+ fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT
+ fileop.fAnyOperationsAborted = 0
+ fileop.hNameMappings = 0
+ fileop.lpszProgressTitle = None
+ result = SHFileOperationW(byref(fileop))
+ if result:
+ msg = "Couldn't perform operation. Error code: %d" % result
+ raise OSError(msg)
+
diff --git a/module/lib/simplejson/__init__.py b/lib/Python/Lib/simplejson/__init__.py
index ef5c0db48..ef5c0db48 100644
--- a/module/lib/simplejson/__init__.py
+++ b/lib/Python/Lib/simplejson/__init__.py
diff --git a/module/lib/simplejson/decoder.py b/lib/Python/Lib/simplejson/decoder.py
index e5496d6e7..e5496d6e7 100644
--- a/module/lib/simplejson/decoder.py
+++ b/lib/Python/Lib/simplejson/decoder.py
diff --git a/module/lib/simplejson/encoder.py b/lib/Python/Lib/simplejson/encoder.py
index 5ec7440f1..5ec7440f1 100644
--- a/module/lib/simplejson/encoder.py
+++ b/lib/Python/Lib/simplejson/encoder.py
diff --git a/module/lib/simplejson/ordered_dict.py b/lib/Python/Lib/simplejson/ordered_dict.py
index 87ad88824..87ad88824 100644
--- a/module/lib/simplejson/ordered_dict.py
+++ b/lib/Python/Lib/simplejson/ordered_dict.py
diff --git a/module/lib/simplejson/scanner.py b/lib/Python/Lib/simplejson/scanner.py
index 54593a371..54593a371 100644
--- a/module/lib/simplejson/scanner.py
+++ b/lib/Python/Lib/simplejson/scanner.py
diff --git a/module/lib/simplejson/tool.py b/lib/Python/Lib/simplejson/tool.py
index 73370db55..73370db55 100644
--- a/module/lib/simplejson/tool.py
+++ b/lib/Python/Lib/simplejson/tool.py
diff --git a/module/lib/thrift/TSCons.py b/lib/Python/Lib/thrift/TSCons.py
index 24046256c..24046256c 100644
--- a/module/lib/thrift/TSCons.py
+++ b/lib/Python/Lib/thrift/TSCons.py
diff --git a/module/lib/thrift/TSerialization.py b/lib/Python/Lib/thrift/TSerialization.py
index b19f98aa8..b19f98aa8 100644
--- a/module/lib/thrift/TSerialization.py
+++ b/lib/Python/Lib/thrift/TSerialization.py
diff --git a/module/lib/thrift/Thrift.py b/lib/Python/Lib/thrift/Thrift.py
index 1d271fcff..1d271fcff 100644
--- a/module/lib/thrift/Thrift.py
+++ b/lib/Python/Lib/thrift/Thrift.py
diff --git a/module/lib/thrift/__init__.py b/lib/Python/Lib/thrift/__init__.py
index 48d659c40..48d659c40 100644
--- a/module/lib/thrift/__init__.py
+++ b/lib/Python/Lib/thrift/__init__.py
diff --git a/module/lib/thrift/protocol/TBase.py b/lib/Python/Lib/thrift/protocol/TBase.py
index e675c7dc0..e675c7dc0 100644
--- a/module/lib/thrift/protocol/TBase.py
+++ b/lib/Python/Lib/thrift/protocol/TBase.py
diff --git a/module/lib/thrift/protocol/TBinaryProtocol.py b/lib/Python/Lib/thrift/protocol/TBinaryProtocol.py
index 50c6aa896..50c6aa896 100644
--- a/module/lib/thrift/protocol/TBinaryProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TBinaryProtocol.py
diff --git a/module/lib/thrift/protocol/TCompactProtocol.py b/lib/Python/Lib/thrift/protocol/TCompactProtocol.py
index 016a33171..016a33171 100644
--- a/module/lib/thrift/protocol/TCompactProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TCompactProtocol.py
diff --git a/module/lib/thrift/protocol/TProtocol.py b/lib/Python/Lib/thrift/protocol/TProtocol.py
index 7338ff68a..7338ff68a 100644
--- a/module/lib/thrift/protocol/TProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TProtocol.py
diff --git a/module/lib/thrift/protocol/__init__.py b/lib/Python/Lib/thrift/protocol/__init__.py
index d53359b28..d53359b28 100644
--- a/module/lib/thrift/protocol/__init__.py
+++ b/lib/Python/Lib/thrift/protocol/__init__.py
diff --git a/module/lib/thrift/server/THttpServer.py b/lib/Python/Lib/thrift/server/THttpServer.py
index 3047d9c00..3047d9c00 100644
--- a/module/lib/thrift/server/THttpServer.py
+++ b/lib/Python/Lib/thrift/server/THttpServer.py
diff --git a/module/lib/thrift/server/TNonblockingServer.py b/lib/Python/Lib/thrift/server/TNonblockingServer.py
index ea348a0b6..ea348a0b6 100644
--- a/module/lib/thrift/server/TNonblockingServer.py
+++ b/lib/Python/Lib/thrift/server/TNonblockingServer.py
diff --git a/module/lib/thrift/server/TProcessPoolServer.py b/lib/Python/Lib/thrift/server/TProcessPoolServer.py
index 7ed814a88..7ed814a88 100644
--- a/module/lib/thrift/server/TProcessPoolServer.py
+++ b/lib/Python/Lib/thrift/server/TProcessPoolServer.py
diff --git a/module/lib/thrift/server/TServer.py b/lib/Python/Lib/thrift/server/TServer.py
index 8456e2d40..8456e2d40 100644
--- a/module/lib/thrift/server/TServer.py
+++ b/lib/Python/Lib/thrift/server/TServer.py
diff --git a/module/lib/thrift/server/__init__.py b/lib/Python/Lib/thrift/server/__init__.py
index 1bf6e254e..1bf6e254e 100644
--- a/module/lib/thrift/server/__init__.py
+++ b/lib/Python/Lib/thrift/server/__init__.py
diff --git a/module/lib/thrift/transport/THttpClient.py b/lib/Python/Lib/thrift/transport/THttpClient.py
index 50269785c..50269785c 100644
--- a/module/lib/thrift/transport/THttpClient.py
+++ b/lib/Python/Lib/thrift/transport/THttpClient.py
diff --git a/module/lib/thrift/transport/TSocket.py b/lib/Python/Lib/thrift/transport/TSocket.py
index 4e0e1874f..4e0e1874f 100644
--- a/module/lib/thrift/transport/TSocket.py
+++ b/lib/Python/Lib/thrift/transport/TSocket.py
diff --git a/module/lib/thrift/transport/TTransport.py b/lib/Python/Lib/thrift/transport/TTransport.py
index 12e51a9bf..12e51a9bf 100644
--- a/module/lib/thrift/transport/TTransport.py
+++ b/lib/Python/Lib/thrift/transport/TTransport.py
diff --git a/module/lib/thrift/transport/TTwisted.py b/lib/Python/Lib/thrift/transport/TTwisted.py
index b6dcb4e0b..b6dcb4e0b 100644
--- a/module/lib/thrift/transport/TTwisted.py
+++ b/lib/Python/Lib/thrift/transport/TTwisted.py
diff --git a/module/lib/thrift/transport/TZlibTransport.py b/lib/Python/Lib/thrift/transport/TZlibTransport.py
index 784d4e1e0..784d4e1e0 100644
--- a/module/lib/thrift/transport/TZlibTransport.py
+++ b/lib/Python/Lib/thrift/transport/TZlibTransport.py
diff --git a/module/lib/thrift/transport/__init__.py b/lib/Python/Lib/thrift/transport/__init__.py
index 46e54fe6b..46e54fe6b 100644
--- a/module/lib/thrift/transport/__init__.py
+++ b/lib/Python/Lib/thrift/transport/__init__.py
diff --git a/module/lib/wsgiserver/LICENSE.txt b/lib/Python/Lib/wsgiserver/LICENSE.txt
index a15165ee2..a15165ee2 100644
--- a/module/lib/wsgiserver/LICENSE.txt
+++ b/lib/Python/Lib/wsgiserver/LICENSE.txt
diff --git a/module/lib/wsgiserver/__init__.py b/lib/Python/Lib/wsgiserver/__init__.py
index c380e18b0..c380e18b0 100644
--- a/module/lib/wsgiserver/__init__.py
+++ b/lib/Python/Lib/wsgiserver/__init__.py
diff --git a/locale/cli.pot b/locale/cli.pot
index 646c6c70e..db8033d31 100644
--- a/locale/cli.pot
+++ b/locale/cli.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,283 +17,283 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pyLoadCli.py:75 pyLoadCli.py:133
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
msgid " Command Line Interface"
msgstr ""
-#: pyLoadCli.py:165
+#: pyload/cli/Cli.py:165
#, python-format
msgid "%s Downloads:"
msgstr ""
-#: pyLoadCli.py:177
+#: pyload/cli/Cli.py:177
msgid " Speed: "
msgstr ""
-#: pyLoadCli.py:177
+#: pyload/cli/Cli.py:177
msgid " Size: "
msgstr ""
-#: pyLoadCli.py:178
+#: pyload/cli/Cli.py:178
msgid " Finished in: "
msgstr ""
-#: pyLoadCli.py:179
+#: pyload/cli/Cli.py:179
msgid " ID: "
msgstr ""
-#: pyLoadCli.py:184
+#: pyload/cli/Cli.py:184
msgid "waiting: "
msgstr ""
-#: pyLoadCli.py:191 pyLoadCli.py:193
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
msgid "Status:"
msgstr ""
-#: pyLoadCli.py:191
+#: pyload/cli/Cli.py:191
msgid "paused"
msgstr ""
-#: pyLoadCli.py:193
+#: pyload/cli/Cli.py:193
msgid "running"
msgstr ""
-#: pyLoadCli.py:196
+#: pyload/cli/Cli.py:196
msgid "total Speed"
msgstr ""
-#: pyLoadCli.py:196
+#: pyload/cli/Cli.py:196
msgid "Files in queue"
msgstr ""
-#: pyLoadCli.py:197
+#: pyload/cli/Cli.py:197
msgid "Total"
msgstr ""
-#: pyLoadCli.py:203
+#: pyload/cli/Cli.py:203
msgid "Menu:"
msgstr ""
-#: pyLoadCli.py:205
+#: pyload/cli/Cli.py:205
msgid " Add Links"
msgstr ""
-#: pyLoadCli.py:206
+#: pyload/cli/Cli.py:206
msgid " Manage Queue"
msgstr ""
-#: pyLoadCli.py:207
+#: pyload/cli/Cli.py:207
msgid " Manage Collector"
msgstr ""
-#: pyLoadCli.py:208
+#: pyload/cli/Cli.py:208
msgid " (Un)Pause Server"
msgstr ""
-#: pyLoadCli.py:209
+#: pyload/cli/Cli.py:209
msgid " Kill Server"
msgstr ""
-#: pyLoadCli.py:210
+#: pyload/cli/Cli.py:210
msgid " Quit"
msgstr ""
-#: pyLoadCli.py:289 pyLoadCli.py:296
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
msgid "Please use this syntax: add <Package name> <link> <link2> ..."
msgstr ""
-#: pyLoadCli.py:315
+#: pyload/cli/Cli.py:315
#, python-format
msgid "Checking %d links:"
msgstr ""
-#: pyLoadCli.py:324
+#: pyload/cli/Cli.py:324
msgid "File does not exists."
msgstr ""
-#: pyLoadCli.py:385
+#: pyload/cli/Cli.py:385
msgid "pyLoad was terminated"
msgstr ""
-#: pyLoadCli.py:443
+#: pyload/cli/Cli.py:443
msgid "Prints server status"
msgstr ""
-#: pyLoadCli.py:444
+#: pyload/cli/Cli.py:444
msgid "Prints downloads in queue"
msgstr ""
-#: pyLoadCli.py:445
+#: pyload/cli/Cli.py:445
msgid "Prints downloads in collector"
msgstr ""
-#: pyLoadCli.py:446
+#: pyload/cli/Cli.py:446
msgid "Adds package to queue"
msgstr ""
-#: pyLoadCli.py:447
+#: pyload/cli/Cli.py:447
msgid "Adds package to collector"
msgstr ""
-#: pyLoadCli.py:448
+#: pyload/cli/Cli.py:448
msgid "Delete Files from Queue/Collector"
msgstr ""
-#: pyLoadCli.py:449
+#: pyload/cli/Cli.py:449
msgid "Delete Packages from Queue/Collector"
msgstr ""
-#: pyLoadCli.py:450
+#: pyload/cli/Cli.py:450
msgid "Move Packages from Queue to Collector or vice versa"
msgstr ""
-#: pyLoadCli.py:451
+#: pyload/cli/Cli.py:451
msgid "Restart files"
msgstr ""
-#: pyLoadCli.py:452
+#: pyload/cli/Cli.py:452
msgid "Restart packages"
msgstr ""
-#: pyLoadCli.py:453
+#: pyload/cli/Cli.py:453
msgid "Check online status, works with local container"
msgstr ""
-#: pyLoadCli.py:454
+#: pyload/cli/Cli.py:454
msgid "Checks online status of a container file"
msgstr ""
-#: pyLoadCli.py:455
+#: pyload/cli/Cli.py:455
msgid "Pause the server"
msgstr ""
-#: pyLoadCli.py:456
+#: pyload/cli/Cli.py:456
msgid "continue downloads"
msgstr ""
-#: pyLoadCli.py:457
+#: pyload/cli/Cli.py:457
msgid "Toggle pause/unpause"
msgstr ""
-#: pyLoadCli.py:458
+#: pyload/cli/Cli.py:458
msgid "kill server"
msgstr ""
-#: pyLoadCli.py:460
+#: pyload/cli/Cli.py:460
msgid "List of commands:"
msgstr ""
-#: pyLoadCli.py:473
+#: pyload/cli/Cli.py:473
msgid "Couldn't write user config file"
msgstr ""
-#: pyLoadCli.py:548
+#: pyload/cli/Cli.py:548
msgid "You need py-openssl to connect to this pyLoad Core."
msgstr ""
-#: pyLoadCli.py:555
+#: pyload/cli/Cli.py:555
msgid "Address: "
msgstr ""
-#: pyLoadCli.py:556
+#: pyload/cli/Cli.py:556
msgid "Port: "
msgstr ""
-#: pyLoadCli.py:557
+#: pyload/cli/Cli.py:557
msgid "Username: "
msgstr ""
-#: pyLoadCli.py:561
+#: pyload/cli/Cli.py:561
msgid "Password: "
msgstr ""
-#: pyLoadCli.py:566 pyLoadCli.py:575
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
msgid "Login data is wrong."
msgstr ""
-#: pyLoadCli.py:568 pyLoadCli.py:577
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
#, python-format
msgid "Could not establish connection to %(addr)s:%(port)s."
msgstr ""
-#: pyLoadCli.py:580
+#: pyload/cli/Cli.py:580
msgid "You need py-openssl to connect to this pyLoad core."
msgstr ""
-#: pyLoadCli.py:582
+#: pyload/cli/Cli.py:582
msgid "Interactive mode ignored since you passed some commands."
msgstr ""
-#: module/cli/ManageFiles.py:97
-msgid "Manage Packages:"
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
msgstr ""
-#: module/cli/ManageFiles.py:99
-msgid "Manage Links:"
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
msgstr ""
-#: module/cli/ManageFiles.py:104
-msgid "What do you want to move?"
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
msgstr ""
-#: module/cli/ManageFiles.py:106
-msgid "What do you want to delete?"
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
msgstr ""
-#: module/cli/ManageFiles.py:108
-msgid "What do you want to restart?"
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
msgstr ""
-#: module/cli/ManageFiles.py:113
-msgid "Choose what yout want to do or enter package number."
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "delete"
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "move"
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "restart"
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
msgstr ""
-#: module/cli/ManageFiles.py:148
-msgid " - previous"
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
msgstr ""
-#: module/cli/ManageFiles.py:148
-msgid " - next"
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
msgstr ""
-#: module/cli/ManageFiles.py:149 module/cli/AddPackage.py:64
-msgid " back to main menu"
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
msgstr ""
-#: module/cli/AddPackage.py:48
-msgid "Add Package:"
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
msgstr ""
-#: module/cli/AddPackage.py:53
-msgid "Enter a name for the new package"
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
msgstr ""
-#: module/cli/AddPackage.py:57
-#, python-format
-msgid "Package: %s"
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
msgstr ""
-#: module/cli/AddPackage.py:58
-msgid "Parse the links you want to add."
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
msgstr ""
-#: module/cli/AddPackage.py:59
-#, python-format
-msgid "Type %s when done."
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
msgstr ""
-#: module/cli/AddPackage.py:60
-msgid "Links added: "
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
msgstr ""
diff --git a/locale/core.pot b/locale/core.pot
index 546f0e4d3..84879674d 100644
--- a/locale/core.pot
+++ b/locale/core.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,850 +17,883 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pyLoadCore.py:185
+#: pyload/Core.py:181
msgid "Received Quit signal"
msgstr ""
-#: pyLoadCore.py:301
+#: pyload/Core.py:302
#, python-format
msgid "pyLoad already running with pid %s"
msgstr ""
-#: pyLoadCore.py:315
+#: pyload/Core.py:316
#, python-format
msgid "Failed changing group: %s"
msgstr ""
-#: pyLoadCore.py:325
+#: pyload/Core.py:326
#, python-format
msgid "Failed changing user: %s"
msgstr ""
-#: pyLoadCore.py:327
+#: pyload/Core.py:328
msgid "folder for logs"
msgstr ""
-#: pyLoadCore.py:338
+#: pyload/Core.py:339
msgid "Starting"
msgstr ""
-#: pyLoadCore.py:339
+#: pyload/Core.py:340
#, python-format
msgid "Using home directory: %s"
msgstr ""
-#: pyLoadCore.py:348
+#: pyload/Core.py:349
msgid "pycrypto to decode container files"
msgstr ""
-#: pyLoadCore.py:351
+#: pyload/Core.py:352
msgid "folder for temporary files"
msgstr ""
-#: pyLoadCore.py:356
+#: pyload/Core.py:357
msgid "folder for downloads"
msgstr ""
-#: pyLoadCore.py:359
+#: pyload/Core.py:360
msgid "OpenSSL for secure connection"
msgstr ""
-#: pyLoadCore.py:363
+#: pyload/Core.py:364
msgid "Moving old user config to DB"
msgstr ""
-#: pyLoadCore.py:366
-msgid "Please check your logindata with ./pyLoadCore.py -u"
+#: pyload/Core.py:367
+msgid "Please check your logindata with ./pyload.py -u"
msgstr ""
-#: pyLoadCore.py:369
+#: pyload/Core.py:370
msgid "All links removed"
msgstr ""
-#: pyLoadCore.py:400
+#: pyload/Core.py:401
#, python-format
msgid "Downloadtime: %s"
msgstr ""
-#: pyLoadCore.py:410
+#: pyload/Core.py:411
#, python-format
msgid "Free space: %s"
msgstr ""
-#: pyLoadCore.py:430
+#: pyload/Core.py:431
msgid "Activating Accounts..."
msgstr ""
-#: pyLoadCore.py:436
+#: pyload/Core.py:437
msgid "Activating Plugins..."
msgstr ""
-#: pyLoadCore.py:439
+#: pyload/Core.py:440
msgid "pyLoad is up and running"
msgstr ""
-#: pyLoadCore.py:458
+#: pyload/Core.py:459
msgid "restarting pyLoad"
msgstr ""
-#: pyLoadCore.py:462
+#: pyload/Core.py:463
msgid "pyLoad quits"
msgstr ""
-#: pyLoadCore.py:519
+#: pyload/Core.py:520
#, python-format
msgid "Install %s"
msgstr ""
-#: pyLoadCore.py:555
+#: pyload/Core.py:556
#, python-format
msgid "could not find %(desc)s: %(name)s"
msgstr ""
-#: pyLoadCore.py:557
+#: pyload/Core.py:558
#, python-format
msgid "could not create %(desc)s: %(name)s"
msgstr ""
-#: pyLoadCore.py:578
+#: pyload/Core.py:579
msgid "shutting down..."
msgstr ""
-#: pyLoadCore.py:595
+#: pyload/Core.py:596
msgid "error while shutting down"
msgstr ""
-#: pyLoadCore.py:659
+#: pyload/Core.py:660
msgid "killed pyLoad from Terminal"
msgstr ""
-#: module/common/JsEngine.py:156
-msgid ""
-"No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or "
-"rhino"
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
msgstr ""
-#: module/remote/ThriftBackend.py:39
-msgid "Using SSL ThriftBackend"
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
msgstr ""
-#: module/remote/RemoteManager.py:35
-#, python-format
-msgid "Remote backend error: %s"
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
msgstr ""
-#: module/remote/RemoteManager.py:82
-#, python-format
-msgid "Starting %(name)s: %(addr)s:%(port)s"
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
msgstr ""
-#: module/remote/RemoteManager.py:84
-#, python-format
-msgid "Failed loading backend %(name)s | %(error)s"
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
msgstr ""
-#: module/ThreadManager.py:137
-#, python-format
-msgid "Reconnect Failed: %s"
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
msgstr ""
-#: module/ThreadManager.py:176
-msgid "Reconnect script not found!"
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
msgstr ""
-#: module/ThreadManager.py:182
-msgid "Starting reconnect"
+#: pyload/database/FileDatabase.py:45
+msgid "online"
msgstr ""
-#: module/ThreadManager.py:196
-msgid "Failed executing reconnect script!"
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
msgstr ""
-#: module/ThreadManager.py:208
-#, python-format
-msgid "Reconnected, new IP: %s"
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
msgstr ""
-#: module/ThreadManager.py:288
-msgid "Not enough space left on device"
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
msgstr ""
-#: module/HookManager.py:90 module/plugins/Hook.py:102
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugin/hooks/IRCInterface.py:74
+#: pyload/plugin/hooks/XMPPInterface.py:83
#, python-format
-msgid "Error executing hooks: %s"
+msgid "Package finished: %s"
msgstr ""
-#: module/HookManager.py:140
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
#, python-format
-msgid "Failed activating %(name)s"
+msgid "Remote backend error: %s"
msgstr ""
-#: module/HookManager.py:144
+#: pyload/remote/RemoteManager.py:82
#, python-format
-msgid "Activated plugins: %s"
+msgid "Starting %(name)s: %(addr)s:%(port)s"
msgstr ""
-#: module/HookManager.py:145
+#: pyload/remote/RemoteManager.py:84
#, python-format
-msgid "Deactivate plugins: %s"
+msgid "Failed loading backend %(name)s | %(error)s"
msgstr ""
-#: module/CaptchaManager.py:78 module/interaction/InteractionManager.py:82
-msgid "No Client connected for captcha decrypting"
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
msgstr ""
-#: module/web/ServerThread.py:35
+#: pyload/threads/ServerThread.py:36
msgid "SSL certificates not found."
msgstr ""
-#: module/web/ServerThread.py:39
+#: pyload/threads/ServerThread.py:40
#, python-format
msgid "Sorry, we dropped support for starting %s directly within pyLoad"
msgstr ""
-#: module/web/ServerThread.py:40
+#: pyload/threads/ServerThread.py:41
msgid "You can use the threaded server which offers good performance and ssl,"
msgstr ""
-#: module/web/ServerThread.py:41
+#: pyload/threads/ServerThread.py:42
#, python-format
msgid ""
"of course you can still use your existing %s with pyLoads fastcgi server"
msgstr ""
-#: module/web/ServerThread.py:42
-msgid "sample configs are located in the module/web/servers directory"
+#: pyload/threads/ServerThread.py:43
+msgid "sample configs are located in the pyload/webui/servers directory"
msgstr ""
-#: module/web/ServerThread.py:49
+#: pyload/threads/ServerThread.py:49
#, python-format
msgid "Can't use %(server)s, python-flup is not installed!"
msgstr ""
-#: module/web/ServerThread.py:56
+#: pyload/threads/ServerThread.py:58
#, python-format
msgid "Error importing lightweight server: %s"
msgstr ""
-#: module/web/ServerThread.py:57
+#: pyload/threads/ServerThread.py:59
msgid ""
"You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
msgstr ""
-#: module/web/ServerThread.py:58
-msgid "Copy the boern.so to module/lib folder or use setup.py install"
+#: pyload/threads/ServerThread.py:60
+msgid "Copy the boern.so file to lib/Python/Lib or use setup.py install"
msgstr ""
-#: module/web/ServerThread.py:59
+#: pyload/threads/ServerThread.py:61
msgid ""
"Of course you need to be familiar with linux and know how to compile software"
msgstr ""
-#: module/web/ServerThread.py:63
+#: pyload/threads/ServerThread.py:64
msgid "Server set to threaded, due to known performance problems on windows."
msgstr ""
-#: module/web/ServerThread.py:80 module/web/ServerThread.py:103
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
msgid "This server offers no SSL, please consider using threaded instead"
msgstr ""
-#: module/web/ServerThread.py:82
+#: pyload/threads/ServerThread.py:82
#, python-format
msgid "Starting builtin webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:87
+#: pyload/threads/ServerThread.py:87
#, python-format
msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:91
+#: pyload/threads/ServerThread.py:91
#, python-format
msgid "Starting threaded webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:97
+#: pyload/threads/ServerThread.py:97
#, python-format
msgid "Starting fastcgi server: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:105
+#: pyload/threads/ServerThread.py:105
#, python-format
msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
msgstr ""
-#: module/web/pyload_app.py:125
+#: pyload/webui/app/pyload.py:127
msgid "You dont have permission to access this page."
msgstr ""
-#: module/web/pyload_app.py:193
+#: pyload/webui/app/pyload.py:195
msgid "Download directory not found."
msgstr ""
-#: module/web/pyload_app.py:260 module/web/pyload_app.py:267
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
msgid "unlimited"
msgstr ""
-#: module/web/pyload_app.py:262 module/web/pyload_app.py:269
+#: pyload/webui/app/pyload.py:264 pyload/webui/app/pyload.py:271
msgid "not available"
msgstr ""
-#: module/web/pyload_app.py:509
-msgid "Run pyLoadCore.py -s to access the setup."
+#: pyload/webui/app/pyload.py:509
+msgid "Run pyload.py -s to access the setup."
msgstr ""
-#: module/web/json_app.py:60
+#: pyload/network/HTTPDownload.py:245
#, python-format
-msgid "waiting %s"
+msgid "Download chunks failed, fallback to single connection | %s"
msgstr ""
-#: module/Api.py:329
+#: pyload/threads/PluginThread.py:183
#, python-format
-msgid "Added package %(name)s containing %(count)d links"
+msgid "Download starts: %s"
msgstr ""
-#: module/Api.py:592
+#: pyload/threads/PluginThread.py:189
#, python-format
-msgid "Added %(count)d links to package #%(package)d "
-msgstr ""
-
-#: module/plugins/crypter/SerienjunkiesOrg.py:125
-msgid "Downloadlimit reached"
-msgstr ""
-
-#: module/plugins/hooks/ClickAndLoad.py:74
-msgid "Click'N'Load: Port 9666 already in use"
+msgid "Download finished: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:91
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
#, python-format
-msgid "No %s installed"
+msgid "Plugin %s is missing a function."
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:93
-#: module/plugins/hooks/ExtractArchive.py:98
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
#, python-format
-msgid "Could not activate %s"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:103
-msgid "Activated"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:105
-msgid "No Extract plugins activated"
+msgid "Download aborted: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:117
+#: pyload/threads/PluginThread.py:222
#, python-format
-msgid "Package %s queued for later extracting"
+msgid "Download restarted: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:142
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
#, python-format
-msgid "Check package %s"
+msgid "Download is offline: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:179
+#: pyload/threads/PluginThread.py:234
#, python-format
-msgid "Extract to %s"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:198
-msgid "extracting"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:209
-msgid "Password protected"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:229
-msgid "Wrong password"
+msgid "Download is temporary offline: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:237
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
#, python-format
-msgid "Deleting %s files"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:242
-msgid "Extracting finished"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:249
-msgid "Archive Error"
+msgid "Download failed: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:251
-msgid "CRC Mismatch"
+#: pyload/threads/PluginThread.py:254
+msgid ""
+"Couldn't connect to host or connection reset, waiting 1 minute and retry."
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:255
-msgid "Unknown Error"
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:307
-msgid "Setting User and Group failed"
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
msgstr ""
-#: module/plugins/hooks/CaptchaTrader.py:71
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
#, python-format
-msgid "%s credits left"
+msgid "Decrypting failed: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/CaptchaTrader.py:132
-msgid "Your CaptchaTrader Account has not enough credits"
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:74
-#: module/plugins/hooks/XMPPInterface.py:82
-#: module/database/FileDatabase.py:507
+#: pyload/threads/PluginThread.py:635
#, python-format
-msgid "Package finished: %s"
+msgid "Info Fetching for %(name)s failed | %(err)s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:81
+#: pyload/HookManager.py:90 pyload/plugin/Hook.py:103
#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s "
+msgid "Error executing hooks: %s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:93
+#: pyload/HookManager.py:140
#, python-format
-msgid "New Captcha Request: %s"
+msgid "Failed activating %(name)s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:94
+#: pyload/HookManager.py:144
#, python-format
-msgid "Answer with 'c %s text on the captcha'"
+msgid "Activated plugins: %s"
msgstr ""
-#: module/plugins/hooks/XMPPInterface.py:90
+#: pyload/HookManager.py:145
#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s"
+msgid "Deactivate plugins: %s"
msgstr ""
-#: module/plugins/hooks/RehostTo.py:32
-msgid "Please add your rehost.to account first and restart pyLoad"
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
msgstr ""
-#: module/plugins/hooks/HotFolder.py:82
-#, python-format
-msgid "Added %s from HotFolder"
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:54
-#, python-format
-msgid "Installed scripts for %s: "
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:70
-msgid "Script not executable:"
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:80
+#: pyload/ThreadManager.py:208
#, python-format
-msgid "Error in %(script)s: %(error)s"
+msgid "Reconnected, new IP: %s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:68
-msgid "No Updates for pyLoad"
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:73
-msgid "*** Plugins have been updated, please restart pyLoad ***"
+#: pyload/plugin/Account.py:85 pyload/plugin/Account.py:90
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:75
-msgid "Plugins updated and reloaded"
+#: pyload/plugin/Account.py:86
+msgid "Wrong Password"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:78
-msgid "No plugin updates available"
+#: pyload/plugin/Account.py:243
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:93
+#: pyload/plugin/Account.py:269
#, python-format
-msgid "*** New pyLoad Version %s available ***"
+msgid "Account %s has not enough traffic, checking again in 30min"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:94
-msgid "*** Get it here: http://pyload.org/download ***"
+#: pyload/plugin/Account.py:276
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:97
-#: module/plugins/hooks/UpdateManager.py:110
-msgid "Not able to connect server for updates"
+#: pyload/plugin/crypter/SerienjunkiesOrg.py:126
+msgid "Downloadlimit reached"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:141
+#: pyload/plugin/PluginManager.py:153
#, python-format
-msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgid "%s has a invalid pattern."
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:150
-#: module/plugins/hooks/UpdateManager.py:155
+#: pyload/plugin/PluginManager.py:272
#, python-format
-msgid "Error when updating %s"
+msgid "Error importing %(name)s: %(msg)s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:155
-msgid "Version mismatch"
+#: pyload/plugin/internal/MultiHoster.py:132
+msgid "No Hoster loaded"
msgstr ""
-#: module/plugins/hoster/BasePlugin.py:53
-msgid "Authorization required (username:password)"
+#: pyload/plugin/accounts/BitshareCom.py:38
+msgid "Activate direct Download in your Bitshare Account"
msgstr ""
-#: module/plugins/hoster/OronCom.py:135
-msgid "Not enough traffic left"
+#: pyload/plugin/container/LinkList.py:64
+msgid "LinkList could not be cleared."
msgstr ""
-#: module/plugins/hoster/OronCom.py:137
-#: module/plugins/hoster/UploadedTo.py:158
-msgid "Traffic exceeded"
+#: pyload/plugin/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:135
-msgid "You should enable direct Download in your Megaupload Account settings"
+#: pyload/plugin/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:158
+#: pyload/plugin/hoster/SimplydebridCom.py:23
+#: pyload/plugin/hoster/RealdebridCom.py:40
+#: pyload/plugin/hoster/FreeWayMe.py:39 pyload/plugin/hoster/ZeveraCom.py:21
+#: pyload/plugin/hoster/UnrestrictLi.py:52
+#: pyload/plugin/hoster/Vipleech4uCom.py:30
+#: pyload/plugin/hoster/Premium4Me.py:27 pyload/plugin/hoster/FastixRu.py:36
+#: pyload/plugin/hoster/SimplyPremiumCom.py:52
+#: pyload/plugin/hoster/MegaDebridEu.py:46
+#: pyload/plugin/hoster/AlldebridCom.py:38
+#: pyload/plugin/hoster/LinksnappyCom.py:29
+#: pyload/plugin/hoster/DebridItaliaCom.py:39
+#: pyload/plugin/hoster/RPNetBiz.py:28
+#: pyload/plugin/hoster/MultiDebridCom.py:40
+#: pyload/plugin/hoster/ReloadCc.py:26 pyload/plugin/hoster/OverLoadMe.py:38
+#: pyload/plugin/hoster/RehostTo.py:25
+#: pyload/plugin/hoster/PremiumizeMe.py:24
+#: pyload/plugin/hooks/RPNetBiz.py:45
#, python-format
-msgid "Megaupload: waiting %d minutes"
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:172
-msgid "You need premium to download files larger than 1 GB"
+msgid "Please enter your %s account or deactivate this plugin"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:177
-msgid "The file is password protected, enter a password and restart."
+#: pyload/plugin/hoster/FilesMailRu.py:98
+#, python-format
+msgid ""
+"There was HTML Code in the Downloaded File (%s)...redirect error? The "
+"Download will be restarted."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:194
-msgid "Megaupload is currently blocking your IP. Try again later, manually."
+#: pyload/plugin/hoster/NetloadIn.py:145
+#: pyload/plugin/hoster/NetloadIn.py:169
+msgid "File temporarily not available"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:269
-msgid ""
-"Looks like the file is still not available. Retry downloading later, "
-"manually."
+#: pyload/plugin/hoster/NetloadIn.py:182
+#, python-format
+msgid "Netload: waiting between downloads %d s."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:272
-msgid "Wrong password for download link."
+#: pyload/plugin/hoster/NetloadIn.py:213
+#, python-format
+msgid "Netload: waiting for captcha %d s."
msgstr ""
-#: module/plugins/hoster/UploadedTo.py:131
-msgid "API key invalid"
+#: pyload/plugin/hoster/NetloadIn.py:251
+msgid "Downloaded File was empty"
msgstr ""
-#: module/plugins/hoster/UploadedTo.py:155
+#: pyload/plugin/hoster/MegaDebridEu.py:49
#, python-format
-msgid "%s: Not enough traffic left"
-msgstr ""
-
-#: module/plugins/hoster/ShareonlineBiz.py:106
-msgid "Parallel download issue"
+msgid "Impossible to connect to %s"
msgstr ""
-#: module/plugins/hoster/ShareonlineBiz.py:121
-msgid "Invalid download ticket"
+#: pyload/plugin/hoster/MegaDebridEu.py:89
+#, python-format
+msgid "Impossible to debrid %s"
msgstr ""
-#: module/plugins/hoster/RehostTo.py:26
-msgid "Please enter your rehost.to account or deactivate this plugin"
+#: pyload/plugin/hoster/UploadedTo.py:129
+msgid "API key invalid"
msgstr ""
-#: module/plugins/hoster/FileserveCom.py:87
-msgid "Not logged in."
+#: pyload/plugin/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
msgstr ""
-#: module/plugins/hoster/FileserveCom.py:112
-msgid "Parallel download error, now waiting 60s."
+#: pyload/plugin/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:99
+#: pyload/plugin/hoster/RapidshareCom.py:99
msgid "Rapidshare: Traffic Share (direct download)"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:126
-#: module/plugins/hoster/RapidshareCom.py:192
+#: pyload/plugin/hoster/RapidshareCom.py:126
+#: pyload/plugin/hoster/RapidshareCom.py:193
msgid "Already downloading from this ip address, waiting 60 seconds"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:130
+#: pyload/plugin/hoster/RapidshareCom.py:130
msgid "Invalid Auth Code, download will be restarted"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:196
+#: pyload/plugin/hoster/RapidshareCom.py:198
msgid "RapidShareCom: No free slots"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:199
+#: pyload/plugin/hoster/RapidshareCom.py:201
msgid "You need a premium account for this file"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:201
+#: pyload/plugin/hoster/RapidshareCom.py:203
msgid "Filename reported invalid"
msgstr ""
-#: module/plugins/hoster/FilesMailRu.py:99
-msgid "There was HTML Code in the Downloaded File("
+#: pyload/plugin/hoster/FileserveCom.py:100
+msgid "Parallel download error, now waiting 60s."
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:141
-#: module/plugins/hoster/NetloadIn.py:161
-msgid "File temporarily not available"
+#: pyload/plugin/hoster/FileserveCom.py:216
+msgid "Not logged in."
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:174
-#, python-format
-msgid "Netload: waiting between downloads %d s."
+#: pyload/plugin/hoster/MegaNz.py:56
+msgid "Decryption failed"
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:203
-#, python-format
-msgid "Netload: waiting for captcha %d s."
+#: pyload/plugin/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:242
-msgid "Downloaded File was empty"
+#: pyload/plugin/hoster/MegaNz.py:118
+msgid "Error code:"
msgstr ""
-#: module/plugins/hoster/RealdebridCom.py:37
-msgid "Please enter your Real-debrid account or deactivate this plugin"
+#: pyload/plugin/Container.py:68
+msgid "File not exists."
msgstr ""
-#: module/plugins/container/LinkList.py:54
-msgid "LinkList could not be cleared."
+#: pyload/plugin/hooks/UpdateManager.py:97
+msgid "Not able to connect server to get updates"
msgstr ""
-#: module/plugins/Plugin.py:381
-msgid ""
-"Pil and tesseract not installed and no Client connected for captcha "
-"decrypting"
+#: pyload/plugin/hooks/UpdateManager.py:111
+msgid "No pyLoad version available"
msgstr ""
-#: module/plugins/Plugin.py:385
-msgid "No captcha result obtained in appropiate time by any of the plugins."
+#: pyload/plugin/hooks/UpdateManager.py:118
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugin/hooks/UpdateManager.py:119
+msgid "*** Get it here: https://github.com/pyload/pyload/releases ***"
msgstr ""
-#: module/plugins/Plugin.py:490 module/plugins/Plugin.py:520
+#: pyload/plugin/hooks/UpdateManager.py:170
#, python-format
-msgid "Setting User and Group failed: %s"
+msgid "New version of [%(type)s] %(name)s (v%(oldver)s -> v%(newver)s)"
msgstr ""
-#: module/plugins/Container.py:68
-msgid "File not exists."
+#: pyload/plugin/hooks/UpdateManager.py:180
+#: pyload/plugin/hooks/UpdateManager.py:185
+#, python-format
+msgid "Error when updating plugin %s"
msgstr ""
-#: module/plugins/accounts/MegauploadCom.py:41
-msgid "Activate direct Download in your MegaUpload Account"
+#: pyload/plugin/hooks/UpdateManager.py:185
+msgid "Version mismatch"
msgstr ""
-#: module/plugins/accounts/FilesonicCom.py:49
-msgid "Invalid login retrieving user details"
+#: pyload/plugin/hooks/UpdateManager.py:196
+#, python-format
+msgid "Removed blacklisted plugin: [%(type)s] %(name)s"
msgstr ""
-#: module/plugins/accounts/BitshareCom.py:36
-msgid "Activate direct Download in your Bitshare Account"
+#: pyload/plugin/hooks/UpdateManager.py:204
+msgid "Plugins updated and reloaded"
msgstr ""
-#: module/plugins/PluginManager.py:153
-#, python-format
-msgid "%s has a invalid pattern."
+#: pyload/plugin/hooks/UpdateManager.py:206
+msgid "*** Plugins have been updated, pyLoad will be restarted now ***"
msgstr ""
-#: module/plugins/PluginManager.py:272
-#, python-format
-msgid "Error importing %(name)s: %(msg)s"
+#: pyload/plugin/hooks/UpdateManager.py:211
+msgid "No plugin updates available"
msgstr ""
-#: module/plugins/AccountManager.py:88
-msgid "Account settings deleted, due to new config format."
+#: pyload/plugin/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
msgstr ""
-#: module/plugins/internal/MultiHoster.py:60
-msgid "No Hoster loaded"
+#: pyload/plugin/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
msgstr ""
-#: module/plugins/Account.py:85 module/plugins/Account.py:91
+#: pyload/plugin/hooks/IRCInterface.py:96
#, python-format
-msgid "Could not login with account %(user)s | %(msg)s"
+msgid "Answer with 'c %s text on the captcha'"
msgstr ""
-#: module/plugins/Account.py:86
-msgid "Wrong Password"
+#: pyload/plugin/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
msgstr ""
-#: module/plugins/Account.py:240
+#: pyload/plugin/hooks/HotFolder.py:81
#, python-format
-msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgid "Added %s from HotFolder"
msgstr ""
-#: module/plugins/Account.py:266
+#: pyload/plugin/hooks/ExtractArchive.py:94
#, python-format
-msgid "Account %s has not enough traffic, checking again in 30min"
+msgid "No %s installed"
msgstr ""
-#: module/plugins/Account.py:273
+#: pyload/plugin/hooks/ExtractArchive.py:96
+#: pyload/plugin/hooks/ExtractArchive.py:101
#, python-format
-msgid "Account %s is expired, checking again in 1h"
+msgid "Could not activate %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "finished"
+#: pyload/plugin/hooks/ExtractArchive.py:106
+msgid "Activated"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "offline"
+#: pyload/plugin/hooks/ExtractArchive.py:108
+msgid "No Extract plugins activated"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "online"
+#: pyload/plugin/hooks/ExtractArchive.py:120
+#, python-format
+msgid "Package %s queued for later extracting"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "queued"
+#: pyload/plugin/hooks/ExtractArchive.py:143
+#, python-format
+msgid "Check package %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "skipped"
+#: pyload/plugin/hooks/ExtractArchive.py:184
+#, python-format
+msgid "Extract to %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "waiting"
+#: pyload/plugin/hooks/ExtractArchive.py:199
+msgid "No files found to extract"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "temp. offline"
+#: pyload/plugin/hooks/ExtractArchive.py:206
+msgid "extracting"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "starting"
+#: pyload/plugin/hooks/ExtractArchive.py:217
+msgid "Password protected"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "failed"
+#: pyload/plugin/hooks/ExtractArchive.py:238
+msgid "Wrong password"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "aborted"
+#: pyload/plugin/hooks/ExtractArchive.py:246
+#, python-format
+msgid "Deleting %s files"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "decrypting"
+#: pyload/plugin/hooks/ExtractArchive.py:253
+msgid "Extracting finished"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "custom"
+#: pyload/plugin/hooks/ExtractArchive.py:259
+msgid "Archive Error"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "downloading"
+#: pyload/plugin/hooks/ExtractArchive.py:261
+msgid "CRC Mismatch"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "processing"
+#: pyload/plugin/hooks/ExtractArchive.py:265
+msgid "Unknown Error"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "unknown"
+#: pyload/plugin/hooks/ExtractArchive.py:317
+msgid "Setting User and Group failed"
msgstr ""
-#: module/database/DatabaseBackend.py:174
-msgid "Filedatabase was deleted due to incompatible version."
+#: pyload/plugin/hooks/ClickNLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
msgstr ""
-#: module/database/DatabaseBackend.py:189
-msgid "Filedatabase could NOT be converted."
+#: pyload/plugin/hooks/CaptchaTrader.py:70
+#: pyload/plugin/hooks/Captcha9kw.py:60
+#: pyload/plugin/hooks/ExpertDecoders.py:51
+#, python-format
+msgid "%s credits left"
msgstr ""
-#: module/database/DatabaseBackend.py:198
-msgid "Database was converted from v2 to v3."
+#: pyload/plugin/hooks/CaptchaTrader.py:118
+msgid "Could not send response."
msgstr ""
-#: module/database/DatabaseBackend.py:206
-msgid "Database was converted from v3 to v4."
+#: pyload/plugin/hooks/CaptchaTrader.py:136
+msgid "Your CaptchaTrader Account has not enough credits"
msgstr ""
-#: module/database/DatabaseBackend.py:252
-msgid "Converting old Django DB"
+#: pyload/plugin/hooks/LinkdecrypterCom.py:45
+msgid "Crypter list not found"
msgstr ""
-#: module/network/HTTPDownload.py:245
-#, python-format
-msgid "Download chunks failed, fallback to single connection | %s"
+#: pyload/plugin/hooks/LinkdecrypterCom.py:59
+msgid "Crypter list is empty"
msgstr ""
-#: module/PluginThread.py:183
+#: pyload/plugin/hooks/XMPPInterface.py:91
#, python-format
-msgid "Download starts: %s"
+msgid "Download finished: %(name)s @ %(plugin)s"
msgstr ""
-#: module/PluginThread.py:189
+#: pyload/plugin/hooks/Captcha9kw.py:94
#, python-format
-msgid "Download finished: %s"
+msgid "New CaptchaID from upload: %s : %s"
msgstr ""
-#: module/PluginThread.py:194 module/PluginThread.py:366
-#, python-format
-msgid "Plugin %s is missing a function."
+#: pyload/plugin/hooks/Captcha9kw.py:130
+msgid "Your Captcha 9kw.eu Account has not enough credits"
msgstr ""
-#: module/PluginThread.py:202 module/PluginThread.py:265
-#: module/PluginThread.py:383
+#: pyload/plugin/hooks/ExternalScripts.py:54
#, python-format
-msgid "Download aborted: %s"
+msgid "Installed scripts for %s: "
msgstr ""
-#: module/PluginThread.py:222
-#, python-format
-msgid "Download restarted: %(name)s | %(msg)s"
+#: pyload/plugin/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
msgstr ""
-#: module/PluginThread.py:231 module/PluginThread.py:374
+#: pyload/plugin/hooks/ExternalScripts.py:80
#, python-format
-msgid "Download is offline: %s"
+msgid "Error in %(script)s: %(error)s"
msgstr ""
-#: module/PluginThread.py:234
-#, python-format
-msgid "Download is temporary offline: %s"
+#: pyload/plugin/hooks/ExpertDecoders.py:96
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugin/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
msgstr ""
-#: module/PluginThread.py:237 module/PluginThread.py:304
+#: pyload/plugin/hooks/PremiumizeMe.py:50
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugin/hooks/CaptchaBrotherhood.py:70
#, python-format
-msgid "Download failed: %(name)s | %(msg)s"
+msgid "%d credits left"
msgstr ""
-#: module/PluginThread.py:254
+#: pyload/plugin/Plugin.py:389
msgid ""
-"Couldn't connect to host or connection reset, waiting 1 minute and retry."
+"Pil and tesseract not installed and no Client connected for captcha "
+"decrypting"
msgstr ""
-#: module/PluginThread.py:290
-#, python-format
-msgid "Download skipped: %(name)s due to %(plugin)s"
+#: pyload/plugin/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
msgstr ""
-#: module/PluginThread.py:362
+#: pyload/plugin/Plugin.py:498 pyload/plugin/Plugin.py:532
#, python-format
-msgid "Decrypting starts: %s"
+msgid "Setting User and Group failed: %s"
msgstr ""
-#: module/PluginThread.py:377 module/PluginThread.py:395
-#, python-format
-msgid "Decrypting failed: %(name)s | %(msg)s"
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
msgstr ""
-#: module/PluginThread.py:389
+#: pyload/Api.py:330
#, python-format
-msgid "Retrying %s"
+msgid "Added package %(name)s containing %(count)d links"
msgstr ""
-#: module/PluginThread.py:636
+#: pyload/Api.py:593
#, python-format
-msgid "Info Fetching for %(name)s failed | %(err)s"
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid ""
+"No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or "
+"rhino"
msgstr ""
diff --git a/locale/crowdin.yaml b/locale/crowdin.yaml
new file mode 100644
index 000000000..a3bd3a3e2
--- /dev/null
+++ b/locale/crowdin.yaml
@@ -0,0 +1,15 @@
+project_identifier: pyload
+preserve_hierarchy: true
+api_key: {key}
+base_path: {tmp}
+
+files:
+ -
+ source: 'pyLoad/*.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/%file_name%.po'
+ -
+ source: 'pyLoad/cli.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/pyLoadCli.po'
+ -
+ source: 'pyLoad/core.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/pyLoad.po'
diff --git a/locale/cs/LC_MESSAGES/django.mo b/locale/cs/LC_MESSAGES/django.mo
deleted file mode 100644
index 3d771c127..000000000
--- a/locale/cs/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/pyLoad.mo b/locale/cs/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index a0c01d4e9..000000000
--- a/locale/cs/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/pyLoadCli.mo b/locale/cs/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 6f8b94b67..000000000
--- a/locale/cs/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/pyLoadGui.mo b/locale/cs/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index c913703f1..000000000
--- a/locale/cs/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/setup.mo b/locale/cs/LC_MESSAGES/setup.mo
deleted file mode 100644
index 91ab321ec..000000000
--- a/locale/cs/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
deleted file mode 100644
index 017f373dc..000000000
--- a/locale/de/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/pyLoad.mo b/locale/de/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index e1e29cebc..000000000
--- a/locale/de/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/pyLoadCli.mo b/locale/de/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index e46503121..000000000
--- a/locale/de/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/pyLoadGui.mo b/locale/de/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 8ead8383c..000000000
--- a/locale/de/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/setup.mo b/locale/de/LC_MESSAGES/setup.mo
deleted file mode 100644
index 80ca3553e..000000000
--- a/locale/de/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/django.pot b/locale/django.pot
index 81c9c7b6b..277108e5b 100644
--- a/locale/django.pot
+++ b/locale/django.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,670 +17,673 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: module/web/translations.js:1 module/web/templates/default/base.html:123
-#: module/web/templates/default/base.html:124
-#: module/web/templates/default/settings_item.html:14
-msgid "on"
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
msgstr ""
-#: module/web/translations.js:2 module/web/templates/default/captcha.html:7
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
msgid "Please read the text on the captcha."
msgstr ""
-#: module/web/translations.js:3
-msgid "Settings saved."
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
msgstr ""
-#: module/web/translations.js:4 module/web/templates/default/base.html:123
-#: module/web/templates/default/base.html:124
-#: module/web/templates/default/settings_item.html:16
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
msgid "off"
msgstr ""
-#: module/web/translations.js:5
+#: pyload/webui/translations.js:5
msgid "Success"
msgstr ""
-#: module/web/translations.js:6
-msgid "Passwords did not match."
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
msgstr ""
-#: module/web/translations.js:7
-msgid "Delete Link"
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
msgstr ""
-#: module/web/translations.js:8
-msgid "pyLoad restarted"
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
msgstr ""
-#: module/web/translations.js:9
-msgid "You are really sure you want to quit pyLoad?"
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
msgstr ""
-#: module/web/translations.js:10
+#: pyload/webui/translations.js:10
msgid "Please Enter a packagename."
msgstr ""
-#: module/web/translations.js:11
+#: pyload/webui/translations.js:11
msgid "Please click on the right captcha position."
msgstr ""
-#: module/web/translations.js:12
+#: pyload/webui/translations.js:12
msgid "Error occured."
msgstr ""
-#: module/web/translations.js:13
-msgid "New Captcha Request"
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
msgstr ""
-#: module/web/translations.js:14
+#: pyload/webui/translations.js:14
msgid "Failed"
msgstr ""
-#: module/web/translations.js:15
+#: pyload/webui/translations.js:15
msgid "No Captchas to read."
msgstr ""
-#: module/web/translations.js:16
-#: module/web/templates/default/filemanager.html:65
-#: module/web/templates/default/folder.html:14
-msgid "Folder is empty"
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
msgstr ""
-#: module/web/translations.js:17
-msgid "Restart Link"
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
msgstr ""
-#: module/web/translations.js:18
+#: pyload/webui/translations.js:18
msgid "New folder"
msgstr ""
-#: module/web/translations.js:19
+#: pyload/webui/translations.js:19
msgid "Are you sure you want to restart pyLoad?"
msgstr ""
-#: module/web/pyload_app.py:125
-msgid "You dont have permission to access this page."
-msgstr ""
-
-#: module/web/pyload_app.py:193
-msgid "Download directory not found."
-msgstr ""
-
-#: module/web/pyload_app.py:260 module/web/pyload_app.py:267
-msgid "unlimited"
-msgstr ""
-
-#: module/web/pyload_app.py:262 module/web/pyload_app.py:269
-msgid "not available"
-msgstr ""
-
-#: module/web/pyload_app.py:509
-msgid "Run pyLoadCore.py -s to access the setup."
-msgstr ""
-
-#: module/web/json_app.py:60
+#: pyload/webui/app/json.py:57
#, python-format
msgid "waiting %s"
msgstr ""
-#: module/web/templates/default/info.html:14
-#: module/web/templates/default/info.html:15
-#: module/web/templates/default/home.html:239
-msgid "Information"
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
msgstr ""
-#: module/web/templates/default/info.html:18
-msgid "News"
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
msgstr ""
-#: module/web/templates/default/info.html:21
-msgid "Support"
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
msgstr ""
-#: module/web/templates/default/info.html:37
-msgid "System"
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
msgstr ""
-#: module/web/templates/default/info.html:40
-msgid "Python:"
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
msgstr ""
-#: module/web/templates/default/info.html:44
-msgid "OS:"
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
msgstr ""
-#: module/web/templates/default/info.html:48
-msgid "pyLoad version:"
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
msgstr ""
-#: module/web/templates/default/info.html:52
-msgid "Installation Folder:"
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
msgstr ""
-#: module/web/templates/default/info.html:56
-msgid "Config Folder:"
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
msgstr ""
-#: module/web/templates/default/info.html:60
-msgid "Download Folder:"
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
msgstr ""
-#: module/web/templates/default/info.html:64
-msgid "Free Space:"
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
msgstr ""
-#: module/web/templates/default/info.html:68
-msgid "Language:"
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
msgstr ""
-#: module/web/templates/default/info.html:72
-msgid "Webinterface Port:"
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
msgstr ""
-#: module/web/templates/default/info.html:76
-msgid "Remote Interface Port:"
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
msgstr ""
-#: module/web/templates/default/downloads.html:6
-#: module/web/templates/default/base.html:93
-#: module/web/templates/default/home.html:220
-msgid "Downloads"
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
msgstr ""
-#: module/web/templates/default/filemanager.html:19
-msgid "FileManager"
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
msgstr ""
-#: module/web/templates/default/admin.html:8
-#: module/web/templates/default/admin.html:9
-#: module/web/templates/default/base.html:59
-msgid "Administrate"
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
msgstr ""
-#: module/web/templates/default/admin.html:13
-msgid "Quit pyLoad"
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
msgstr ""
-#: module/web/templates/default/admin.html:14
-msgid "Restart pyLoad"
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
msgstr ""
-#: module/web/templates/default/admin.html:18
-msgid "To add user or change passwords use:"
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
msgstr ""
-#: module/web/templates/default/admin.html:19
-msgid "Important: Admin user have always all permissions!"
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
msgstr ""
-#: module/web/templates/default/admin.html:25
-#: module/web/templates/default/settings.html:91
-#: module/web/templates/default/queue.html:82
-#: module/web/templates/default/window.html:7
-#: module/web/templates/default/home.html:237
-msgid "Name"
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
msgstr ""
-#: module/web/templates/default/admin.html:28
-#: module/web/templates/default/admin.html:67
-msgid "Change Password"
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
msgstr ""
-#: module/web/templates/default/admin.html:31
-msgid "Admin"
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
msgstr ""
-#: module/web/templates/default/admin.html:34
-msgid "Permissions"
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
msgstr ""
-#: module/web/templates/default/admin.html:41
-msgid "change"
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
msgstr ""
-#: module/web/templates/default/admin.html:61
-#: module/web/templates/default/admin.html:91
-#: module/web/templates/default/settings.html:167
-#: module/web/templates/default/queue.html:97
-#: module/web/templates/default/captcha.html:33
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
msgid "Submit"
msgstr ""
-#: module/web/templates/default/admin.html:69
-msgid "Enter your current and desired Password."
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
msgstr ""
-#: module/web/templates/default/admin.html:70
-msgid "User"
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
msgstr ""
-#: module/web/templates/default/admin.html:71
-#: module/web/templates/default/settings.html:179
-msgid "Your username."
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
msgstr ""
-#: module/web/templates/default/admin.html:75
-msgid "Current password"
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
msgstr ""
-#: module/web/templates/default/admin.html:76
-#: module/web/templates/default/settings.html:184
-msgid "The password for this account."
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
msgstr ""
-#: module/web/templates/default/admin.html:80
-msgid "New password"
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
msgstr ""
-#: module/web/templates/default/admin.html:81
-msgid "The new password."
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
msgstr ""
-#: module/web/templates/default/admin.html:85
-msgid "New password (repeat)"
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
msgstr ""
-#: module/web/templates/default/admin.html:86
-msgid "Please repeat the new password."
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
msgstr ""
-#: module/web/templates/default/admin.html:92
-#: module/web/templates/default/settings.html:198
-#: module/web/templates/default/queue.html:98
-#: module/web/templates/default/window.html:41
-msgid "Reset"
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
msgstr ""
-#: module/web/templates/default/settings.html:3
-#: module/web/templates/default/settings.html:4
-#: module/web/templates/default/base.html:102
-#: module/web/templates/default/home.html:229
-msgid "Config"
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
msgstr ""
-#: module/web/templates/default/settings.html:16
+#: pyload/webui/themes/default/tml/settings.html:16
msgid "General"
msgstr ""
-#: module/web/templates/default/settings.html:17
+#: pyload/webui/themes/default/tml/settings.html:17
msgid "Plugins"
msgstr ""
-#: module/web/templates/default/settings.html:18
+#: pyload/webui/themes/default/tml/settings.html:18
msgid "Accounts"
msgstr ""
-#: module/web/templates/default/settings.html:45
-#: module/web/templates/default/settings.html:74
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
msgid "Choose a section from the menu"
msgstr ""
-#: module/web/templates/default/settings.html:90
+#: pyload/webui/themes/default/tml/settings.html:90
msgid "Plugin"
msgstr ""
-#: module/web/templates/default/settings.html:92
-#: module/web/templates/default/settings.html:183
-#: module/web/templates/default/login.html:19
-#: module/web/templates/default/queue.html:92
-#: module/web/templates/default/window.html:21
-msgid "Password"
-msgstr ""
-
-#: module/web/templates/default/settings.html:93
-#: module/web/templates/default/home.html:238
-msgid "Status"
-msgstr ""
-
-#: module/web/templates/default/settings.html:94
+#: pyload/webui/themes/default/tml/settings.html:94
msgid "Premium"
msgstr ""
-#: module/web/templates/default/settings.html:95
+#: pyload/webui/themes/default/tml/settings.html:95
msgid "Valid until"
msgstr ""
-#: module/web/templates/default/settings.html:96
+#: pyload/webui/themes/default/tml/settings.html:96
msgid "Traffic left"
msgstr ""
-#: module/web/templates/default/settings.html:97
+#: pyload/webui/themes/default/tml/settings.html:97
msgid "Time"
msgstr ""
-#: module/web/templates/default/settings.html:98
+#: pyload/webui/themes/default/tml/settings.html:98
msgid "Max Parallel"
msgstr ""
-#: module/web/templates/default/settings.html:99
+#: pyload/webui/themes/default/tml/settings.html:99
msgid "Delete?"
msgstr ""
-#: module/web/templates/default/settings.html:121
+#: pyload/webui/themes/default/tml/settings.html:121
msgid "valid"
msgstr ""
-#: module/web/templates/default/settings.html:124
+#: pyload/webui/themes/default/tml/settings.html:124
msgid "not valid"
msgstr ""
-#: module/web/templates/default/settings.html:131
+#: pyload/webui/themes/default/tml/settings.html:131
msgid "yes"
msgstr ""
-#: module/web/templates/default/settings.html:134
+#: pyload/webui/themes/default/tml/settings.html:134
msgid "no"
msgstr ""
-#: module/web/templates/default/settings.html:168
-#: module/web/templates/default/settings.html:197
-#: module/web/templates/default/base.html:117
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
msgid "Add"
msgstr ""
-#: module/web/templates/default/settings.html:176
+#: pyload/webui/themes/default/tml/settings.html:176
msgid "Add Account"
msgstr ""
-#: module/web/templates/default/settings.html:177
+#: pyload/webui/themes/default/tml/settings.html:177
msgid "Enter your account data to use premium features."
msgstr ""
-#: module/web/templates/default/settings.html:178
-#: module/web/templates/default/login.html:3
-msgid "Login"
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
msgstr ""
-#: module/web/templates/default/settings.html:188
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
msgid "Type"
msgstr ""
-#: module/web/templates/default/settings.html:189
+#: pyload/webui/themes/default/tml/settings.html:189
msgid "Choose the hoster for your account."
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "Path"
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "absolute"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "relative"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
msgstr ""
-#: module/web/templates/default/pathchooser.html:46
-msgid "name"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
msgstr ""
-#: module/web/templates/default/pathchooser.html:47
-msgid "size"
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
msgstr ""
-#: module/web/templates/default/pathchooser.html:48
-msgid "type"
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
msgstr ""
-#: module/web/templates/default/pathchooser.html:49
-msgid "last modified"
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
msgstr ""
-#: module/web/templates/default/pathchooser.html:54
-msgid "parent directory"
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
msgstr ""
-#: module/web/templates/default/pathchooser.html:70
-msgid "no content"
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
msgstr ""
-#: module/web/templates/default/setup.html:3
-#: module/web/templates/default/setup.html:4
-msgid "Setup"
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
msgstr ""
-#: module/web/templates/default/login.html:14
-msgid "Username"
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
msgstr ""
-#: module/web/templates/default/login.html:29
-msgid "Your username and password didn't match. Please try again."
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
msgstr ""
-#: module/web/templates/default/login.html:30
-msgid "To reset your login data or add an user run:"
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
msgstr ""
-#: module/web/templates/default/base.html:20
-#: module/web/templates/default/base.html:139
-msgid "Webinterface"
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
msgstr ""
-#: module/web/templates/default/base.html:39
-msgid "pyLoad Update available!"
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
msgstr ""
-#: module/web/templates/default/base.html:46
-msgid "Plugins updated, please restart!"
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
msgstr ""
-#: module/web/templates/default/base.html:52
-msgid "Captcha waiting"
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
msgstr ""
-#: module/web/templates/default/base.html:57
-msgid "Logout"
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
msgstr ""
-#: module/web/templates/default/base.html:61
-msgid "Info"
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
msgstr ""
-#: module/web/templates/default/base.html:65
-msgid "Please Login!"
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
msgstr ""
-#: module/web/templates/default/base.html:84
-#: module/web/templates/default/home.html:211
-msgid "Home"
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
msgstr ""
-#: module/web/templates/default/base.html:87
-#: module/web/templates/default/queue.html:15
-#: module/web/templates/default/window.html:34
-#: module/web/templates/default/home.html:214
-msgid "Queue"
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
msgstr ""
-#: module/web/templates/default/base.html:90
-#: module/web/templates/default/queue.html:17
-#: module/web/templates/default/window.html:36
-#: module/web/templates/default/home.html:217
-msgid "Collector"
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
msgstr ""
-#: module/web/templates/default/base.html:99
-#: module/web/templates/default/logs.html:3
-#: module/web/templates/default/logs.html:4
-#: module/web/templates/default/home.html:226
-msgid "Logs"
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
msgstr ""
-#: module/web/templates/default/base.html:114
-#: module/web/templates/default/logs.html:12
-msgid "Start"
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
msgstr ""
-#: module/web/templates/default/base.html:115
-msgid "Stop"
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
msgstr ""
-#: module/web/templates/default/base.html:116
-msgid "Cancel"
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
msgstr ""
-#: module/web/templates/default/base.html:123
-msgid "Download:"
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
msgstr ""
-#: module/web/templates/default/base.html:124
-msgid "Reconnect:"
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
msgstr ""
-#: module/web/templates/default/base.html:125
-msgid "Speed:"
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
msgstr ""
-#: module/web/templates/default/base.html:126
-msgid "Active:"
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
msgstr ""
-#: module/web/templates/default/base.html:127
-msgid "Reload page"
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
msgstr ""
-#: module/web/templates/default/base.html:157
-msgid "loading"
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
msgstr ""
-#: module/web/templates/default/base.html:166
-msgid "Back to top"
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "prev"
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "next"
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "End"
+#: pyload/webui/themes/default/tml/base.html:39
+#, python-format
+msgid "New pyLoad version %s available!"
msgstr ""
-#: module/web/templates/default/logout.html:8
-msgid "You were successfully logged out."
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
msgstr ""
-#: module/web/templates/default/queue.html:25
-msgid "Delete Finished"
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
msgstr ""
-#: module/web/templates/default/queue.html:26
-msgid "Restart Failed"
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
msgstr ""
-#: module/web/templates/default/queue.html:65
-msgid "Folder:"
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
msgstr ""
-#: module/web/templates/default/queue.html:65
-msgid "Password:"
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
msgstr ""
-#: module/web/templates/default/queue.html:79
-msgid "Edit Package"
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
msgstr ""
-#: module/web/templates/default/queue.html:80
-msgid "Edit the package detais below."
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
msgstr ""
-#: module/web/templates/default/queue.html:83
-msgid "The name of the package."
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
msgstr ""
-#: module/web/templates/default/queue.html:87
-msgid "Folder"
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
msgstr ""
-#: module/web/templates/default/queue.html:88
-msgid "Name of subfolder for these downloads."
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
msgstr ""
-#: module/web/templates/default/queue.html:93
-msgid "List of passwords used for unrar."
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
msgstr ""
-#: module/web/templates/default/window.html:5
-#: module/web/templates/default/window.html:40
-msgid "Add Package"
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
msgstr ""
-#: module/web/templates/default/window.html:6
-msgid "Paste your links or upload a container."
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
msgstr ""
-#: module/web/templates/default/window.html:8
-msgid "The name of the new package."
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
msgstr ""
-#: module/web/templates/default/window.html:12
-msgid "Links"
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
msgstr ""
-#: module/web/templates/default/window.html:13
-msgid "Paste your links here or any text and press the filter button."
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
msgstr ""
-#: module/web/templates/default/window.html:14
-msgid "Filter urls"
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
msgstr ""
-#: module/web/templates/default/window.html:22
-msgid "Password for RAR-Archive"
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
msgstr ""
-#: module/web/templates/default/window.html:26
-msgid "File"
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
msgstr ""
-#: module/web/templates/default/window.html:27
-msgid "Upload a container."
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
msgstr ""
-#: module/web/templates/default/window.html:31
-msgid "Destination"
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
msgstr ""
-#: module/web/templates/default/home.html:206
-msgid "Active Downloads"
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
msgstr ""
-#: module/web/templates/default/home.html:240
-msgid "Size"
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
msgstr ""
-#: module/web/templates/default/home.html:241
-msgid "Progress"
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
msgstr ""
-#: module/web/templates/default/captcha.html:6
-msgid "Captcha reading"
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
msgstr ""
-#: module/web/templates/default/captcha.html:13
-msgid "Captcha"
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
msgstr ""
-#: module/web/templates/default/captcha.html:14
-msgid "The captcha."
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
msgstr ""
-#: module/web/templates/default/captcha.html:20
-msgid "Text"
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
msgstr ""
-#: module/web/templates/default/captcha.html:21
-msgid "Input the text on the captcha."
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
msgstr ""
-#: module/web/templates/default/captcha.html:34
-msgid "Close"
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:127
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:195
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:264 pyload/webui/app/pyload.py:271
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:509
+msgid "Run pyload.py -s to access the setup."
msgstr ""
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoad.mo b/locale/en/LC_MESSAGES/pyLoad.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoadCli.mo b/locale/en/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoadGui.mo b/locale/en/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/setup.mo b/locale/en/LC_MESSAGES/setup.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo
deleted file mode 100644
index 899597735..000000000
--- a/locale/es/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/pyLoad.mo b/locale/es/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 90aa70f1d..000000000
--- a/locale/es/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/pyLoadCli.mo b/locale/es/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index f969ad0d1..000000000
--- a/locale/es/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/pyLoadGui.mo b/locale/es/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index a4944783f..000000000
--- a/locale/es/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/setup.mo b/locale/es/LC_MESSAGES/setup.mo
deleted file mode 100644
index 87c4dd811..000000000
--- a/locale/es/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/pyLoad.mo b/locale/fi/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/fi/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/pyLoadCli.mo b/locale/fi/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/fi/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/pyLoadGui.mo b/locale/fi/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/fi/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo
deleted file mode 100644
index b7b2a3fb5..000000000
--- a/locale/fr/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/pyLoad.mo b/locale/fr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 95fd73cae..000000000
--- a/locale/fr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/pyLoadCli.mo b/locale/fr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 460641599..000000000
--- a/locale/fr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/pyLoadGui.mo b/locale/fr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 001412ddf..000000000
--- a/locale/fr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/setup.mo b/locale/fr/LC_MESSAGES/setup.mo
deleted file mode 100644
index 89cb21969..000000000
--- a/locale/fr/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/gui.pot b/locale/gui.pot
deleted file mode 100644
index dc74397a0..000000000
--- a/locale/gui.pot
+++ /dev/null
@@ -1,511 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR pyLoad Team
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
-"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: pyLoadGui.py:290
-msgid "paused"
-msgstr ""
-
-#: pyLoadGui.py:292
-msgid "running"
-msgstr ""
-
-#: pyLoadGui.py:332
-msgid "Unnamed"
-msgstr ""
-
-#: pyLoadGui.py:655
-#, python-format
-msgid "Finished downloading of '%s'"
-msgstr ""
-
-#: pyLoadGui.py:657
-#, python-format
-msgid "Failed downloading '%s'!"
-msgstr ""
-
-#: pyLoadGui.py:660
-#, python-format
-msgid "Added '%s' to queue"
-msgstr ""
-
-#: pyLoadGui.py:685
-msgid "Connection lost"
-msgstr ""
-
-#: pyLoadGui.py:685
-msgid "Lost connection to the core!"
-msgstr ""
-
-#: pyLoadGui.py:720
-msgid "Show"
-msgstr ""
-
-#: pyLoadGui.py:725 module/gui/MainWindow.py:133
-msgid "Exit"
-msgstr ""
-
-#: module/gui/Connector.py:76
-msgid "bad login credentials"
-msgstr ""
-
-#: module/gui/Connector.py:78
-msgid "no ssl support"
-msgstr ""
-
-#: module/gui/Connector.py:80
-msgid "can't connect to host"
-msgstr ""
-
-#: module/gui/Connector.py:94
-#, python-format
-msgid "server is version %(new)s client accepts version %(current)s"
-msgstr ""
-
-#: module/gui/Collector.py:47
-msgid "finished"
-msgstr ""
-
-#: module/gui/Collector.py:48
-msgid "offline"
-msgstr ""
-
-#: module/gui/Collector.py:49
-msgid "online"
-msgstr ""
-
-#: module/gui/Collector.py:50
-msgid "queued"
-msgstr ""
-
-#: module/gui/Collector.py:51
-msgid "skipped"
-msgstr ""
-
-#: module/gui/Collector.py:52
-msgid "waiting"
-msgstr ""
-
-#: module/gui/Collector.py:53
-msgid "temp. offline"
-msgstr ""
-
-#: module/gui/Collector.py:54
-msgid "starting"
-msgstr ""
-
-#: module/gui/Collector.py:55
-msgid "failed"
-msgstr ""
-
-#: module/gui/Collector.py:56
-msgid "aborted"
-msgstr ""
-
-#: module/gui/Collector.py:57
-msgid "decrypting"
-msgstr ""
-
-#: module/gui/Collector.py:58
-msgid "custom"
-msgstr ""
-
-#: module/gui/Collector.py:59
-msgid "downloading"
-msgstr ""
-
-#: module/gui/Collector.py:60
-msgid "processing"
-msgstr ""
-
-#: module/gui/Collector.py:284 module/gui/PackageDock.py:66
-#: module/gui/Queue.py:152
-msgid "Name"
-msgstr ""
-
-#: module/gui/Collector.py:286 module/gui/Queue.py:156
-msgid "Plugin"
-msgstr ""
-
-#: module/gui/Collector.py:288 module/gui/Queue.py:154
-msgid "Status"
-msgstr ""
-
-#: module/gui/Collector.py:290 module/gui/Queue.py:158
-msgid "Size"
-msgstr ""
-
-#: module/gui/Accounts.py:75
-msgid "not valid"
-msgstr ""
-
-#: module/gui/Accounts.py:77 module/gui/Accounts.py:207
-msgid "n/a"
-msgstr ""
-
-#: module/gui/Accounts.py:80
-msgid "%a, %d %b %Y %H:%M"
-msgstr ""
-
-#: module/gui/Accounts.py:83 module/gui/Accounts.py:205
-msgid "unlimited"
-msgstr ""
-
-#: module/gui/Accounts.py:138 module/gui/AccountEdit.py:38
-msgid "Type"
-msgstr ""
-
-#: module/gui/Accounts.py:140 module/gui/AccountEdit.py:39
-msgid "Login"
-msgstr ""
-
-#: module/gui/Accounts.py:142
-msgid "Valid until"
-msgstr ""
-
-#: module/gui/Accounts.py:144
-msgid "Traffic left"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:74
-msgid "General"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:75
-msgid "Plugins"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:89
-msgid "Reload"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:90 module/gui/ConnectionManager.py:206
-#: module/gui/AccountEdit.py:53
-msgid "Save"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:192
-msgid "Yes"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:193
-msgid "No"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:42 module/gui/ConnectionManager.py:185
-msgid "pyLoad ConnectionManager"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:47
-msgid "New"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:48 module/gui/MainWindow.py:324
-msgid "Edit"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:49 module/gui/MainWindow.py:275
-#: module/gui/MainWindow.py:295 module/gui/MainWindow.py:323
-msgid "Remove"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:50
-msgid "Connect"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:56
-msgid "Connect:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:73
-msgid "Use internal Core:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:117
-#, python-format
-msgid "%s (Default)"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:190
-msgid "Name:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:191
-msgid "Host:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:192
-msgid "Local:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:193
-msgid "User:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:194
-msgid "Password:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:195
-msgid "Port:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:207 module/gui/CaptchaDock.py:65
-msgid "Cancel"
-msgstr ""
-
-#: module/gui/MainWindow.py:44
-msgid "pyLoad Client"
-msgstr ""
-
-#: module/gui/MainWindow.py:92
-msgid "Packages:"
-msgstr ""
-
-#: module/gui/MainWindow.py:96
-msgid "Files:"
-msgstr ""
-
-#: module/gui/MainWindow.py:100
-msgid "Status:"
-msgstr ""
-
-#: module/gui/MainWindow.py:104
-msgid "Space:"
-msgstr ""
-
-#: module/gui/MainWindow.py:108
-msgid "Speed:"
-msgstr ""
-
-#: module/gui/MainWindow.py:129
-msgid "File"
-msgstr ""
-
-#: module/gui/MainWindow.py:130
-msgid "Connections"
-msgstr ""
-
-#: module/gui/MainWindow.py:134
-msgid "Connection manager"
-msgstr ""
-
-#: module/gui/MainWindow.py:156
-msgid "Overview"
-msgstr ""
-
-#: module/gui/MainWindow.py:157
-msgid "Queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:158
-msgid "Collector"
-msgstr ""
-
-#: module/gui/MainWindow.py:159
-msgid "Accounts"
-msgstr ""
-
-#: module/gui/MainWindow.py:160
-msgid "Settings"
-msgstr ""
-
-#: module/gui/MainWindow.py:161
-msgid "Log"
-msgstr ""
-
-#: module/gui/MainWindow.py:190
-msgid "Hide Toolbar"
-msgstr ""
-
-#: module/gui/MainWindow.py:194
-msgid "Toggle Pause/Resume"
-msgstr ""
-
-#: module/gui/MainWindow.py:200
-msgid "Stop"
-msgstr ""
-
-#: module/gui/MainWindow.py:202 module/gui/MainWindow.py:302
-#: module/gui/MainWindow.py:322
-msgid "Add"
-msgstr ""
-
-#: module/gui/MainWindow.py:204
-msgid "Check Clipboard"
-msgstr ""
-
-#: module/gui/MainWindow.py:211 module/gui/MainWindow.py:308
-#: module/gui/Overview.py:101
-msgid "Package"
-msgstr ""
-
-#: module/gui/MainWindow.py:212 module/gui/MainWindow.py:309
-msgid "Container"
-msgstr ""
-
-#: module/gui/MainWindow.py:213
-msgid "Account"
-msgstr ""
-
-#: module/gui/MainWindow.py:214 module/gui/MainWindow.py:310
-msgid "Links"
-msgstr ""
-
-#: module/gui/MainWindow.py:238
-msgid "Push selected packages to queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:261
-msgid "New Account"
-msgstr ""
-
-#: module/gui/MainWindow.py:276 module/gui/MainWindow.py:298
-msgid "Restart"
-msgstr ""
-
-#: module/gui/MainWindow.py:277
-msgid "Pull out"
-msgstr ""
-
-#: module/gui/MainWindow.py:278
-msgid "Abort"
-msgstr ""
-
-#: module/gui/MainWindow.py:279 module/gui/MainWindow.py:297
-msgid "Edit Name"
-msgstr ""
-
-#: module/gui/MainWindow.py:296
-msgid "Push to queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:299
-msgid "Refresh Status"
-msgstr ""
-
-#: module/gui/MainWindow.py:402
-#, python-format
-msgid "All Container Types (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:403
-#, python-format
-msgid "DLC (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:404
-#, python-format
-msgid "CCF (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:405
-#, python-format
-msgid "RSDF (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:406
-#, python-format
-msgid "Text Files (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:408
-msgid "Open container"
-msgstr ""
-
-#: module/gui/PackageDock.py:25
-msgid "New Package"
-msgstr ""
-
-#: module/gui/PackageDock.py:68
-msgid "Password"
-msgstr ""
-
-#: module/gui/PackageDock.py:71
-msgid "Links in this Package"
-msgstr ""
-
-#: module/gui/PackageDock.py:77
-msgid "Create"
-msgstr ""
-
-#: module/gui/PackageDock.py:78
-msgid "Filter URLs"
-msgstr ""
-
-#: module/gui/CaptchaDock.py:29
-msgid "Captcha"
-msgstr ""
-
-#: module/gui/CaptchaDock.py:64
-msgid "OK"
-msgstr ""
-
-#: module/gui/AccountEdit.py:32
-msgid "Edit account"
-msgstr ""
-
-#: module/gui/AccountEdit.py:40
-msgid "New password"
-msgstr ""
-
-#: module/gui/AccountEdit.py:83
-msgid "Create account"
-msgstr ""
-
-#: module/gui/Queue.py:160
-msgid "ETA"
-msgstr ""
-
-#: module/gui/Queue.py:162
-msgid "Progress"
-msgstr ""
-
-#: module/gui/Queue.py:384
-#, python-format
-msgid "waiting %d seconds"
-msgstr ""
-
-#: module/gui/Overview.py:71 module/gui/Overview.py:152
-msgid "Downloading"
-msgstr ""
-
-#: module/gui/Overview.py:83
-msgid "Queued"
-msgstr ""
-
-#: module/gui/Overview.py:147
-msgid "ETA: "
-msgstr ""
-
-#: module/gui/Overview.py:149
-msgid "Parts: "
-msgstr ""
-
-#: module/gui/Overview.py:151
-msgid "Finished"
-msgstr ""
-
-#: module/gui/Overview.py:155
-#, python-format
-msgid "Speed: %s"
-msgstr ""
-
-#: module/gui/Overview.py:158 module/gui/Overview.py:160
-msgid "Size:"
-msgstr ""
diff --git a/locale/it/LC_MESSAGES/django.mo b/locale/it/LC_MESSAGES/django.mo
deleted file mode 100644
index 57858c8a6..000000000
--- a/locale/it/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/pyLoad.mo b/locale/it/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 07b1c01c5..000000000
--- a/locale/it/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/pyLoadCli.mo b/locale/it/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 158fd317a..000000000
--- a/locale/it/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/pyLoadGui.mo b/locale/it/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index cd1cbdf73..000000000
--- a/locale/it/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/setup.mo b/locale/it/LC_MESSAGES/setup.mo
deleted file mode 100644
index e13b99bc9..000000000
--- a/locale/it/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/django.mo b/locale/nl/LC_MESSAGES/django.mo
deleted file mode 100644
index bea13751c..000000000
--- a/locale/nl/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/pyLoad.mo b/locale/nl/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 7c52dee71..000000000
--- a/locale/nl/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/pyLoadCli.mo b/locale/nl/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index c414cfea4..000000000
--- a/locale/nl/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/pyLoadGui.mo b/locale/nl/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 8bbc9385f..000000000
--- a/locale/nl/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/setup.mo b/locale/nl/LC_MESSAGES/setup.mo
deleted file mode 100644
index 8d8fd0d4f..000000000
--- a/locale/nl/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pavement.py b/locale/pavement.py
new file mode 100644
index 000000000..03acf3dcc
--- /dev/null
+++ b/locale/pavement.py
@@ -0,0 +1,428 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from paver.easy import *
+from paver.setuputils import setup
+from paver.doctools import cog
+
+import os
+import sys
+import shutil
+import re
+from glob import glob
+from tempfile import mkdtemp
+from urllib import urlretrieve
+from subprocess import call, Popen, PIPE
+from zipfile import ZipFile
+
+PROJECT_DIR = path(__file__).dirname()
+sys.path.append(PROJECT_DIR)
+
+options = environment.options
+path("pyload").mkdir()
+
+extradeps = []
+if sys.version_info <= (2, 5):
+ extradeps += 'simplejson'
+
+setup(
+ name="pyload",
+ version="0.4.10",
+ description='Fast, lightweight and full featured download manager.',
+ long_description=open(PROJECT_DIR / "README.md").read(),
+ keywords = ("pyload", "download-manager", "one-click-hoster", "download"),
+ url="http://pyload.org",
+ download_url='http://pyload.org/download',
+ license='GPL v3',
+ author="pyLoad Team",
+ author_email="support@pyload.org",
+ platforms = ('Any',),
+ # package_dir={'pyload': "src"},
+ packages=["pyload"],
+ # package_data=find_package_data(),
+ # data_files=[],
+ include_package_data=True,
+ exclude_package_data={'pyload': ["docs*", "scripts*", "tests*"]}, #: exluced from build but not from sdist
+ # 'bottle >= 0.10.0' not in list, because its small and contain little modifications
+ install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker', 'BeautifulSoup >= 3.2, < 3.3'] + extradeps,
+ extras_require={
+ 'SSL': ["pyOpenSSL"],
+ 'DLC': ['pycrypto'],
+ 'lightweight webserver': ['bjoern'],
+ 'RSS plugins': ['feedparser'],
+ },
+ # setup_requires=["setuptools_hg"],
+ entry_points={
+ 'console_scripts': [
+ 'pyLoadCore = pyLoadCore:main',
+ 'pyLoadCli = pyLoadCli:main'
+ ]},
+ zip_safe=False,
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Topic :: Internet :: WWW/HTTP",
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2"
+ ]
+)
+
+options(
+ sphinx=Bunch(
+ builddir="_build",
+ sourcedir=""
+ ),
+ get_source=Bunch(
+ src="https://bitbucket.org/spoob/pyload/get/tip.zip",
+ rev=None,
+ clean=False
+ ),
+ thrift=Bunch(
+ path="../thrift/trunk/compiler/cpp/thrift",
+ gen=""
+ ),
+ virtualenv=Bunch(
+ dir="env",
+ python="python2",
+ virtual="virtualenv2",
+ ),
+ cog=Bunch(
+ pattern="*.py",
+ )
+)
+
+# xgettext args
+xargs = ["--language=Python", "--add-comments=L10N",
+ "--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyLoad",
+ "--package-version=%s" % options.version, "--msgid-bugs-address='bugs@pyload.org'"]
+
+
+@task
+@needs('cog')
+def html():
+ """Build html documentation"""
+ module = path("docs") / "pyload"
+ pyload.rmtree()
+ call_task('paver.doctools.html')
+
+
+@task
+@cmdopts([
+ ('src=', 's', 'Url to source'),
+ ('rev=', 'r', "HG revision"),
+ ("clean", 'c', 'Delete old source folder')
+])
+
+
+def get_source(options):
+ """ Downloads pyload source from bitbucket tip or given rev"""
+ if options.rev: options.url = "https://bitbucket.org/spoob/pyload/get/%s.zip" % options.rev
+
+ pyload = path("pyload")
+
+ if len(pyload.listdir()) and not options.clean:
+ return
+ elif pyload.exists():
+ pyload.rmtree()
+
+ urlretrieve(options.src, "pyload_src.zip")
+ zip = ZipFile("pyload_src.zip")
+ zip.extractall()
+ path("pyload_src.zip").remove()
+
+ folder = [x for x in path(".").dirs() if x.name.startswith("spoob-pyload-")][0]
+ folder.move(pyload)
+
+ change_mode(pyload, 0644)
+ change_mode(pyload, 0755, folder=True)
+
+ for file in pyload.files():
+ if file.name.endswith(".py"):
+ file.chmod(0755)
+
+ (pyload / ".hgtags").remove()
+ (pyload / ".gitignore").remove()
+ #(pyload / "docs").rmtree()
+
+ f = open(pyload / "__init__.py", "wb")
+ f.close()
+
+ # options.setup.packages = find_packages()
+ # options.setup.package_data = find_package_data()
+
+
+@task
+@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist')
+def sdist():
+ """ Build source code package with distutils """
+
+
+@task
+@cmdopts([
+ ('path=', 'p', 'Thrift path'),
+ ('gen=', 'g', "Extra --gen option")
+])
+
+
+def thrift(options):
+ """ Generate Thrift stubs """
+
+ print "add import for TApplicationException manually as long it is not fixed"
+
+ outdir = path("pyload") / "remote" / "thriftbackend"
+ (outdir / "gen-py").rmtree()
+
+ cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots, dynamic", outdir / "pyload.thrift"]
+
+ if options.gen:
+ cmd.insert(len(cmd) - 1, "--gen")
+ cmd.insert(len(cmd) - 1, options.gen)
+
+ print "running", cmd
+
+ p = Popen(cmd)
+ p.communicate()
+
+ (outdir / "thriftgen").rmtree()
+ (outdir / "gen-py").move(outdir / "thriftgen")
+
+ # create light ttypes
+ from pyload.remote.socketbackend.create_ttypes import main
+ main()
+
+
+@task
+def compile_js():
+ """ Compile .coffee files to javascript"""
+
+ root = path("pyload") / "web" / "media" / "js"
+ for f in root.glob("*.coffee"):
+ print "generate", f
+ coffee = Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE)
+ yui = Popen(["yuicompressor", "--type", "js"], stdin=coffee.stdout, stdout=PIPE)
+ coffee.stdout.close()
+ content = yui.communicate()[0]
+ with open(root / f.name.replace(".coffee", ".js"), "wb") as js:
+ js.write("{% autoescape true %}\n")
+ js.write(content)
+ js.write("\n{% endautoescape %}")
+
+
+@task
+def generate_locale():
+ """ Generates localization files """
+
+ EXCLUDE = ["BeautifulSoup.py", "pyload/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload",
+ "setup.py"]
+ makepot("core", path("pyload"), EXCLUDE, "./pyload.py\n")
+
+ makepot("cli", path("pyload") / "cli", [], includes="./pyload-cli.py\n")
+ makepot("setup", "", [], includes="./pyload/setup.py\n")
+
+ EXCLUDE = ["ServerThread.py", "web/media/default"]
+
+ # strings from js files
+ strings = set()
+
+ for fi in path("pyload/web").walkfiles():
+ if not fi.name.endswith(".js") and not fi.endswith(".coffee"):
+ continue
+ with open(fi, "rb") as c:
+ content = c.read()
+
+ strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content))
+ strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content))
+
+ trans = path("pyload") / "web" / "translations.js"
+
+ with open(trans, "wb") as js:
+ for s in strings:
+ js.write('_("%s")\n' % s)
+
+ makepot("django", path("pyload/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"])
+
+ trans.remove()
+
+ path("includes.txt").remove()
+
+ print "Locale generated"
+
+
+@task
+@cmdopts([
+ ('key=', 'k', 'api key')
+])
+
+
+def upload_translations(options):
+ """ Uploads the locale files to translation server """
+ tmp = path(mkdtemp())
+
+ shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ with open(config, 'rb') as f:
+ content = f.read()
+ content = content.format(key=options.key, tmp=tmp)
+ with open(config, 'wb') as f:
+ f.write(content)
+
+ call(['crowdin-cli', '-c', config, 'upload', 'source'])
+
+ shutil.rmtree(tmp)
+
+ print "Translations uploaded"
+
+
+@task
+@cmdopts([
+ ('key=', 'k', 'api key')
+])
+
+
+def download_translations(options):
+ """ Downloads the translated files from translation server """
+ tmp = path(mkdtemp())
+
+ shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ with open(config, 'rb') as f:
+ content = f.read()
+ content = content.format(key=options.key, tmp=tmp)
+ with open(config, 'wb') as f:
+ f.write(content)
+
+ call(['crowdin-cli', '-c', config, 'download'])
+
+ for language in (tmp / 'pyLoad').listdir():
+ if not language.isdir():
+ continue
+
+ target = path('locale') / language.basename()
+ print "Copy language %s" % target
+ if target.exists():
+ shutil.rmtree(target)
+
+ shutil.copytree(language, target)
+
+ shutil.rmtree(tmp)
+
+
+@task
+def compile_translations():
+ """ Compile PO files to MO """
+ for language in path('locale').listdir():
+ if not language.isdir():
+ continue
+
+ for f in glob(language / 'LC_MESSAGES' / '*.po'):
+ print "Compiling %s" % f
+ call(['msgfmt', '-o', f.replace('.po', '.mo'), f])
+
+
+@task
+def tests():
+ call(["nosetests2"])
+
+
+@task
+def virtualenv(options):
+ """Setup virtual environment"""
+ if path(options.dir).exists():
+ return
+
+ call([options.virtual, "--no-site-packages", "--python", options.python, options.dir])
+ print "$ source %s/bin/activate" % options.dir
+
+
+@task
+def clean_env():
+ """Deletes the virtual environment"""
+ env = path(options.virtualenv.dir)
+ if env.exists():
+ env.rmtree()
+
+
+@task
+@needs('generate_setup', 'minilib', 'get_source', 'virtualenv')
+def env_install():
+ """Install pyLoad into the virtualenv"""
+ venv = options.virtualenv
+ call([path(venv.dir) / "bin" / "easy_install", "."])
+
+
+@task
+def clean():
+ """Cleans build directories"""
+ path("build").rmtree()
+ path("dist").rmtree()
+
+
+# helper functions
+
+
+def walk_trans(path, EXCLUDE, endings=[".py"]):
+ result = ""
+
+ for f in path.walkfiles():
+ if [True for x in EXCLUDE if x in f.dirname().relpath()]:
+ continue
+ if f.name in EXCLUDE:
+ continue
+
+ for e in endings:
+ if f.name.endswith(e):
+ result += "./%s\n" % f.relpath()
+ break
+
+ return result
+
+
+def makepot(domain, p, excludes=[], includes="", endings=[".py"], xxargs=[]):
+ print "Generate %s.pot" % domain
+
+ with open("includes.txt", "wb") as f:
+ if includes:
+ f.write(includes)
+
+ if p:
+ f.write(walk_trans(path(p), excludes, endings))
+
+ call(["xgettext", "--files-from=includes.txt", "--default-domain=%s" % domain] + xargs + xxargs)
+
+ # replace charset und move file
+ with open("%s.po" % domain, "rb") as f:
+ content = f.read()
+
+ path("%s.po" % domain).remove()
+ content = content.replace("charset=CHARSET", "charset=UTF-8")
+
+ with open("locale/%s.pot" % domain, "wb") as f:
+ f.write(content)
+
+
+def change_owner(dir, uid, gid):
+ for p in dir.walk():
+ p.chown(uid, gid)
+
+
+def change_mode(dir, mode, folder=False):
+ for p in dir.walk():
+ if folder and p.isdir():
+ p.chmod(mode)
+ elif p.isfile() and not folder:
+ p.chmod(mode)
diff --git a/locale/pl/LC_MESSAGES/django.mo b/locale/pl/LC_MESSAGES/django.mo
deleted file mode 100644
index fae662552..000000000
--- a/locale/pl/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/pyLoad.mo b/locale/pl/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 31ea1e162..000000000
--- a/locale/pl/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/pyLoadCli.mo b/locale/pl/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 565f26e3a..000000000
--- a/locale/pl/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/pyLoadGui.mo b/locale/pl/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index bbfcb176e..000000000
--- a/locale/pl/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/setup.mo b/locale/pl/LC_MESSAGES/setup.mo
deleted file mode 100644
index c8303602c..000000000
--- a/locale/pl/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/django.mo b/locale/pt_BR/LC_MESSAGES/django.mo
deleted file mode 100644
index 2fdf540b0..000000000
--- a/locale/pt_BR/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoad.mo b/locale/pt_BR/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2e8a1e5b3..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo b/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 2ca834196..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo b/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 28fe26252..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/setup.mo b/locale/pt_BR/LC_MESSAGES/setup.mo
deleted file mode 100644
index c64d0f23c..000000000
--- a/locale/pt_BR/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/pyLoad.mo b/locale/ro/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/ro/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/pyLoadCli.mo b/locale/ro/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/ro/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/pyLoadGui.mo b/locale/ro/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/ro/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/django.mo b/locale/ru/LC_MESSAGES/django.mo
deleted file mode 100644
index 0a1394f21..000000000
--- a/locale/ru/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/pyLoad.mo b/locale/ru/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 156a1bc44..000000000
--- a/locale/ru/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/pyLoadCli.mo b/locale/ru/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index adbb5e141..000000000
--- a/locale/ru/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/pyLoadGui.mo b/locale/ru/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 11d4328c0..000000000
--- a/locale/ru/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/setup.mo b/locale/ru/LC_MESSAGES/setup.mo
deleted file mode 100644
index fb1201628..000000000
--- a/locale/ru/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/setup.pot b/locale/setup.pot
index cf4dd8cfc..ceba6a5d2 100644
--- a/locale/setup.pot
+++ b/locale/setup.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,479 +17,463 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: module/setup.py:51
+#: pyload/setup.py:33
msgid "y"
msgstr ""
-#: module/setup.py:53
+#: pyload/setup.py:35
msgid "n"
msgstr ""
-#: module/setup.py:72
-msgid "Welcome to the pyLoad Configuration Assistent."
+#: pyload/setup.py:53
+msgid "Welcome to the pyLoad Configuration Assistant."
msgstr ""
-#: module/setup.py:73
+#: pyload/setup.py:54
msgid ""
"It will check your system and make a basic setup in order to run pyLoad."
msgstr ""
-#: module/setup.py:75
+#: pyload/setup.py:56
msgid "The value in brackets [] always is the default value,"
msgstr ""
-#: module/setup.py:76
+#: pyload/setup.py:57
msgid ""
"in case you don't want to change it or you are unsure what to choose, just "
"hit enter."
msgstr ""
-#: module/setup.py:77
+#: pyload/setup.py:59
msgid ""
-"Don't forget: You can always rerun this assistent with --setup or -s "
-"parameter, when you start pyLoadCore."
+"Don't forget: You can always rerun this assistant with --setup or -s "
+"parameter, when you start pyload.py ."
msgstr ""
-#: module/setup.py:78
-msgid "If you have any problems with this assistent hit STRG-C,"
+#: pyload/setup.py:60
+msgid "If you have any problems with this assistant hit STRG-C,"
msgstr ""
-#: module/setup.py:79
-msgid "to abort and don't let him start with pyLoadCore automatically anymore."
+#: pyload/setup.py:61
+msgid "to abort and don't let him start with pyload.py automatically anymore."
msgstr ""
-#: module/setup.py:81
+#: pyload/setup.py:63
msgid "When you are ready for system check, hit enter."
msgstr ""
-#: module/setup.py:88
+#: pyload/setup.py:70
msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
msgstr ""
-#: module/setup.py:89
+#: pyload/setup.py:71
msgid "Please correct this and re-run pyLoad."
msgstr ""
-#: module/setup.py:90
+#: pyload/setup.py:72
msgid "Setup will now close."
msgstr ""
-#: module/setup.py:94
+#: pyload/setup.py:76
msgid "System check finished, hit enter to see your status report."
msgstr ""
-#: module/setup.py:96
+#: pyload/setup.py:78
msgid "## Status ##"
msgstr ""
-#: module/setup.py:101
+#: pyload/setup.py:83
msgid "container decrypting"
msgstr ""
-#: module/setup.py:102
+#: pyload/setup.py:85
msgid "ssl connection"
msgstr ""
-#: module/setup.py:103
+#: pyload/setup.py:87
msgid "automatic captcha decryption"
msgstr ""
-#: module/setup.py:104
-msgid "GUI"
-msgstr ""
-
-#: module/setup.py:105
+#: pyload/setup.py:89
msgid "Webinterface"
msgstr ""
-#: module/setup.py:106
+#: pyload/setup.py:91
msgid "extended Click'N'Load"
msgstr ""
-#: module/setup.py:113
+#: pyload/setup.py:98
msgid "Features available:"
msgstr ""
-#: module/setup.py:117
+#: pyload/setup.py:102
msgid "Featues missing: "
msgstr ""
-#: module/setup.py:121
+#: pyload/setup.py:106
msgid "no py-crypto available"
msgstr ""
-#: module/setup.py:122
+#: pyload/setup.py:107
msgid "You need this if you want to decrypt container files."
msgstr ""
-#: module/setup.py:126
+#: pyload/setup.py:111
msgid "no SSL available"
msgstr ""
-#: module/setup.py:127
+#: pyload/setup.py:112
msgid ""
"This is needed if you want to establish a secure connection to core or "
"webinterface."
msgstr ""
-#: module/setup.py:128
+#: pyload/setup.py:113
msgid "If you only want to access locally to pyLoad ssl is not usefull."
msgstr ""
-#: module/setup.py:132
+#: pyload/setup.py:117
msgid "no Captcha Recognition available"
msgstr ""
-#: module/setup.py:133
+#: pyload/setup.py:118
msgid "Only needed for some hosters and as freeuser."
msgstr ""
-#: module/setup.py:137
-msgid "Gui not available"
-msgstr ""
-
-#: module/setup.py:138
-msgid "The Graphical User Interface."
-msgstr ""
-
-#: module/setup.py:142
+#: pyload/setup.py:122
msgid "no JavaScript engine found"
msgstr ""
-#: module/setup.py:143
+#: pyload/setup.py:123
msgid ""
"You will need this for some Click'N'Load links. Install Spidermonkey, ossp-"
"js, pyv8 or rhino"
msgstr ""
-#: module/setup.py:145
+#: pyload/setup.py:125
msgid "You can abort the setup now and fix some dependicies if you want."
msgstr ""
-#: module/setup.py:147
+#: pyload/setup.py:127
msgid "Continue with setup?"
msgstr ""
-#: module/setup.py:153
+#: pyload/setup.py:133
#, python-format
msgid "Do you want to change the config path? Current is %s"
msgstr ""
-#: module/setup.py:154
+#: pyload/setup.py:135
msgid ""
"If you use pyLoad on a server or the home partition lives on an iternal "
"flash it may be a good idea to change it."
msgstr ""
-#: module/setup.py:155
+#: pyload/setup.py:136
msgid "Change config path?"
msgstr ""
-#: module/setup.py:162
+#: pyload/setup.py:142
msgid "Do you want to configure login data and basic settings?"
msgstr ""
-#: module/setup.py:163
+#: pyload/setup.py:143
msgid "This is recommend for first run."
msgstr ""
-#: module/setup.py:164
+#: pyload/setup.py:144
msgid "Make basic setup?"
msgstr ""
-#: module/setup.py:171
+#: pyload/setup.py:151
msgid "Do you want to configure ssl?"
msgstr ""
-#: module/setup.py:172
+#: pyload/setup.py:152
msgid "Configure ssl?"
msgstr ""
-#: module/setup.py:178
+#: pyload/setup.py:158
msgid "Do you want to configure webinterface?"
msgstr ""
-#: module/setup.py:179
+#: pyload/setup.py:159
msgid "Configure webinterface?"
msgstr ""
-#: module/setup.py:184
+#: pyload/setup.py:164
msgid "Setup finished successfully."
msgstr ""
-#: module/setup.py:185
+#: pyload/setup.py:165
msgid "Hit enter to exit and restart pyLoad"
msgstr ""
-#: module/setup.py:191
+#: pyload/setup.py:172
msgid "## System Check ##"
msgstr ""
-#: module/setup.py:196
+#: pyload/setup.py:175
msgid "Your python version is to new, Please use Python 2.6/2.7"
msgstr ""
-#: module/setup.py:199
+#: pyload/setup.py:178
msgid "Your python version is to old, Please use at least Python 2.5"
msgstr ""
-#: module/setup.py:202
+#: pyload/setup.py:181
msgid "Python Version: OK"
msgstr ""
-#: module/setup.py:249
+#: pyload/setup.py:224
#, python-format
msgid "Your installed jinja2 version %s seems too old."
msgstr ""
-#: module/setup.py:250
+#: pyload/setup.py:225
msgid "You can safely continue but if the webinterface is not working,"
msgstr ""
-#: module/setup.py:251
+#: pyload/setup.py:226
msgid ""
"please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
msgstr ""
-#: module/setup.py:268
+#: pyload/setup.py:243
msgid "JS engine"
msgstr ""
-#: module/setup.py:274
+#: pyload/setup.py:250
msgid "## Basic Setup ##"
msgstr ""
-#: module/setup.py:277
-msgid "The following logindata is valid for CLI, GUI and webinterface."
+#: pyload/setup.py:253
+msgid "The following logindata is valid for CLI and webinterface."
msgstr ""
-#: module/setup.py:282 module/setup.py:371 module/setup.py:387
+#: pyload/setup.py:259 pyload/setup.py:357 pyload/setup.py:373
msgid "Username"
msgstr ""
-#: module/setup.py:288
+#: pyload/setup.py:265
msgid ""
"External clients (GUI, CLI or other) need remote access to work over the "
"network."
msgstr ""
-#: module/setup.py:289
+#: pyload/setup.py:266
msgid ""
"However, if you only want to use the webinterface you may disable it to save "
"ram."
msgstr ""
-#: module/setup.py:290
+#: pyload/setup.py:267
msgid "Enable remote access"
msgstr ""
-#: module/setup.py:295
+#: pyload/setup.py:271
msgid "Language"
msgstr ""
-#: module/setup.py:298
+#: pyload/setup.py:273
msgid "Downloadfolder"
msgstr ""
-#: module/setup.py:299
+#: pyload/setup.py:274
msgid "Max parallel downloads"
msgstr ""
-#: module/setup.py:303
+#: pyload/setup.py:278
msgid "Use Reconnect?"
msgstr ""
-#: module/setup.py:306
+#: pyload/setup.py:281
msgid "Reconnect script location"
msgstr ""
-#: module/setup.py:311
+#: pyload/setup.py:286
msgid "## Webinterface Setup ##"
msgstr ""
-#: module/setup.py:314
+#: pyload/setup.py:289
msgid "Activate webinterface?"
msgstr ""
-#: module/setup.py:316
+#: pyload/setup.py:291
msgid ""
"Listen address, if you use 127.0.0.1 or localhost, the webinterface will "
"only accessible locally."
msgstr ""
-#: module/setup.py:317
+#: pyload/setup.py:292
msgid "Address"
msgstr ""
-#: module/setup.py:318
+#: pyload/setup.py:293
msgid "Port"
msgstr ""
-#: module/setup.py:320
+#: pyload/setup.py:295
msgid ""
"pyLoad offers several server backends, now following a short explanation."
msgstr ""
-#: module/setup.py:321
-msgid "Default server, best choice if you dont know which one to choose."
-msgstr ""
-
-#: module/setup.py:322
-msgid "This server offers SSL and is a good alternative to builtin."
+#: pyload/setup.py:296
+msgid "Default server; best choice if you plan to use pyLoad just for you."
msgstr ""
-#: module/setup.py:323
+#: pyload/setup.py:297
msgid ""
-"Can be used by apache, lighttpd, requires you to configure them, which is "
-"not too easy job."
+"Support SSL connection and can serve simultaneously more client flawlessly."
msgstr ""
-#: module/setup.py:324
-msgid "Very fast alternative written in C, requires libev and linux knowlegde."
-msgstr ""
-
-#: module/setup.py:325
-msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+#: pyload/setup.py:299
+msgid ""
+"Can be used by apache, lighttpd, etc.; needs to be properly configured "
+"before."
msgstr ""
-#: module/setup.py:326
-msgid "and copy bjoern.so to module/lib"
+#: pyload/setup.py:301
+msgid "Very fast alternative to builtin; requires libev and bjoern packages."
msgstr ""
-#: module/setup.py:329
+#: pyload/setup.py:305
msgid ""
"Attention: In some rare cases the builtin server is not working, if you "
"notice problems with the webinterface"
msgstr ""
-#: module/setup.py:330
+#: pyload/setup.py:306
msgid "come back here and change the builtin server to the threaded one here."
msgstr ""
-#: module/setup.py:333
+#: pyload/setup.py:315
msgid "Server"
msgstr ""
-#: module/setup.py:337
+#: pyload/setup.py:320
msgid "## SSL Setup ##"
msgstr ""
-#: module/setup.py:339
+#: pyload/setup.py:322
msgid ""
"Execute these commands from pyLoad config folder to make ssl certificates:"
msgstr ""
-#: module/setup.py:345
+#: pyload/setup.py:328
msgid "If you're done and everything went fine, you can activate ssl now."
msgstr ""
-#: module/setup.py:347
+#: pyload/setup.py:330
msgid "Activate SSL?"
msgstr ""
-#: module/setup.py:361
+#: pyload/setup.py:347
msgid "Select action"
msgstr ""
-#: module/setup.py:362
+#: pyload/setup.py:348
msgid "1 - Create/Edit user"
msgstr ""
-#: module/setup.py:363
+#: pyload/setup.py:349
msgid "2 - List users"
msgstr ""
-#: module/setup.py:364
+#: pyload/setup.py:350
msgid "3 - Remove user"
msgstr ""
-#: module/setup.py:365
+#: pyload/setup.py:351
msgid "4 - Quit"
msgstr ""
-#: module/setup.py:377
+#: pyload/setup.py:363
msgid "Users"
msgstr ""
-#: module/setup.py:406
+#: pyload/setup.py:391
msgid "Setting new configpath, current configuration will not be transfered!"
msgstr ""
-#: module/setup.py:407
+#: pyload/setup.py:392
msgid "Configpath"
msgstr ""
-#: module/setup.py:415
+#: pyload/setup.py:400
msgid "Configpath changed, setup will now close, please restart to go on."
msgstr ""
-#: module/setup.py:416
+#: pyload/setup.py:401
msgid "Press Enter to exit."
msgstr ""
-#: module/setup.py:420
+#: pyload/setup.py:405
#, python-format
msgid "Setting config path failed: %s"
msgstr ""
-#: module/setup.py:425
+#: pyload/setup.py:411
#, python-format
msgid "%s: OK"
msgstr ""
-#: module/setup.py:427
+#: pyload/setup.py:413
#, python-format
msgid "%s: missing"
msgstr ""
-#: module/setup.py:456
-msgid "[y]/n"
+#: pyload/setup.py:456
+msgid ""
+"Warning: Consider a password of 10 or more symbols if you expect to access "
+"from outside your local network (ex. internet)."
msgstr ""
-#: module/setup.py:458
-msgid "y/[n]"
+#: pyload/setup.py:457
+msgid "Password: "
msgstr ""
-#: module/setup.py:470
-msgid "Password: "
+#: pyload/setup.py:461
+#, python-format
+msgid "Password too short! Use at least %s symbols."
msgstr ""
-#: module/setup.py:475
-msgid "Password to short. Use at least 4 symbols."
+#: pyload/setup.py:464
+msgid "Password must be alphanumeric."
msgstr ""
-#: module/setup.py:481
+#: pyload/setup.py:467
msgid "Password (again): "
msgstr ""
-#: module/setup.py:488
+#: pyload/setup.py:473
msgid "Passwords did not match."
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "yes"
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "true"
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "t"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "no"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "false"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "f"
msgstr ""
-#: module/setup.py:505 module/setup.py:516
+#: pyload/setup.py:495 pyload/setup.py:505
msgid "Invalid Input"
msgstr ""
diff --git a/locale/sr/LC_MESSAGES/django.mo b/locale/sr/LC_MESSAGES/django.mo
deleted file mode 100644
index 2f03721f1..000000000
--- a/locale/sr/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/pyLoad.mo b/locale/sr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index e66d60ec6..000000000
--- a/locale/sr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/pyLoadCli.mo b/locale/sr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 6d4d21e9d..000000000
--- a/locale/sr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/pyLoadGui.mo b/locale/sr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 5ece99c01..000000000
--- a/locale/sr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/setup.mo b/locale/sr/LC_MESSAGES/setup.mo
deleted file mode 100644
index 3ce6ecf6c..000000000
--- a/locale/sr/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/django.mo b/locale/sv/LC_MESSAGES/django.mo
deleted file mode 100644
index c85b2c173..000000000
--- a/locale/sv/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/pyLoad.mo b/locale/sv/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 29d93d585..000000000
--- a/locale/sv/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/pyLoadCli.mo b/locale/sv/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index f0c2c04bd..000000000
--- a/locale/sv/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/pyLoadGui.mo b/locale/sv/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 4231a1bbf..000000000
--- a/locale/sv/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/setup.mo b/locale/sv/LC_MESSAGES/setup.mo
deleted file mode 100644
index 2cf2efd70..000000000
--- a/locale/sv/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/pyLoad.mo b/locale/tr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/tr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/pyLoadCli.mo b/locale/tr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 30e94695b..000000000
--- a/locale/tr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/pyLoadGui.mo b/locale/tr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/tr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/module/Api.py b/module/Api.py
deleted file mode 100644
index f0bf5e264..000000000
--- a/module/Api.py
+++ /dev/null
@@ -1,1033 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from base64 import standard_b64encode
-from os.path import join
-from time import time
-import re
-
-from PyFile import PyFile
-from utils import freeSpace, compare_time
-from common.packagetools import parseNames
-from network.RequestFactory import getURL
-from remote import activated
-
-if activated:
- try:
- from remote.thriftbackend.thriftgen.pyload.ttypes import *
- from remote.thriftbackend.thriftgen.pyload.Pyload import Iface
- BaseObject = TBase
- except ImportError:
- print "Thrift not imported"
- from remote.socketbackend.ttypes import *
-else:
- from remote.socketbackend.ttypes import *
-
-# contains function names mapped to their permissions
-# unlisted functions are for admins only
-permMap = {}
-
-# decorator only called on init, never initialized, so has no effect on runtime
-def permission(bits):
- class _Dec(object):
- def __new__(cls, func, *args, **kwargs):
- permMap[func.__name__] = bits
- return func
-
- return _Dec
-
-
-urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE)
-
-class PERMS:
- ALL = 0 # requires no permission, but login
- ADD = 1 # can add packages
- DELETE = 2 # can delete packages
- STATUS = 4 # see and change server status
- LIST = 16 # see queue and collector
- MODIFY = 32 # moddify some attribute of downloads
- DOWNLOAD = 64 # can download from webinterface
- SETTINGS = 128 # can access settings
- ACCOUNTS = 256 # can access accounts
- LOGS = 512 # can see server logs
-
-class ROLE:
- ADMIN = 0 #admin has all permissions implicit
- USER = 1
-
-def has_permission(userperms, perms):
- # bytewise or perms before if needed
- return perms == (userperms & perms)
-
-
-class Api(Iface):
- """
- **pyLoads API**
-
- This is accessible either internal via core.api or via thrift backend.
-
- see Thrift specification file remote/thriftbackend/pyload.thrift\
- for information about data structures and what methods are usuable with rpc.
-
- Most methods requires specific permissions, please look at the source code if you need to know.\
- These can be configured via webinterface.
- Admin user have all permissions, and are the only ones who can access the methods with no specific permission.
- """
-
- EXTERNAL = Iface # let the json api know which methods are external
-
- def __init__(self, core):
- self.core = core
-
- def _convertPyFile(self, p):
- f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"],
- p["format_size"], p["status"], p["statusmsg"],
- p["package"], p["error"], p["order"])
- return f
-
- def _convertConfigFormat(self, c):
- sections = {}
- for sectionName, sub in c.iteritems():
- section = ConfigSection(sectionName, sub["desc"])
- items = []
- for key, data in sub.iteritems():
- if key in ("desc", "outline"):
- continue
- item = ConfigItem()
- item.name = key
- item.description = data["desc"]
- item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"]
- item.type = data["type"]
- items.append(item)
- section.items = items
- sections[sectionName] = section
- if "outline" in sub:
- section.outline = sub["outline"]
- return sections
-
- @permission(PERMS.SETTINGS)
- def getConfigValue(self, category, option, section="core"):
- """Retrieve config value.
-
- :param category: name of category, or plugin
- :param option: config option
- :param section: 'plugin' or 'core'
- :return: config value as string
- """
- if section == "core":
- value = self.core.config[category][option]
- else:
- value = self.core.config.getPlugin(category, option)
-
- return str(value) if not isinstance(value, basestring) else value
-
- @permission(PERMS.SETTINGS)
- def setConfigValue(self, category, option, value, section="core"):
- """Set new config value.
-
- :param category:
- :param option:
- :param value: new config value
- :param section: 'plugin' or 'core
- """
- self.core.hookManager.dispatchEvent("configChanged", category, option, value, section)
-
- if section == "core":
- self.core.config[category][option] = value
-
- if option in ("limit_speed", "max_speed"): #not so nice to update the limit
- self.core.requestFactory.updateBucket()
-
- elif section == "plugin":
- self.core.config.setPlugin(category, option, value)
-
- @permission(PERMS.SETTINGS)
- def getConfig(self):
- """Retrieves complete config of core.
-
- :return: list of `ConfigSection`
- """
- return self._convertConfigFormat(self.core.config.config)
-
- def getConfigDict(self):
- """Retrieves complete config in dict format, not for RPC.
-
- :return: dict
- """
- return self.core.config.config
-
- @permission(PERMS.SETTINGS)
- def getPluginConfig(self):
- """Retrieves complete config for all plugins.
-
- :return: list of `ConfigSection`
- """
- return self._convertConfigFormat(self.core.config.plugin)
-
- def getPluginConfigDict(self):
- """Plugin config as dict, not for RPC.
-
- :return: dict
- """
- return self.core.config.plugin
-
-
- @permission(PERMS.STATUS)
- def pauseServer(self):
- """Pause server: Tt wont start any new downloads, but nothing gets aborted."""
- self.core.threadManager.pause = True
-
- @permission(PERMS.STATUS)
- def unpauseServer(self):
- """Unpause server: New Downloads will be started."""
- self.core.threadManager.pause = False
-
- @permission(PERMS.STATUS)
- def togglePause(self):
- """Toggle pause state.
-
- :return: new pause state
- """
- self.core.threadManager.pause ^= True
- return self.core.threadManager.pause
-
- @permission(PERMS.STATUS)
- def toggleReconnect(self):
- """Toggle reconnect activation.
-
- :return: new reconnect state
- """
- self.core.config["reconnect"]["activated"] ^= True
- return self.core.config["reconnect"]["activated"]
-
- @permission(PERMS.LIST)
- def statusServer(self):
- """Some general information about the current status of pyLoad.
-
- :return: `ServerStatus`
- """
- serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()),
- self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
- not self.core.threadManager.pause and self.isTimeDownload(),
- self.core.config['reconnect']['activated'] and self.isTimeReconnect())
-
- for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]:
- serverStatus.speed += pyfile.getSpeed() #bytes/s
-
- return serverStatus
-
- @permission(PERMS.STATUS)
- def freeSpace(self):
- """Available free space at download directory in bytes"""
- return freeSpace(self.core.config["general"]["download_folder"])
-
- @permission(PERMS.ALL)
- def getServerVersion(self):
- """pyLoad Core version """
- return self.core.version
-
- def kill(self):
- """Clean way to quit pyLoad"""
- self.core.do_kill = True
-
- def restart(self):
- """Restart pyload core"""
- self.core.do_restart = True
-
- @permission(PERMS.LOGS)
- def getLog(self, offset=0):
- """Returns most recent log entries.
-
- :param offset: line offset
- :return: List of log entries
- """
- filename = join(self.core.config['log']['log_folder'], 'log.txt')
- try:
- fh = open(filename, "r")
- lines = fh.readlines()
- fh.close()
- if offset >= len(lines):
- return []
- return lines[offset:]
- except:
- return ['No log available']
-
- @permission(PERMS.STATUS)
- def isTimeDownload(self):
- """Checks if pyload will start new downloads according to time in config.
-
- :return: bool
- """
- start = self.core.config['downloadTime']['start'].split(":")
- end = self.core.config['downloadTime']['end'].split(":")
- return compare_time(start, end)
-
- @permission(PERMS.STATUS)
- def isTimeReconnect(self):
- """Checks if pyload will try to make a reconnect
-
- :return: bool
- """
- start = self.core.config['reconnect']['startTime'].split(":")
- end = self.core.config['reconnect']['endTime'].split(":")
- return compare_time(start, end) and self.core.config["reconnect"]["activated"]
-
- @permission(PERMS.LIST)
- def statusDownloads(self):
- """ Status off all currently running downloads.
-
- :return: list of `DownloadStatus`
- """
- data = []
- for pyfile in self.core.threadManager.getActiveFiles():
- if not isinstance(pyfile, PyFile):
- continue
-
- data.append(DownloadInfo(
- pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(),
- pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(),
- pyfile.status, pyfile.getStatusName(), pyfile.formatWait(),
- pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname))
-
- return data
-
- @permission(PERMS.ADD)
- def addPackage(self, name, links, dest=Destination.Queue):
- """Adds a package, with links to desired destination.
-
- :param name: name of the new package
- :param links: list of urls
- :param dest: `Destination`
- :return: package id of the new package
- """
- if self.core.config['general']['folder_per_package']:
- folder = name
- else:
- folder = ""
-
- folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_")
-
- pid = self.core.files.addPackage(name, folder, dest)
-
- self.core.files.addLinks(links, pid)
-
- self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)})
-
- self.core.files.save()
-
- return pid
-
- @permission(PERMS.ADD)
- def parseURLs(self, html=None, url=None):
- """Parses html content or any arbitaty text for links and returns result of `checkURLs`
-
- :param html: html source
- :return:
- """
- urls = []
-
- if html:
- urls += [x[0] for x in urlmatcher.findall(html)]
-
- if url:
- page = getURL(url)
- urls += [x[0] for x in urlmatcher.findall(page)]
-
- # remove duplicates
- return self.checkURLs(set(urls))
-
-
- @permission(PERMS.ADD)
- def checkURLs(self, urls):
- """ Gets urls and returns pluginname mapped to list of matches urls.
-
- :param urls:
- :return: {plugin: urls}
- """
- data = self.core.pluginManager.parseUrls(urls)
- plugins = {}
-
- for url, plugin in data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
- return plugins
-
- @permission(PERMS.ADD)
- def checkOnlineStatus(self, urls):
- """ initiates online status check
-
- :param urls:
- :return: initial set of data as `OnlineCheck` instance containing the result id
- """
- data = self.core.pluginManager.parseUrls(urls)
-
- rid = self.core.threadManager.createResultThread(data, False)
-
- tmp = [(url, (url, OnlineStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
- data = parseNames(tmp)
- result = {}
-
- for k, v in data.iteritems():
- for url, status in v:
- status.packagename = k
- result[url] = status
-
- return OnlineCheck(rid, result)
-
- @permission(PERMS.ADD)
- def checkOnlineStatusContainer(self, urls, container, data):
- """ checks online status of urls and a submited container file
-
- :param urls: list of urls
- :param container: container file name
- :param data: file content
- :return: online check
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
- th.write(str(data))
- th.close()
-
- return self.checkOnlineStatus(urls + [th.name])
-
- @permission(PERMS.ADD)
- def pollResults(self, rid):
- """ Polls the result available for ResultID
-
- :param rid: `ResultID`
- :return: `OnlineCheck`, if rid is -1 then no more data available
- """
- result = self.core.threadManager.getInfoResult(rid)
-
- if "ALL_INFO_FETCHED" in result:
- del result["ALL_INFO_FETCHED"]
- return OnlineCheck(-1, result)
- else:
- return OnlineCheck(rid, result)
-
-
- @permission(PERMS.ADD)
- def generatePackages(self, links):
- """ Parses links, generates packages names from urls
-
- :param links: list of urls
- :return: package names mapped to urls
- """
- result = parseNames((x, x) for x in links)
- return result
-
- @permission(PERMS.ADD)
- def generateAndAddPackages(self, links, dest=Destination.Queue):
- """Generates and add packages
-
- :param links: list of urls
- :param dest: `Destination`
- :return: list of package ids
- """
- return [self.addPackage(name, urls, dest) for name, urls
- in self.generatePackages(links).iteritems()]
-
- @permission(PERMS.ADD)
- def checkAndAddPackages(self, links, dest=Destination.Queue):
- """Checks online status, retrieves names, and will add packages.\
- Because of this packages are not added immediatly, only for internal use.
-
- :param links: list of urls
- :param dest: `Destination`
- :return: None
- """
- data = self.core.pluginManager.parseUrls(links)
- self.core.threadManager.createResultThread(data, True)
-
-
- @permission(PERMS.LIST)
- def getPackageData(self, pid):
- """Returns complete information about package, and included files.
-
- :param pid: package id
- :return: `PackageData` with .links attribute
- """
- data = self.core.files.getPackageData(int(pid))
-
- if not data:
- raise PackageDoesNotExists(pid)
-
- pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- links=[self._convertPyFile(x) for x in data["links"].itervalues()])
-
- return pdata
-
- @permission(PERMS.LIST)
- def getPackageInfo(self, pid):
- """Returns information about package, without detailed information about containing files
-
- :param pid: package id
- :return: `PackageData` with .fid attribute
- """
- data = self.core.files.getPackageData(int(pid))
-
- if not data:
- raise PackageDoesNotExists(pid)
-
- pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- fids=[int(x) for x in data["links"]])
-
- return pdata
-
- @permission(PERMS.LIST)
- def getFileData(self, fid):
- """Get complete information about a specific file.
-
- :param fid: file id
- :return: `FileData`
- """
- info = self.core.files.getFileData(int(fid))
- if not info:
- raise FileDoesNotExists(fid)
-
- fdata = self._convertPyFile(info.values()[0])
- return fdata
-
- @permission(PERMS.DELETE)
- def deleteFiles(self, fids):
- """Deletes several file entries from pyload.
-
- :param fids: list of file ids
- """
- for id in fids:
- self.core.files.deleteLink(int(id))
-
- self.core.files.save()
-
- @permission(PERMS.DELETE)
- def deletePackages(self, pids):
- """Deletes packages and containing links.
-
- :param pids: list of package ids
- """
- for id in pids:
- self.core.files.deletePackage(int(id))
-
- self.core.files.save()
-
- @permission(PERMS.LIST)
- def getQueue(self):
- """Returns info about queue and packages, **not** about files, see `getQueueData` \
- or `getPackageData` instead.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
- for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
-
- @permission(PERMS.LIST)
- def getQueueData(self):
- """Return complete data about everything in queue, this is very expensive use it sparely.\
- See `getQueue` for alternative.
-
- :return: list of `PackageData`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
- for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
-
- @permission(PERMS.LIST)
- def getCollector(self):
- """same as `getQueue` for collector.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
- for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
-
- @permission(PERMS.LIST)
- def getCollectorData(self):
- """same as `getQueueData` for collector.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
- for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
-
-
- @permission(PERMS.ADD)
- def addFiles(self, pid, links):
- """Adds files to specific package.
-
- :param pid: package id
- :param links: list of urls
- """
- self.core.files.addLinks(links, int(pid))
-
- self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
- self.core.files.save()
-
- @permission(PERMS.MODIFY)
- def pushToQueue(self, pid):
- """Moves package from Collector to Queue.
-
- :param pid: package id
- """
- self.core.files.setPackageLocation(pid, Destination.Queue)
-
- @permission(PERMS.MODIFY)
- def pullFromQueue(self, pid):
- """Moves package from Queue to Collector.
-
- :param pid: package id
- """
- self.core.files.setPackageLocation(pid, Destination.Collector)
-
- @permission(PERMS.MODIFY)
- def restartPackage(self, pid):
- """Restarts a package, resets every containing files.
-
- :param pid: package id
- """
- self.core.files.restartPackage(int(pid))
-
- @permission(PERMS.MODIFY)
- def restartFile(self, fid):
- """Resets file status, so it will be downloaded again.
-
- :param fid: file id
- """
- self.core.files.restartFile(int(fid))
-
- @permission(PERMS.MODIFY)
- def recheckPackage(self, pid):
- """Proofes online status of all files in a package, also a default action when package is added.
-
- :param pid:
- :return:
- """
- self.core.files.reCheckPackage(int(pid))
-
- @permission(PERMS.MODIFY)
- def stopAllDownloads(self):
- """Aborts all running downloads."""
-
- pyfiles = self.core.files.cache.values()
- for pyfile in pyfiles:
- pyfile.abortDownload()
-
- @permission(PERMS.MODIFY)
- def stopDownloads(self, fids):
- """Aborts specific downloads.
-
- :param fids: list of file ids
- :return:
- """
- pyfiles = self.core.files.cache.values()
-
- for pyfile in pyfiles:
- if pyfile.id in fids:
- pyfile.abortDownload()
-
- @permission(PERMS.MODIFY)
- def setPackageName(self, pid, name):
- """Renames a package.
-
- :param pid: package id
- :param name: new package name
- """
- pack = self.core.files.getPackage(pid)
- pack.name = name
- pack.sync()
-
- @permission(PERMS.MODIFY)
- def movePackage(self, destination, pid):
- """Set a new package location.
-
- :param destination: `Destination`
- :param pid: package id
- """
- if destination not in (0, 1): return
- self.core.files.setPackageLocation(pid, destination)
-
- @permission(PERMS.MODIFY)
- def moveFiles(self, fids, pid):
- """Move multiple files to another package
-
- :param fids: list of file ids
- :param pid: destination package
- :return:
- """
- #TODO: implement
- pass
-
-
- @permission(PERMS.ADD)
- def uploadContainer(self, filename, data):
- """Uploads and adds a container file to pyLoad.
-
- :param filename: filename, extension is important so it can correctly decrypted
- :param data: file content
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
- th.write(str(data))
- th.close()
-
- self.addPackage(th.name, [th.name], Destination.Queue)
-
- @permission(PERMS.MODIFY)
- def orderPackage(self, pid, position):
- """Gives a package a new position.
-
- :param pid: package id
- :param position:
- """
- self.core.files.reorderPackage(pid, position)
-
- @permission(PERMS.MODIFY)
- def orderFile(self, fid, position):
- """Gives a new position to a file within its package.
-
- :param fid: file id
- :param position:
- """
- self.core.files.reorderFile(fid, position)
-
- @permission(PERMS.MODIFY)
- def setPackageData(self, pid, data):
- """Allows to modify several package attributes.
-
- :param pid: package id
- :param data: dict that maps attribute to desired value
- """
- p = self.core.files.getPackage(pid)
- if not p: raise PackageDoesNotExists(pid)
-
- for key, value in data.iteritems():
- if key == "id": continue
- setattr(p, key, value)
-
- p.sync()
- self.core.files.save()
-
- @permission(PERMS.DELETE)
- def deleteFinished(self):
- """Deletes all finished files and completly finished packages.
-
- :return: list of deleted package ids
- """
- return self.core.files.deleteFinishedLinks()
-
- @permission(PERMS.MODIFY)
- def restartFailed(self):
- """Restarts all failed failes."""
- self.core.files.restartFailed()
-
- @permission(PERMS.LIST)
- def getPackageOrder(self, destination):
- """Returns information about package order.
-
- :param destination: `Destination`
- :return: dict mapping order to package id
- """
-
- packs = self.core.files.getInfoData(destination)
- order = {}
-
- for pid in packs:
- pack = self.core.files.getPackageData(int(pid))
- while pack["order"] in order.keys(): #just in case
- pack["order"] += 1
- order[pack["order"]] = pack["id"]
- return order
-
- @permission(PERMS.LIST)
- def getFileOrder(self, pid):
- """Information about file order within package.
-
- :param pid:
- :return: dict mapping order to file id
- """
- rawData = self.core.files.getPackageData(int(pid))
- order = {}
- for id, pyfile in rawData["links"].iteritems():
- while pyfile["order"] in order.keys(): #just in case
- pyfile["order"] += 1
- order[pyfile["order"]] = pyfile["id"]
- return order
-
-
- @permission(PERMS.STATUS)
- def isCaptchaWaiting(self):
- """Indicates wether a captcha task is available
-
- :return: bool
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTask()
- return not task is None
-
- @permission(PERMS.STATUS)
- def getCaptchaTask(self, exclusive=False):
- """Returns a captcha task
-
- :param exclusive: unused
- :return: `CaptchaTask`
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTask()
- if task:
- task.setWatingForUser(exclusive=exclusive)
- data, type, result = task.getCaptcha()
- t = CaptchaTask(int(task.id), standard_b64encode(data), type, result)
- return t
- else:
- return CaptchaTask(-1)
-
- @permission(PERMS.STATUS)
- def getCaptchaTaskStatus(self, tid):
- """Get information about captcha task
-
- :param tid: task id
- :return: string
- """
- self.core.lastClientConnected = time()
- t = self.core.captchaManager.getTaskByID(tid)
- return t.getStatus() if t else ""
-
- @permission(PERMS.STATUS)
- def setCaptchaResult(self, tid, result):
- """Set result for a captcha task
-
- :param tid: task id
- :param result: captcha result
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTaskByID(tid)
- if task:
- task.setResult(result)
- self.core.captchaManager.removeTask(task)
-
-
- @permission(PERMS.STATUS)
- def getEvents(self, uuid):
- """Lists occured events, may be affected to changes in future.
-
- :param uuid:
- :return: list of `Events`
- """
- events = self.core.pullManager.getEvents(uuid)
- newEvents = []
-
- def convDest(d):
- return Destination.Queue if d == "queue" else Destination.Collector
-
- for e in events:
- event = EventInfo()
- event.eventname = e[0]
- if e[0] in ("update", "remove", "insert"):
- event.id = e[3]
- event.type = ElementType.Package if e[2] == "pack" else ElementType.File
- event.destination = convDest(e[1])
- elif e[0] == "order":
- if e[1]:
- event.id = e[1]
- event.type = ElementType.Package if e[2] == "pack" else ElementType.File
- event.destination = convDest(e[3])
- elif e[0] == "reload":
- event.destination = convDest(e[1])
- newEvents.append(event)
- return newEvents
-
- @permission(PERMS.ACCOUNTS)
- def getAccounts(self, refresh):
- """Get information about all entered accounts.
-
- :param refresh: reload account info
- :return: list of `AccountInfo`
- """
- accs = self.core.accountManager.getAccountInfos(False, refresh)
- accounts = []
- for group in accs.values():
- accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"],
- acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"])
- for acc in group])
- return accounts
-
- @permission(PERMS.ALL)
- def getAccountTypes(self):
- """All available account types.
-
- :return: list
- """
- return self.core.accountManager.accounts.keys()
-
- @permission(PERMS.ACCOUNTS)
- def updateAccount(self, plugin, account, password=None, options={}):
- """Changes pw/options for specific account."""
- self.core.accountManager.updateAccount(plugin, account, password, options)
-
- @permission(PERMS.ACCOUNTS)
- def removeAccount(self, plugin, account):
- """Remove account from pyload.
-
- :param plugin: pluginname
- :param account: accountname
- """
- self.core.accountManager.removeAccount(plugin, account)
-
- @permission(PERMS.ALL)
- def login(self, username, password, remoteip=None):
- """Login into pyLoad, this **must** be called when using rpc before any methods can be used.
-
- :param username:
- :param password:
- :param remoteip: Omit this argument, its only used internal
- :return: bool indicating login was successful
- """
- return True if self.checkAuth(username, password, remoteip) else False
-
- def checkAuth(self, username, password, remoteip=None):
- """Check authentication and returns details
-
- :param username:
- :param password:
- :param remoteip:
- :return: dict with info, empty when login is incorrect
- """
- if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
- return "local"
- if self.core.startedInGui and remoteip == "127.0.0.1":
- return "local"
-
- return self.core.db.checkAuth(username, password)
-
- def isAuthorized(self, func, userdata):
- """checks if the user is authorized for specific method
-
- :param func: function name
- :param userdata: dictionary of user data
- :return: boolean
- """
- if userdata == "local" or userdata["role"] == ROLE.ADMIN:
- return True
- elif func in permMap and has_permission(userdata["permission"], permMap[func]):
- return True
- else:
- return False
-
-
- @permission(PERMS.ALL)
- def getUserData(self, username, password):
- """similar to `checkAuth` but returns UserData thrift type """
- user = self.checkAuth(username, password)
- if user:
- return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"])
- else:
- return UserData()
-
-
- def getAllUserData(self):
- """returns all known user and info"""
- res = {}
- for user, data in self.core.db.getAllUserData().iteritems():
- res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"])
-
- return res
-
- @permission(PERMS.STATUS)
- def getServices(self):
- """ A dict of available services, these can be defined by hook plugins.
-
- :return: dict with this style: {"plugin": {"method": "description"}}
- """
- data = {}
- for plugin, funcs in self.core.hookManager.methods.iteritems():
- data[plugin] = funcs
-
- return data
-
- @permission(PERMS.STATUS)
- def hasService(self, plugin, func):
- """Checks wether a service is available.
-
- :param plugin:
- :param func:
- :return: bool
- """
- cont = self.core.hookManager.methods
- return plugin in cont and func in cont[plugin]
-
- @permission(PERMS.STATUS)
- def call(self, info):
- """Calls a service (a method in hook plugin).
-
- :param info: `ServiceCall`
- :return: result
- :raises: ServiceDoesNotExists, when its not available
- :raises: ServiceException, when a exception was raised
- """
- plugin = info.plugin
- func = info.func
- args = info.arguments
- parse = info.parseArguments
-
- if not self.hasService(plugin, func):
- raise ServiceDoesNotExists(plugin, func)
-
- try:
- ret = self.core.hookManager.callRPC(plugin, func, args, parse)
- return str(ret)
- except Exception, e:
- raise ServiceException(e.message)
-
- @permission(PERMS.STATUS)
- def getAllInfo(self):
- """Returns all information stored by hook plugins. Values are always strings
-
- :return: {"plugin": {"name": value } }
- """
- return self.core.hookManager.getAllInfo()
-
- @permission(PERMS.STATUS)
- def getInfoByPlugin(self, plugin):
- """Returns information stored by a specific plugin.
-
- :param plugin: pluginname
- :return: dict of attr names mapped to value {"name": value}
- """
- return self.core.hookManager.getInfo(plugin)
-
- def changePassword(self, user, oldpw, newpw):
- """ changes password for specific user """
- return self.core.db.changePassword(user, oldpw, newpw)
-
- def setUserPermission(self, user, permission, role):
- self.core.db.setPermission(user, permission)
- self.core.db.setRole(user, role) \ No newline at end of file
diff --git a/module/CaptchaManager.py b/module/CaptchaManager.py
deleted file mode 100644
index 02cd10a11..000000000
--- a/module/CaptchaManager.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from time import time
-from traceback import print_exc
-from threading import Lock
-
-class CaptchaManager():
- def __init__(self, core):
- self.lock = Lock()
- self.core = core
- self.tasks = [] #task store, for outgoing tasks only
-
- self.ids = 0 #only for internal purpose
-
- def newTask(self, img, format, file, result_type):
- task = CaptchaTask(self.ids, img, format, file, result_type)
- self.ids += 1
- return task
-
- def removeTask(self, task):
- self.lock.acquire()
- if task in self.tasks:
- self.tasks.remove(task)
- self.lock.release()
-
- def getTask(self):
- self.lock.acquire()
- for task in self.tasks:
- if task.status in ("waiting", "shared-user"):
- self.lock.release()
- return task
- self.lock.release()
- return None
-
- def getTaskByID(self, tid):
- self.lock.acquire()
- for task in self.tasks:
- if task.id == str(tid): #task ids are strings
- self.lock.release()
- return task
- self.lock.release()
- return None
-
- def handleCaptcha(self, task):
- cli = self.core.isClientConnected()
-
- if cli: #client connected -> should solve the captcha
- task.setWaiting(50) #wait 50 sec for response
-
- for plugin in self.core.hookManager.activePlugins():
- try:
- plugin.newCaptchaTask(task)
- except:
- if self.core.debug:
- print_exc()
-
- if task.handler or cli: #the captcha was handled
- self.tasks.append(task)
- return True
-
- task.error = _("No Client connected for captcha decrypting")
-
- return False
-
-
-class CaptchaTask():
- def __init__(self, id, img, format, file, result_type='textual'):
- self.id = str(id)
- self.captchaImg = img
- self.captchaFormat = format
- self.captchaFile = file
- self.captchaResultType = result_type
- self.handler = [] #the hook plugins that will take care of the solution
- self.result = None
- self.waitUntil = None
- self.error = None #error message
-
- self.status = "init"
- self.data = {} #handler can store data here
-
- def getCaptcha(self):
- return self.captchaImg, self.captchaFormat, self.captchaResultType
-
- def setResult(self, text):
- if self.isTextual():
- self.result = text
- if self.isPositional():
- try:
- parts = text.split(',')
- self.result = (int(parts[0]), int(parts[1]))
- except:
- self.result = None
-
- def getResult(self):
- try:
- res = self.result.encode("utf8", "replace")
- except:
- res = self.result
-
- return res
-
- def getStatus(self):
- return self.status
-
- def setWaiting(self, sec):
- """ let the captcha wait secs for the solution """
- self.waitUntil = max(time() + sec, self.waitUntil)
- self.status = "waiting"
-
- def isWaiting(self):
- if self.result or self.error or time() > self.waitUntil:
- return False
-
- return True
-
- def isTextual(self):
- """ returns if text is written on the captcha """
- return self.captchaResultType == 'textual'
-
- def isPositional(self):
- """ returns if user have to click a specific region on the captcha """
- return self.captchaResultType == 'positional'
-
- def setWatingForUser(self, exclusive):
- if exclusive:
- self.status = "user"
- else:
- self.status = "shared-user"
-
- def timedOut(self):
- return time() > self.waitUntil
-
- def invalid(self):
- """ indicates the captcha was not correct """
- [x.captchaInvalid(self) for x in self.handler]
-
- def correct(self):
- [x.captchaCorrect(self) for x in self.handler]
-
- def __str__(self):
- return "<CaptchaTask '%s'>" % self.id
diff --git a/module/ConfigParser.py b/module/ConfigParser.py
deleted file mode 100644
index 78b612f13..000000000
--- a/module/ConfigParser.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-from time import sleep
-from os.path import exists, join
-from shutil import copy
-
-from traceback import print_exc
-from utils import chmod
-
-# ignore these plugin configs, mainly because plugins were wiped out
-IGNORE = (
- "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'),
- 'EasyShareCom', 'FlyshareCz'
- )
-
-CONF_VERSION = 1
-
-class ConfigParser:
- """
- holds and manage the configuration
-
- current dict layout:
-
- {
-
- section : {
- option : {
- value:
- type:
- desc:
- }
- desc:
-
- }
-
-
- """
-
-
- def __init__(self):
- """Constructor"""
- self.config = {} # the config values
- self.plugin = {} # the config for plugins
- self.oldRemoteData = {}
-
- self.pluginCB = None # callback when plugin config value is changed
-
- self.checkVersion()
-
- self.readConfig()
-
- self.deleteOldPlugins()
-
-
- def checkVersion(self, n=0):
- """determines if config need to be copied"""
- try:
- if not exists("pyload.conf"):
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
-
- if not exists("plugin.conf"):
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
-
- f = open("pyload.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
- print "Old version of config was replaced"
-
- f = open("plugin.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
- print "Old version of plugin-config replaced"
- except:
- if n < 3:
- sleep(0.3)
- self.checkVersion(n + 1)
- else:
- raise
-
- def readConfig(self):
- """reads the config file"""
-
- self.config = self.parseConfig(join(pypath, "module", "config", "default.conf"))
- self.plugin = self.parseConfig("plugin.conf")
-
- try:
- homeconf = self.parseConfig("pyload.conf")
- if "username" in homeconf["remote"]:
- if "password" in homeconf["remote"]:
- self.oldRemoteData = {"username": homeconf["remote"]["username"]["value"],
- "password": homeconf["remote"]["username"]["value"]}
- del homeconf["remote"]["password"]
- del homeconf["remote"]["username"]
- self.updateValues(homeconf, self.config)
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
-
- def parseConfig(self, config):
- """parses a given configfile"""
-
- f = open(config)
-
- config = f.read()
-
- config = config.splitlines()[1:]
-
- conf = {}
-
- section, option, value, typ, desc = "", "", "", "", ""
-
- listmode = False
-
- for line in config:
- comment = line.rfind("#")
- if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace():
- line = line.rpartition("#") # removes comments
- if line[1]:
- line = line[0]
- else:
- line = line[2]
-
- line = line.strip()
-
- try:
- if line == "":
- continue
- elif line.endswith(":"):
- section, none, desc = line[:-1].partition('-')
- section = section.strip()
- desc = desc.replace('"', "").strip()
- conf[section] = {"desc": desc}
- else:
- if listmode:
- if line.endswith("]"):
- listmode = False
- line = line.replace("]", "")
-
- value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
-
- else:
- content, none, value = line.partition("=")
-
- content, none, desc = content.partition(":")
-
- desc = desc.replace('"', "").strip()
-
- typ, none, option = content.strip().rpartition(" ")
-
- value = value.strip()
-
- if value.startswith("["):
- if value.endswith("]"):
- listmode = False
- value = value[:-1]
- else:
- listmode = True
-
- value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
- else:
- value = self.cast(typ, value)
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
- f.close()
- return conf
-
-
- def updateValues(self, config, dest):
- """sets the config values from a parsed config file to values in destination"""
-
- for section in config.iterkeys():
- if section in dest:
- for option in config[section].iterkeys():
- if option in ("desc", "outline"): continue
-
- if option in dest[section]:
- dest[section][option]["value"] = config[section][option]["value"]
-
- #else:
- # dest[section][option] = config[section][option]
-
-
- #else:
- # dest[section] = config[section]
-
- def saveConfig(self, config, filename):
- """saves config to filename"""
- with open(filename, "wb") as f:
- chmod(filename, 0600)
- f.write("version: %i \n" % CONF_VERSION)
- for section in config.iterkeys():
- f.write('\n%s - "%s":\n' % (section, config[section]["desc"]))
-
- for option, data in config[section].iteritems():
- if option in ("desc", "outline"): continue
-
- if isinstance(data["value"], list):
- value = "[ \n"
- for x in data["value"]:
- value += "\t\t" + str(x) + ",\n"
- value += "\t\t]\n"
- else:
- if type(data["value"]) in (str, unicode):
- value = data["value"] + "\n"
- else:
- value = str(data["value"]) + "\n"
- try:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value))
- except UnicodeEncodeError:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value.encode("utf8")))
-
- def cast(self, typ, value):
- """cast value to given format"""
- if type(value) not in (str, unicode):
- return value
-
- elif typ == "int":
- return int(value)
- elif typ == "bool":
- return True if value.lower() in ("1", "true", "on", "an", "yes") else False
- elif typ == "time":
- if not value: value = "0:00"
- if not ":" in value: value += ":00"
- return value
- elif typ in ("str", "file", "folder"):
- try:
- return value.encode("utf8")
- except:
- return value
- else:
- return value
-
-
- def save(self):
- """saves the configs to disk"""
-
- self.saveConfig(self.config, "pyload.conf")
- self.saveConfig(self.plugin, "plugin.conf")
-
-
- def __getitem__(self, section):
- """provides dictonary like access: c['section']['option']"""
- return Section(self, section)
-
-
- def get(self, section, option):
- """get value"""
- val = self.config[section][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def set(self, section, option, value):
- """set value"""
-
- value = self.cast(self.config[section][option]["type"], value)
-
- self.config[section][option]["value"] = value
- self.save()
-
- def getPlugin(self, plugin, option):
- """gets a value for a plugin"""
- val = self.plugin[plugin][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def setPlugin(self, plugin, option, value):
- """sets a value for a plugin"""
-
- value = self.cast(self.plugin[plugin][option]["type"], value)
-
- if self.pluginCB: self.pluginCB(plugin, option, value)
-
- self.plugin[plugin][option]["value"] = value
- self.save()
-
- def getMetaData(self, section, option):
- """ get all config data for an option """
- return self.config[section][option]
-
- def addPluginConfig(self, name, config, outline=""):
- """adds config options with tuples (name, type, desc, default)"""
- if name not in self.plugin:
- conf = {"desc": name,
- "outline": outline}
- self.plugin[name] = conf
- else:
- conf = self.plugin[name]
- conf["outline"] = outline
-
- for item in config:
- if item[0] in conf:
- conf[item[0]]["type"] = item[1]
- conf[item[0]]["desc"] = item[2]
- else:
- conf[item[0]] = {
- "desc": item[2],
- "type": item[1],
- "value": self.cast(item[1], item[3])
- }
-
- values = [x[0] for x in config] + ["desc", "outline"]
- #delete old values
- for item in conf.keys():
- if item not in values:
- del conf[item]
-
- def deleteConfig(self, name):
- """Removes a plugin config"""
- if name in self.plugin:
- del self.plugin[name]
-
-
- def deleteOldPlugins(self):
- """ remove old plugins from config """
-
- for name in IGNORE:
- if name in self.plugin:
- del self.plugin[name]
-
-
-class Section:
- """provides dictionary like access for configparser"""
-
- def __init__(self, parser, section):
- """Constructor"""
- self.parser = parser
- self.section = section
-
- def __getitem__(self, item):
- """getitem"""
- return self.parser.get(self.section, item)
-
- def __setitem__(self, item, value):
- """setitem"""
- self.parser.set(self.section, item, value)
-
-
-if __name__ == "__main__":
- pypath = ""
-
- from time import time
-
- a = time()
-
- c = ConfigParser()
-
- b = time()
-
- print "sec", b - a
-
- print c.config
-
- c.saveConfig(c.config, "user.conf")
diff --git a/module/HookManager.py b/module/HookManager.py
deleted file mode 100644
index 16f692d76..000000000
--- a/module/HookManager.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN, mkaay
- @interface-version: 0.1
-"""
-import __builtin__
-
-import traceback
-from thread import start_new_thread
-from threading import RLock
-
-from types import MethodType
-
-from module.PluginThread import HookThread
-from module.plugins.PluginManager import literal_eval
-from utils import lock
-
-class HookManager:
- """Manages hooks, delegates and handles Events.
-
- Every plugin can define events, \
- but some very usefull events are called by the Core.
- Contrary to overwriting hook methods you can use event listener,
- which provides additional entry point in the control flow.
- Only do very short tasks or use threads.
-
- **Known Events:**
- Most hook methods exists as events. These are the additional known events.
-
- ===================== ============== ==================================
- Name Arguments Description
- ===================== ============== ==================================
- downloadPreparing fid A download was just queued and will be prepared now.
- downloadStarts fid A plugin will immediately starts the download afterwards.
- linksAdded links, pid Someone just added links, you are able to modify the links.
- allDownloadsProcessed Every link was handled, pyload would idle afterwards.
- allDownloadsFinished Every download in queue is finished.
- unrarFinished folder, fname An Unrar job finished
- configChanged The config was changed via the api.
- pluginConfigChanged The plugin config changed, due to api or internal process.
- ===================== ============== ==================================
-
- | Notes:
- | allDownloadsProcessed is *always* called before allDownloadsFinished.
- | configChanged is *always* called before pluginConfigChanged.
-
-
- """
-
- def __init__(self, core):
- self.core = core
- self.config = self.core.config
-
- __builtin__.hookManager = self #needed to let hooks register themself
-
- self.log = self.core.log
- self.plugins = []
- self.pluginMap = {}
- self.methods = {} #dict of names and list of methods usable by rpc
-
- self.events = {} # contains events
-
- #registering callback for config event
- self.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring)
-
- self.addEvent("pluginConfigChanged", self.manageHooks)
-
- self.lock = RLock()
- self.createIndex()
-
- def try_catch(func):
- def new(*args):
- try:
- return func(*args)
- except Exception, e:
- args[0].log.error(_("Error executing hooks: %s") % str(e))
- if args[0].core.debug:
- traceback.print_exc()
-
- return new
-
-
- def addRPC(self, plugin, func, doc):
- plugin = plugin.rpartition(".")[2]
- doc = doc.strip() if doc else ""
-
- if plugin in self.methods:
- self.methods[plugin][func] = doc
- else:
- self.methods[plugin] = {func: doc}
-
- def callRPC(self, plugin, func, args, parse):
- if not args: args = tuple()
- if parse:
- args = tuple([literal_eval(x) for x in args])
-
- plugin = self.pluginMap[plugin]
- f = getattr(plugin, func)
- return f(*args)
-
-
- def createIndex(self):
- plugins = []
-
- active = []
- deactive = []
-
- for pluginname in self.core.pluginManager.hookPlugins:
- try:
- #hookClass = getattr(plugin, plugin.__name__)
-
- if self.core.config.getPlugin(pluginname, "activated"):
- pluginClass = self.core.pluginManager.loadClass("hooks", pluginname)
- if not pluginClass: continue
-
- plugin = pluginClass(self.core, self)
- plugins.append(plugin)
- self.pluginMap[pluginClass.__name__] = plugin
- if plugin.isActivated():
- active.append(pluginClass.__name__)
- else:
- deactive.append(pluginname)
-
-
- except:
- self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
- if self.core.debug:
- traceback.print_exc()
-
- self.log.info(_("Activated plugins: %s") % ", ".join(sorted(active)))
- self.log.info(_("Deactivate plugins: %s") % ", ".join(sorted(deactive)))
-
- self.plugins = plugins
-
- def manageHooks(self, plugin, name, value):
- if name == "activated" and value:
- self.activateHook(plugin)
- elif name == "activated" and not value:
- self.deactivateHook(plugin)
-
- def activateHook(self, plugin):
-
- #check if already loaded
- for inst in self.plugins:
- if inst.__name__ == plugin:
- return
-
- pluginClass = self.core.pluginManager.loadClass("hooks", plugin)
-
- if not pluginClass: return
-
- self.log.debug("Plugin loaded: %s" % plugin)
-
- plugin = pluginClass(self.core, self)
- self.plugins.append(plugin)
- self.pluginMap[pluginClass.__name__] = plugin
-
- # call core Ready
- start_new_thread(plugin.coreReady, tuple())
-
- def deactivateHook(self, plugin):
-
- hook = None
- for inst in self.plugins:
- if inst.__name__ == plugin:
- hook = inst
-
- if not hook: return
-
- self.log.debug("Plugin unloaded: %s" % plugin)
-
- hook.unload()
-
- #remove periodic call
- self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(hook.cb))
- self.plugins.remove(hook)
- del self.pluginMap[hook.__name__]
-
-
- @try_catch
- def coreReady(self):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.coreReady()
-
- self.dispatchEvent("coreReady")
-
- @try_catch
- def coreExiting(self):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.coreExiting()
-
- self.dispatchEvent("coreExiting")
-
- @lock
- def downloadPreparing(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.downloadPreparing(pyfile)
-
- self.dispatchEvent("downloadPreparing", pyfile)
-
- @lock
- def downloadFinished(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "downloadFinished" in plugin.__threaded__:
- self.startThread(plugin.downloadFinished, pyfile)
- else:
- plugin.downloadFinished(pyfile)
-
- self.dispatchEvent("downloadFinished", pyfile)
-
- @lock
- @try_catch
- def downloadFailed(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "downloadFailed" in plugin.__threaded__:
- self.startThread(plugin.downloadFinished, pyfile)
- else:
- plugin.downloadFailed(pyfile)
-
- self.dispatchEvent("downloadFailed", pyfile)
-
- @lock
- def packageFinished(self, package):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "packageFinished" in plugin.__threaded__:
- self.startThread(plugin.packageFinished, package)
- else:
- plugin.packageFinished(package)
-
- self.dispatchEvent("packageFinished", package)
-
- @lock
- def beforeReconnecting(self, ip):
- for plugin in self.plugins:
- plugin.beforeReconnecting(ip)
-
- self.dispatchEvent("beforeReconnecting", ip)
-
- @lock
- def afterReconnecting(self, ip):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.afterReconnecting(ip)
-
- self.dispatchEvent("afterReconnecting", ip)
-
- def startThread(self, function, *args, **kwargs):
- t = HookThread(self.core.threadManager, function, args, kwargs)
-
- def activePlugins(self):
- """ returns all active plugins """
- return [x for x in self.plugins if x.isActivated()]
-
- def getAllInfo(self):
- """returns info stored by hook plugins"""
- info = {}
- for name, plugin in self.pluginMap.iteritems():
- if plugin.info:
- #copy and convert so str
- info[name] = dict([(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()])
- return info
-
-
- def getInfo(self, plugin):
- info = {}
- if plugin in self.pluginMap and self.pluginMap[plugin].info:
- info = dict([(x, str(y) if not isinstance(y, basestring) else y)
- for x, y in self.pluginMap[plugin].info.iteritems()])
-
- return info
-
- def addEvent(self, event, func):
- """Adds an event listener for event name"""
- if event in self.events:
- self.events[event].append(func)
- else:
- self.events[event] = [func]
-
- def removeEvent(self, event, func):
- """removes previously added event listener"""
- if event in self.events:
- self.events[event].remove(func)
-
- def dispatchEvent(self, event, *args):
- """dispatches event with args"""
- if event in self.events:
- for f in self.events[event]:
- try:
- f(*args)
- except Exception, e:
- self.log.warning("Error calling event handler %s: %s, %s, %s"
- % (event, f, args, str(e)))
- if self.core.debug:
- traceback.print_exc()
-
diff --git a/module/InitHomeDir.py b/module/InitHomeDir.py
deleted file mode 100644
index 156c9f932..000000000
--- a/module/InitHomeDir.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-
- This modules inits working directories and global variables, pydir and homedir
-"""
-
-from os import makedirs, path, chdir
-from os.path import join
-import sys
-from sys import argv, platform
-
-import __builtin__
-__builtin__.owd = path.abspath("") #original working directory
-__builtin__.pypath = path.abspath(path.join(__file__, "..", ".."))
-
-sys.path.append(join(pypath, "module", "lib"))
-
-homedir = ""
-
-if platform == 'nt':
- homedir = path.expanduser("~")
- if homedir == "~":
- import ctypes
-
- CSIDL_APPDATA = 26
- _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW
- _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND,
- ctypes.c_int,
- ctypes.wintypes.HANDLE,
- ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR]
-
- path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
- result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf)
- homedir = path_buf.value
-else:
- homedir = path.expanduser("~")
-
-__builtin__.homedir = homedir
-
-args = " ".join(argv[1:])
-
-# dirty method to set configdir from commandline arguments
-if "--configdir=" in args:
- pos = args.find("--configdir=")
- end = args.find("-", pos + 12)
-
- if end == -1:
- configdir = args[pos + 12:].strip()
- else:
- configdir = args[pos + 12:end].strip()
-elif path.exists(path.join(pypath, "module", "config", "configdir")):
- f = open(path.join(pypath, "module", "config", "configdir"), "rb")
- c = f.read().strip()
- f.close()
- configdir = path.join(pypath, c)
-else:
- if platform in ("posix", "linux2"):
- configdir = path.join(homedir, ".pyload")
- else:
- configdir = path.join(homedir, "pyload")
-
-if not path.exists(configdir):
- makedirs(configdir, 0700)
-
-__builtin__.configdir = configdir
-chdir(configdir)
-
-#print "Using %s as working directory." % configdir
diff --git a/module/PluginThread.py b/module/PluginThread.py
deleted file mode 100644
index 56c36c778..000000000
--- a/module/PluginThread.py
+++ /dev/null
@@ -1,677 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from Queue import Queue
-from threading import Thread
-from os import listdir, stat
-from os.path import join
-from time import sleep, time, strftime, gmtime
-from traceback import print_exc, format_exc
-from pprint import pformat
-from sys import exc_info, exc_clear
-from copy import copy
-from types import MethodType
-
-from pycurl import error
-
-from PyFile import PyFile
-from plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
-from common.packagetools import parseNames
-from utils import save_join
-from Api import OnlineStatus
-
-class PluginThread(Thread):
- """abstract base class for thread types"""
-
- #----------------------------------------------------------------------
- def __init__(self, manager):
- """Constructor"""
- Thread.__init__(self)
- self.setDaemon(True)
- self.m = manager #thread manager
-
-
- def writeDebugReport(self, pyfile):
- """ writes a
- :return:
- """
-
- dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S"))
- dump = self.getDebugDump(pyfile)
-
- try:
- import zipfile
-
- zip = zipfile.ZipFile(dump_name, "w")
-
- for f in listdir(join("tmp", pyfile.pluginname)):
- try:
- # avoid encoding errors
- zip.write(join("tmp", pyfile.pluginname, f), save_join(pyfile.pluginname, f))
- except:
- pass
-
- info = zipfile.ZipInfo(save_join(pyfile.pluginname, "debug_Report.txt"), gmtime())
- info.external_attr = 0644 << 16L # change permissions
-
- zip.writestr(info, dump)
- zip.close()
-
- if not stat(dump_name).st_size:
- raise Exception("Empty Zipfile")
-
- except Exception, e:
- self.m.log.debug("Error creating zip file: %s" % e)
-
- dump_name = dump_name.replace(".zip", ".txt")
- f = open(dump_name, "wb")
- f.write(dump)
- f.close()
-
- self.m.core.log.info("Debug Report written to %s" % dump_name)
-
- def getDebugDump(self, pyfile):
- dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % (
- self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version__, format_exc())
-
- tb = exc_info()[2]
- stack = []
- while tb:
- stack.append(tb.tb_frame)
- tb = tb.tb_next
-
- for frame in stack[1:]:
- dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name,
- frame.f_code.co_filename,
- frame.f_lineno)
-
- for key, value in frame.f_locals.items():
- dump += "\t%20s = " % key
- try:
- dump += pformat(value) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- del frame
-
- del stack #delete it just to be sure...
-
- dump += "\n\nPLUGIN OBJECT DUMP: \n\n"
-
- for name in dir(pyfile.plugin):
- attr = getattr(pyfile.plugin, name)
- if not name.endswith("__") and type(attr) != MethodType:
- dump += "\t%20s = " % name
- try:
- dump += pformat(attr) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- dump += "\nPYFILE OBJECT DUMP: \n\n"
-
- for name in dir(pyfile):
- attr = getattr(pyfile, name)
- if not name.endswith("__") and type(attr) != MethodType:
- dump += "\t%20s = " % name
- try:
- dump += pformat(attr) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- if pyfile.pluginname in self.m.core.config.plugin:
- dump += "\n\nCONFIG: \n\n"
- dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n"
-
- return dump
-
- def clean(self, pyfile):
- """ set thread unactive and release pyfile """
- self.active = False
- pyfile.release()
-
-
-class DownloadThread(PluginThread):
- """thread for downloading files from 'real' hoster plugins"""
-
- #----------------------------------------------------------------------
- def __init__(self, manager):
- """Constructor"""
- PluginThread.__init__(self, manager)
-
- self.queue = Queue() # job queue
- self.active = False
-
- self.start()
-
- #----------------------------------------------------------------------
- def run(self):
- """run method"""
- pyfile = None
-
- while True:
- del pyfile
- self.active = self.queue.get()
- pyfile = self.active
-
- if self.active == "quit":
- self.active = False
- self.m.threads.remove(self)
- return True
-
- try:
- if not pyfile.hasPlugin(): continue
- #this pyfile was deleted while queueing
-
- pyfile.plugin.checkForSameFiles(starting=True)
- self.m.log.info(_("Download starts: %s" % pyfile.name))
-
- # start download
- self.m.core.hookManager.downloadPreparing(pyfile)
- pyfile.plugin.preprocessing(self)
-
- self.m.log.info(_("Download finished: %s") % pyfile.name)
- self.m.core.hookManager.downloadFinished(pyfile)
- self.m.core.files.checkPackageFinished(pyfile)
-
- except NotImplementedError:
- self.m.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
- pyfile.setStatus("failed")
- pyfile.error = "Plugin does not work"
- self.clean(pyfile)
- continue
-
- except Abort:
- try:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- except:
- pass
-
- pyfile.setStatus("aborted")
-
- self.clean(pyfile)
- continue
-
- except Reconnect:
- self.queue.put(pyfile)
- #pyfile.req.clearCookies()
-
- while self.m.reconnecting.isSet():
- sleep(0.5)
-
- continue
-
- except Retry, e:
- reason = e.args[0]
- self.m.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason})
- self.queue.put(pyfile)
- continue
-
- except Fail, e:
- msg = e.args[0]
-
- if msg == "offline":
- pyfile.setStatus("offline")
- self.m.log.warning(_("Download is offline: %s") % pyfile.name)
- elif msg == "temp. offline":
- pyfile.setStatus("temp. offline")
- self.m.log.warning(_("Download is temporary offline: %s") % pyfile.name)
- else:
- pyfile.setStatus("failed")
- self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
- pyfile.error = msg
-
- self.m.core.hookManager.downloadFailed(pyfile)
- self.clean(pyfile)
- continue
-
- except error, e:
- if len(e.args) == 2:
- code, msg = e.args
- else:
- code = 0
- msg = e.args
-
- self.m.log.debug("pycurl exception %s: %s" % (code, msg))
-
- if code in (7, 18, 28, 52, 56):
- self.m.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry."))
- wait = time() + 60
-
- pyfile.waitUntil = wait
- pyfile.setStatus("waiting")
- while time() < wait:
- sleep(1)
- if pyfile.abort:
- break
-
- if pyfile.abort:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- pyfile.setStatus("aborted")
-
- self.clean(pyfile)
- else:
- self.queue.put(pyfile)
-
- continue
-
- else:
- pyfile.setStatus("failed")
- self.m.log.error("pycurl error %s: %s" % (code, msg))
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- self.m.core.hookManager.downloadFailed(pyfile)
-
- self.clean(pyfile)
- continue
-
- except SkipDownload, e:
- pyfile.setStatus("skipped")
-
- self.m.log.info(
- _("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message})
-
- self.clean(pyfile)
-
- self.m.core.files.checkPackageFinished(pyfile)
-
- self.active = False
- self.m.core.files.save()
-
- continue
-
-
- except Exception, e:
- pyfile.setStatus("failed")
- self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
- pyfile.error = str(e)
-
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- self.m.core.hookManager.downloadFailed(pyfile)
- self.clean(pyfile)
- continue
-
- finally:
- self.m.core.files.save()
- pyfile.checkIfProcessed()
- exc_clear()
-
-
- #pyfile.plugin.req.clean()
-
- self.active = False
- pyfile.finishIfDone()
- self.m.core.files.save()
-
-
- def put(self, job):
- """assing job to thread"""
- self.queue.put(job)
-
-
- def stop(self):
- """stops the thread"""
- self.put("quit")
-
-
-class DecrypterThread(PluginThread):
- """thread for decrypting"""
-
- def __init__(self, manager, pyfile):
- """constructor"""
- PluginThread.__init__(self, manager)
-
- self.active = pyfile
- manager.localThreads.append(self)
-
- pyfile.setStatus("decrypting")
-
- self.start()
-
- def getActiveFiles(self):
- return [self.active]
-
- def run(self):
- """run method"""
-
- pyfile = self.active
- retry = False
-
- try:
- self.m.log.info(_("Decrypting starts: %s") % self.active.name)
- self.active.plugin.preprocessing(self)
-
- except NotImplementedError:
- self.m.log.error(_("Plugin %s is missing a function.") % self.active.pluginname)
- return
-
- except Fail, e:
- msg = e.args[0]
-
- if msg == "offline":
- self.active.setStatus("offline")
- self.m.log.warning(_("Download is offline: %s") % self.active.name)
- else:
- self.active.setStatus("failed")
- self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": msg})
- self.active.error = msg
-
- return
-
- except Abort:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- pyfile.setStatus("aborted")
-
- return
-
- except Retry:
- self.m.log.info(_("Retrying %s") % self.active.name)
- retry = True
- return self.run()
-
- except Exception, e:
- self.active.setStatus("failed")
- self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": str(e)})
- self.active.error = str(e)
-
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- return
-
-
- finally:
- if not retry:
- self.active.release()
- self.active = False
- self.m.core.files.save()
- self.m.localThreads.remove(self)
- exc_clear()
-
-
- #self.m.core.hookManager.downloadFinished(pyfile)
-
-
- #self.m.localThreads.remove(self)
- #self.active.finishIfDone()
- if not retry:
- pyfile.delete()
-
-
-class HookThread(PluginThread):
- """thread for hooks"""
-
- #----------------------------------------------------------------------
- def __init__(self, m, function, args, kwargs):
- """Constructor"""
- PluginThread.__init__(self, m)
-
- self.f = function
- self.args = args
- self.kwargs = kwargs
-
- self.active = []
-
- m.localThreads.append(self)
-
- self.start()
-
- def getActiveFiles(self):
- return self.active
-
- def addActive(self, pyfile):
- """ Adds a pyfile to active list and thus will be displayed on overview"""
- if pyfile not in self.active:
- self.active.append(pyfile)
-
- def finishFile(self, pyfile):
- if pyfile in self.active:
- self.active.remove(pyfile)
-
- pyfile.finishIfDone()
-
- def run(self):
- try:
- try:
- self.kwargs["thread"] = self
- self.f(*self.args, **self.kwargs)
- except TypeError, e:
- #dirty method to filter out exceptions
- if "unexpected keyword argument 'thread'" not in e.args[0]:
- raise
-
- del self.kwargs["thread"]
- self.f(*self.args, **self.kwargs)
- finally:
- local = copy(self.active)
- for x in local:
- self.finishFile(x)
-
- self.m.localThreads.remove(self)
-
-
-class InfoThread(PluginThread):
- def __init__(self, manager, data, pid=-1, rid=-1, add=False):
- """Constructor"""
- PluginThread.__init__(self, manager)
-
- self.data = data
- self.pid = pid # package id
- # [ .. (name, plugin) .. ]
-
- self.rid = rid #result id
- self.add = add #add packages instead of return result
-
- self.cache = [] #accumulated data
-
- self.start()
-
- def run(self):
- """run method"""
-
- plugins = {}
- container = []
-
- for url, plugin in self.data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
-
- # filter out container plugins
- for name in self.m.core.pluginManager.containerPlugins:
- if name in plugins:
- container.extend([(name, url) for url in plugins[name]])
-
- del plugins[name]
-
- #directly write to database
- if self.pid > -1:
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
- self.m.core.files.save()
-
- elif self.add:
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True)
-
- else:
- #generate default result
- result = [(url, 0, 3, url) for url in urls]
-
- self.updateCache(pluginname, result)
-
- packs = parseNames([(name, url) for name, x, y, url in self.cache])
-
- self.m.log.debug("Fetched and generated %d packages" % len(packs))
-
- for k, v in packs:
- self.m.core.api.addPackage(k, v)
-
- #empty cache
- del self.cache[:]
-
- else: #post the results
-
-
- for name, url in container:
- #attach container content
- try:
- data = self.decryptContainer(name, url)
- except:
- print_exc()
- self.m.log.error("Could not decrypt container.")
- data = []
-
- for url, plugin in data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
- self.m.infoResults[self.rid] = {}
-
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True)
-
- #force to process cache
- if self.cache:
- self.updateResult(pluginname, [], True)
-
- else:
- #generate default result
- result = [(url, 0, 3, url) for url in urls]
-
- self.updateResult(pluginname, result, True)
-
- self.m.infoResults[self.rid]["ALL_INFO_FETCHED"] = {}
-
- self.m.timestamp = time() + 5 * 60
-
-
- def updateDB(self, plugin, result):
- self.m.core.files.updateFileInfo(result, self.pid)
-
- def updateResult(self, plugin, result, force=False):
- #parse package name and generate result
- #accumulate results
-
- self.cache.extend(result)
-
- if len(self.cache) >= 20 or force:
- #used for package generating
- tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size))))
- for name, size, status, url in self.cache]
-
- data = parseNames(tmp)
- result = {}
- for k, v in data.iteritems():
- for url, status in v:
- status.packagename = k
- result[url] = status
-
- self.m.setInfoResults(self.rid, result)
-
- self.cache = []
-
- def updateCache(self, plugin, result):
- self.cache.extend(result)
-
- def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None):
- try:
- result = [] #result loaded from cache
- process = [] #urls to process
- for url in urls:
- if url in self.m.infoCache:
- result.append(self.m.infoCache[url])
- else:
- process.append(url)
-
- if result:
- self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname))
- cb(pluginname, result)
-
- if process:
- self.m.log.debug("Run Info Fetching for %s" % pluginname)
- for result in plugin.getInfo(process):
- #result = [ .. (name, size, status, url) .. ]
- if not type(result) == list: result = [result]
-
- for res in result:
- self.m.infoCache[res[3]] = res
-
- cb(pluginname, result)
-
- self.m.log.debug("Finished Info Fetching for %s" % pluginname)
- except Exception, e:
- self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") %
- {"name": pluginname, "err": str(e)})
- if self.m.core.debug:
- print_exc()
-
- # generate default results
- if err:
- result = [(url, 0, 3, url) for url in urls]
- cb(pluginname, result)
-
-
- def decryptContainer(self, plugin, url):
- data = []
- # only works on container plugins
-
- self.m.log.debug("Pre decrypting %s with %s" % (url, plugin))
-
- # dummy pyfile
- pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1)
-
- pyfile.initPlugin()
-
- # little plugin lifecycle
- try:
- pyfile.plugin.setup()
- pyfile.plugin.loadToDisk()
- pyfile.plugin.decrypt(pyfile)
- pyfile.plugin.deleteTmp()
-
- for pack in pyfile.plugin.packages:
- pyfile.plugin.urls.extend(pack[1])
-
- data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls)
-
- self.m.log.debug("Got %d links." % len(data))
-
- except Exception, e:
- self.m.log.debug("Pre decrypting error: %s" % str(e))
- finally:
- pyfile.release()
-
- return data
diff --git a/module/PullEvents.py b/module/PullEvents.py
deleted file mode 100644
index 5ec76765e..000000000
--- a/module/PullEvents.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from time import time
-from module.utils import uniqify
-
-class PullManager():
- def __init__(self, core):
- self.core = core
- self.clients = []
-
- def newClient(self, uuid):
- self.clients.append(Client(uuid))
-
- def clean(self):
- for n, client in enumerate(self.clients):
- if client.lastActive + 30 < time():
- del self.clients[n]
-
- def getEvents(self, uuid):
- events = []
- validUuid = False
- for client in self.clients:
- if client.uuid == uuid:
- client.lastActive = time()
- validUuid = True
- while client.newEvents():
- events.append(client.popEvent().toList())
- break
- if not validUuid:
- self.newClient(uuid)
- events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()]
- return uniqify(events, repr)
-
- def addEvent(self, event):
- for client in self.clients:
- client.addEvent(event)
-
-class Client():
- def __init__(self, uuid):
- self.uuid = uuid
- self.lastActive = time()
- self.events = []
-
- def newEvents(self):
- return len(self.events) > 0
-
- def popEvent(self):
- if not len(self.events):
- return None
- return self.events.pop(0)
-
- def addEvent(self, event):
- self.events.append(event)
-
-class UpdateEvent():
- def __init__(self, itype, iid, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.destination = destination
-
- def toList(self):
- return ["update", self.destination, self.type, self.id]
-
-class RemoveEvent():
- def __init__(self, itype, iid, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.destination = destination
-
- def toList(self):
- return ["remove", self.destination, self.type, self.id]
-
-class InsertEvent():
- def __init__(self, itype, iid, after, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.after = after
- self.destination = destination
-
- def toList(self):
- return ["insert", self.destination, self.type, self.id, self.after]
-
-class ReloadAllEvent():
- def __init__(self, destination):
- assert destination == "queue" or destination == "collector"
- self.destination = destination
-
- def toList(self):
- return ["reload", self.destination]
-
-class AccountUpdateEvent():
- def toList(self):
- return ["account"]
-
-class ConfigUpdateEvent():
- def toList(self):
- return ["config"]
diff --git a/module/PyFile.py b/module/PyFile.py
deleted file mode 100644
index 3dede9360..000000000
--- a/module/PyFile.py
+++ /dev/null
@@ -1,285 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-from module.PullEvents import UpdateEvent
-from module.utils import formatSize, lock
-
-from time import sleep, time
-
-from threading import RLock
-
-statusMap = {
- "finished": 0,
- "offline": 1,
- "online": 2,
- "queued": 3,
- "skipped": 4,
- "waiting": 5,
- "temp. offline": 6,
- "starting": 7,
- "failed": 8,
- "aborted": 9,
- "decrypting": 10,
- "custom": 11,
- "downloading": 12,
- "processing": 13,
- "unknown": 14,
-}
-
-
-def setSize(self, value):
- self._size = int(value)
-
-class PyFile(object):
- """
- Represents a file object at runtime
- """
- __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "pluginname", "packageid",
- "error", "order", "lock", "plugin", "waitUntil", "active", "abort", "statusname",
- "reconnected", "progress", "maxprogress", "pluginmodule", "pluginclass")
-
- def __init__(self, manager, id, url, name, size, status, error, pluginname, package, order):
- self.m = manager
-
- self.id = int(id)
- self.url = url
- self.name = name
- self.size = size
- self.status = status
- self.pluginname = pluginname
- self.packageid = package #should not be used, use package() instead
- self.error = error
- self.order = order
- # database information ends here
-
- self.lock = RLock()
-
- self.plugin = None
- #self.download = None
-
- self.waitUntil = 0 # time() + time to wait
-
- # status attributes
- self.active = False #obsolete?
- self.abort = False
- self.reconnected = False
-
- self.statusname = None
-
- self.progress = 0
- self.maxprogress = 100
-
- self.m.cache[int(id)] = self
-
-
- # will convert all sizes to ints
- size = property(lambda self: self._size, setSize)
-
- def __repr__(self):
- return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname)
-
- @lock
- def initPlugin(self):
- """ inits plugin instance """
- if not self.plugin:
- self.pluginmodule = self.m.core.pluginManager.getPlugin(self.pluginname)
- self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.pluginname))
- self.plugin = self.pluginclass(self)
-
- @lock
- def hasPlugin(self):
- """Thread safe way to determine this file has initialized plugin attribute
-
- :return:
- """
- return hasattr(self, "plugin") and self.plugin
-
- def package(self):
- """ return package instance"""
- return self.m.getPackage(self.packageid)
-
- def setStatus(self, status):
- self.status = statusMap[status]
- self.sync() #@TODO needed aslong no better job approving exists
-
- def setCustomStatus(self, msg, status="processing"):
- self.statusname = msg
- self.setStatus(status)
-
- def getStatusName(self):
- if self.status not in (13, 14) or not self.statusname:
- return self.m.statusMsg[self.status]
- else:
- return self.statusname
-
- def hasStatus(self, status):
- return statusMap[status] == self.status
-
- def sync(self):
- """sync PyFile instance with database"""
- self.m.updateLink(self)
-
- @lock
- def release(self):
- """sync and remove from cache"""
- # file has valid package
- if self.packageid > 0:
- self.sync()
-
- if hasattr(self, "plugin") and self.plugin:
- self.plugin.clean()
- del self.plugin
-
- self.m.releaseLink(self.id)
-
- def delete(self):
- """delete pyfile from database"""
- self.m.deleteLink(self.id)
-
- def toDict(self):
- """return dict with all information for interface"""
- return self.toDbDict()
-
- def toDbDict(self):
- """return data as dict for databse
-
- format:
-
- {
- id: {'url': url, 'name': name ... }
- }
-
- """
- return {
- self.id: {
- 'id': self.id,
- 'url': self.url,
- 'name': self.name,
- 'plugin': self.pluginname,
- 'size': self.getSize(),
- 'format_size': self.formatSize(),
- 'status': self.status,
- 'statusmsg': self.getStatusName(),
- 'package': self.packageid,
- 'error': self.error,
- 'order': self.order
- }
- }
-
- def abortDownload(self):
- """abort pyfile if possible"""
- while self.id in self.m.core.threadManager.processingIds():
- self.abort = True
- if self.plugin and self.plugin.req:
- self.plugin.req.abortDownloads()
- sleep(0.1)
-
- self.abort = False
- if self.hasPlugin() and self.plugin.req:
- self.plugin.req.abortDownloads()
-
- self.release()
-
- def finishIfDone(self):
- """set status to finish and release file if every thread is finished with it"""
-
- if self.id in self.m.core.threadManager.processingIds():
- return False
-
- self.setStatus("finished")
- self.release()
- self.m.checkAllLinksFinished()
- return True
-
- def checkIfProcessed(self):
- self.m.checkAllLinksProcessed(self.id)
-
- def formatWait(self):
- """ formats and return wait time in humanreadable format """
- seconds = self.waitUntil - time()
-
- if seconds < 0: return "00:00:00"
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- def formatSize(self):
- """ formats size to readable format """
- return formatSize(self.getSize())
-
- def formatETA(self):
- """ formats eta to readable format """
- seconds = self.getETA()
-
- if seconds < 0: return "00:00:00"
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- def getSpeed(self):
- """ calculates speed """
- try:
- return self.plugin.req.speed
- except:
- return 0
-
- def getETA(self):
- """ gets established time of arrival"""
- try:
- return self.getBytesLeft() / self.getSpeed()
- except:
- return 0
-
- def getBytesLeft(self):
- """ gets bytes left """
- try:
- return self.plugin.req.size - self.plugin.req.arrived
- except:
- return 0
-
- def getPercent(self):
- """ get % of download """
- if self.status == 12:
- try:
- return self.plugin.req.percent
- except:
- return 0
- else:
- return self.progress
-
- def getSize(self):
- """ get size of download """
- try:
- if self.plugin.req.size:
- return self.plugin.req.size
- else:
- return self.size
- except:
- return self.size
-
- def notifyChange(self):
- e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue")
- self.m.core.pullManager.addEvent(e)
-
- def setProgress(self, value):
- if not value == self.progress:
- self.progress = value
- self.notifyChange()
diff --git a/module/PyPackage.py b/module/PyPackage.py
deleted file mode 100644
index f3be6c886..000000000
--- a/module/PyPackage.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-from module.PullEvents import UpdateEvent
-from module.utils import save_path
-
-class PyPackage():
- """
- Represents a package object at runtime
- """
- def __init__(self, manager, id, name, folder, site, password, queue, order):
- self.m = manager
- self.m.packageCache[int(id)] = self
-
- self.id = int(id)
- self.name = name
- self._folder = folder
- self.site = site
- self.password = password
- self.queue = queue
- self.order = order
- self.setFinished = False
-
- @property
- def folder(self):
- return save_path(self._folder)
-
- def toDict(self):
- """ Returns a dictionary representation of the data.
-
- :return: dict: {id: { attr: value }}
- """
- return {
- self.id: {
- 'id': self.id,
- 'name': self.name,
- 'folder': self.folder,
- 'site': self.site,
- 'password': self.password,
- 'queue': self.queue,
- 'order': self.order,
- 'links': {}
- }
- }
-
- def getChildren(self):
- """get information about contained links"""
- return self.m.getPackageData(self.id)["links"]
-
- def sync(self):
- """sync with db"""
- self.m.updatePackage(self)
-
- def release(self):
- """sync and delete from cache"""
- self.sync()
- self.m.releasePackage(self.id)
-
- def delete(self):
- self.m.deletePackage(self.id)
-
- def notifyChange(self):
- e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue")
- self.m.core.pullManager.addEvent(e)
diff --git a/module/Scheduler.py b/module/Scheduler.py
deleted file mode 100644
index 0bc396b69..000000000
--- a/module/Scheduler.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from time import time
-from heapq import heappop, heappush
-from thread import start_new_thread
-from threading import Lock
-
-class AlreadyCalled(Exception):
- pass
-
-
-class Deferred():
- def __init__(self):
- self.call = []
- self.result = ()
-
- def addCallback(self, f, *cargs, **ckwargs):
- self.call.append((f, cargs, ckwargs))
-
- def callback(self, *args, **kwargs):
- if self.result:
- raise AlreadyCalled
- self.result = (args, kwargs)
- for f, cargs, ckwargs in self.call:
- args += tuple(cargs)
- kwargs.update(ckwargs)
- f(*args ** kwargs)
-
-
-class Scheduler():
- def __init__(self, core):
- self.core = core
-
- self.queue = PriorityQueue()
-
- def addJob(self, t, call, args=[], kwargs={}, threaded=True):
- d = Deferred()
- t += time()
- j = Job(t, call, args, kwargs, d, threaded)
- self.queue.put((t, j))
- return d
-
-
- def removeJob(self, d):
- """
- :param d: defered object
- :return: if job was deleted
- """
- index = -1
-
- for i, j in enumerate(self.queue):
- if j[1].deferred == d:
- index = i
-
- if index >= 0:
- del self.queue[index]
- return True
-
- return False
-
- def work(self):
- while True:
- t, j = self.queue.get()
- if not j:
- break
- else:
- if t <= time():
- j.start()
- else:
- self.queue.put((t, j))
- break
-
-
-class Job():
- def __init__(self, time, call, args=[], kwargs={}, deferred=None, threaded=True):
- self.time = float(time)
- self.call = call
- self.args = args
- self.kwargs = kwargs
- self.deferred = deferred
- self.threaded = threaded
-
- def run(self):
- ret = self.call(*self.args, **self.kwargs)
- if self.deferred is None:
- return
- else:
- self.deferred.callback(ret)
-
- def start(self):
- if self.threaded:
- start_new_thread(self.run, ())
- else:
- self.run()
-
-
-class PriorityQueue():
- """ a non blocking priority queue """
-
- def __init__(self):
- self.queue = []
- self.lock = Lock()
-
- def __iter__(self):
- return iter(self.queue)
-
- def __delitem__(self, key):
- del self.queue[key]
-
- def put(self, element):
- self.lock.acquire()
- heappush(self.queue, element)
- self.lock.release()
-
- def get(self):
- """ return element or None """
- self.lock.acquire()
- try:
- el = heappop(self.queue)
- return el
- except IndexError:
- return None, None
- finally:
- self.lock.release() \ No newline at end of file
diff --git a/module/ThreadManager.py b/module/ThreadManager.py
deleted file mode 100644
index 8937f4a29..000000000
--- a/module/ThreadManager.py
+++ /dev/null
@@ -1,318 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from os.path import exists, join
-import re
-from subprocess import Popen
-from threading import Event, Lock
-from time import sleep, time
-from traceback import print_exc
-from random import choice
-
-import pycurl
-
-import PluginThread
-from module.PyFile import PyFile
-from module.network.RequestFactory import getURL
-from module.utils import freeSpace, lock
-
-
-class ThreadManager:
- """manages the download threads, assign jobs, reconnect etc"""
-
-
- def __init__(self, core):
- """Constructor"""
- self.core = core
- self.log = core.log
-
- self.threads = [] # thread list
- self.localThreads = [] #hook+decrypter threads
-
- self.pause = True
-
- self.reconnecting = Event()
- self.reconnecting.clear()
- self.downloaded = 0 #number of files downloaded since last cleanup
-
- self.lock = Lock()
-
- # some operations require to fetch url info from hoster, so we caching them so it wont be done twice
- # contains a timestamp and will be purged after timeout
- self.infoCache = {}
-
- # pool of ids for online check
- self.resultIDs = 0
-
- # threads which are fetching hoster results
- self.infoResults = {}
- #timeout for cache purge
- self.timestamp = 0
-
- pycurl.global_init(pycurl.GLOBAL_DEFAULT)
-
- for i in range(0, self.core.config.get("download", "max_downloads")):
- self.createThread()
-
-
- def createThread(self):
- """create a download thread"""
-
- thread = PluginThread.DownloadThread(self)
- self.threads.append(thread)
-
- def createInfoThread(self, data, pid):
- """
- start a thread whichs fetches online status and other infos
- data = [ .. () .. ]
- """
- self.timestamp = time() + 5 * 60
-
- PluginThread.InfoThread(self, data, pid)
-
- @lock
- def createResultThread(self, data, add=False):
- """ creates a thread to fetch online status, returns result id """
- self.timestamp = time() + 5 * 60
-
- rid = self.resultIDs
- self.resultIDs += 1
-
- PluginThread.InfoThread(self, data, rid=rid, add=add)
-
- return rid
-
-
- @lock
- def getInfoResult(self, rid):
- """returns result and clears it"""
- self.timestamp = time() + 5 * 60
-
- if rid in self.infoResults:
- data = self.infoResults[rid]
- self.infoResults[rid] = {}
- return data
- else:
- return {}
-
- @lock
- def setInfoResults(self, rid, result):
- self.infoResults[rid].update(result)
-
- def getActiveFiles(self):
- active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)]
-
- for t in self.localThreads:
- active.extend(t.getActiveFiles())
-
- return active
-
- def processingIds(self):
- """get a id list of all pyfiles processed"""
- return [x.id for x in self.getActiveFiles()]
-
-
- def work(self):
- """run all task which have to be done (this is for repetivive call by core)"""
- try:
- self.tryReconnect()
- except Exception, e:
- self.log.error(_("Reconnect Failed: %s") % str(e) )
- self.reconnecting.clear()
- if self.core.debug:
- print_exc()
- self.checkThreadCount()
-
- try:
- self.assignJob()
- except Exception, e:
- self.log.warning("Assign job error", e)
- if self.core.debug:
- print_exc()
-
- sleep(0.5)
- self.assignJob()
- #it may be failed non critical so we try it again
-
- if (self.infoCache or self.infoResults) and self.timestamp < time():
- self.infoCache.clear()
- self.infoResults.clear()
- self.log.debug("Cleared Result cache")
-
- #----------------------------------------------------------------------
- def tryReconnect(self):
- """checks if reconnect needed"""
-
- if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()):
- return False
-
- active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active]
-
- if not (0 < active.count(True) == len(active)):
- return False
-
- if not exists(self.core.config['reconnect']['method']):
- if exists(join(pypath, self.core.config['reconnect']['method'])):
- self.core.config['reconnect']['method'] = join(pypath, self.core.config['reconnect']['method'])
- else:
- self.core.config["reconnect"]["activated"] = False
- self.log.warning(_("Reconnect script not found!"))
- return
-
- self.reconnecting.set()
-
- #Do reconnect
- self.log.info(_("Starting reconnect"))
-
- while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0:
- sleep(0.25)
-
- ip = self.getIP()
-
- self.core.hookManager.beforeReconnecting(ip)
-
- self.log.debug("Old IP: %s" % ip)
-
- try:
- reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE)
- except:
- self.log.warning(_("Failed executing reconnect script!"))
- self.core.config["reconnect"]["activated"] = False
- self.reconnecting.clear()
- if self.core.debug:
- print_exc()
- return
-
- reconn.wait()
- sleep(1)
- ip = self.getIP()
- self.core.hookManager.afterReconnecting(ip)
-
- self.log.info(_("Reconnected, new IP: %s") % ip)
-
- self.reconnecting.clear()
-
- def getIP(self):
- """retrieve current ip"""
- services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"),
- ("http://checkip.dyndns.org/",".*Current IP Address: (\S+)</body>.*")]
-
- ip = ""
- for i in range(10):
- try:
- sv = choice(services)
- ip = getURL(sv[0])
- ip = re.match(sv[1], ip).group(1)
- break
- except:
- ip = ""
- sleep(1)
-
- return ip
-
- #----------------------------------------------------------------------
- def checkThreadCount(self):
- """checks if there are need for increasing or reducing thread count"""
-
- if len(self.threads) == self.core.config.get("download", "max_downloads"):
- return True
- elif len(self.threads) < self.core.config.get("download", "max_downloads"):
- self.createThread()
- else:
- free = [x for x in self.threads if not x.active]
- if free:
- free[0].put("quit")
-
-
- def cleanPycurl(self):
- """ make a global curl cleanup (currently ununused) """
- if self.processingIds():
- return False
- pycurl.global_cleanup()
- pycurl.global_init(pycurl.GLOBAL_DEFAULT)
- self.downloaded = 0
- self.log.debug("Cleaned up pycurl")
- return True
-
- #----------------------------------------------------------------------
- def assignJob(self):
- """assing a job to a thread if possible"""
-
- if self.pause or not self.core.api.isTimeDownload(): return
-
- #if self.downloaded > 20:
- # if not self.cleanPyCurl(): return
-
- free = [x for x in self.threads if not x.active]
-
- inuse = set([(x.active.pluginname,self.getLimit(x)) for x in self.threads if x.active and x.active.hasPlugin() and x.active.plugin.account])
- inuse = map(lambda x : (x[0], x[1], len([y for y in self.threads if y.active and y.active.pluginname == x[0]])) ,inuse)
- onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]]
-
- occ = [x.active.pluginname for x in self.threads if x.active and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit
-
- occ.sort()
- occ = tuple(set(occ))
- job = self.core.files.getJob(occ)
- if job:
- try:
- job.initPlugin()
- except Exception, e:
- self.log.critical(str(e))
- print_exc()
- job.setStatus("failed")
- job.error = str(e)
- job.release()
- return
-
- if job.plugin.__type__ == "hoster":
- spaceLeft = freeSpace(self.core.config["general"]["download_folder"]) / 1024 / 1024
- if spaceLeft < self.core.config["general"]["min_free_space"]:
- self.log.warning(_("Not enough space left on device"))
- self.pause = True
-
- if free and not self.pause:
- thread = free[0]
- #self.downloaded += 1
-
- thread.put(job)
- else:
- #put job back
- if occ not in self.core.files.jobCache:
- self.core.files.jobCache[occ] = []
- self.core.files.jobCache[occ].append(job.id)
-
- #check for decrypt jobs
- job = self.core.files.getDecryptJob()
- if job:
- job.initPlugin()
- thread = PluginThread.DecrypterThread(self, job)
-
-
- else:
- thread = PluginThread.DecrypterThread(self, job)
-
- def getLimit(self, thread):
- limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL",["0"])[0]
- return int(limit)
-
- def cleanup(self):
- """do global cleanup, should be called when finished with pycurl"""
- pycurl.global_cleanup()
diff --git a/module/cli/AddPackage.py b/module/cli/AddPackage.py
deleted file mode 100644
index a73401586..000000000
--- a/module/cli/AddPackage.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-
-from Handler import Handler
-from printer import *
-
-class AddPackage(Handler):
- """ let the user add packages """
-
- def init(self):
- self.name = ""
- self.urls = []
-
- def onEnter(self, inp):
- if inp == "0":
- self.cli.reset()
-
- if not self.name:
- self.name = inp
- self.setInput()
- elif inp == "END":
- #add package
- self.client.addPackage(self.name, self.urls, 1)
- self.cli.reset()
- else:
- if inp.strip():
- self.urls.append(inp)
- self.setInput()
-
- def renderBody(self, line):
- println(line, white(_("Add Package:")))
- println(line + 1, "")
- line += 2
-
- if not self.name:
- println(line, _("Enter a name for the new package"))
- println(line + 1, "")
- line += 2
- else:
- println(line, _("Package: %s") % self.name)
- println(line + 1, _("Parse the links you want to add."))
- println(line + 2, _("Type %s when done.") % mag("END"))
- println(line + 3, _("Links added: ") + mag(len(self.urls)))
- line += 4
-
- println(line, "")
- println(line + 1, mag("0.") + _(" back to main menu"))
-
- return line + 2 \ No newline at end of file
diff --git a/module/cli/Handler.py b/module/cli/Handler.py
deleted file mode 100644
index 476d09386..000000000
--- a/module/cli/Handler.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-class Handler:
- def __init__(self, cli):
- self.cli = cli
- self.init()
-
- client = property(lambda self: self.cli.client)
- input = property(lambda self: self.cli.input)
-
- def init(self):
- pass
-
- def onChar(self, char):
- pass
-
- def onBackSpace(self):
- pass
-
- def onEnter(self, inp):
- pass
-
- def setInput(self, inp=""):
- self.cli.setInput(inp)
-
- def backspace(self):
- self.cli.setInput(self.input[:-1])
-
- def renderBody(self, line):
- """ gets the line where to render output and should return the line number below its content """
- return line + 1 \ No newline at end of file
diff --git a/module/cli/ManageFiles.py b/module/cli/ManageFiles.py
deleted file mode 100644
index 4d0377d9d..000000000
--- a/module/cli/ManageFiles.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-
-from itertools import islice
-from time import time
-
-from Handler import Handler
-from printer import *
-
-from module.Api import Destination, PackageData
-
-class ManageFiles(Handler):
- """ possibility to manage queue/collector """
-
- def init(self):
- self.target = Destination.Queue
- self.pos = 0 #position in queue
- self.package = -1 #choosen package
- self.mode = "" # move/delete/restart
-
- self.cache = None
- self.links = None
- self.time = 0
-
- def onChar(self, char):
- if char in ("m", "d", "r"):
- self.mode = char
- self.setInput()
- elif char == "p":
- self.pos = max(0, self.pos - 5)
- self.backspace()
- elif char == "n":
- self.pos += 5
- self.backspace()
-
- def onBackSpace(self):
- if not self.input and self.mode:
- self.mode = ""
- if not self.input and self.package > -1:
- self.package = -1
-
- def onEnter(self, input):
- if input == "0":
- self.cli.reset()
- elif self.package < 0 and self.mode:
- #mode select
- packs = self.parseInput(input)
- if self.mode == "m":
- [self.client.movePackage((self.target + 1) % 2, x) for x in packs]
- elif self.mode == "d":
- self.client.deletePackages(packs)
- elif self.mode == "r":
- [self.client.restartPackage(x) for x in packs]
-
- elif self.mode:
- #edit links
- links = self.parseInput(input, False)
-
- if self.mode == "d":
- self.client.deleteFiles(links)
- elif self.mode == "r":
- map(self.client.restartFile, links)
-
- else:
- #look into package
- try:
- self.package = int(input)
- except:
- pass
-
- self.cache = None
- self.links = None
- self.pos = 0
- self.mode = ""
- self.setInput()
-
-
- def renderBody(self, line):
- if self.package < 0:
- println(line, white(_("Manage Packages:")))
- else:
- println(line, white((_("Manage Links:"))))
- line += 1
-
- if self.mode:
- if self.mode == "m":
- println(line, _("What do you want to move?"))
- elif self.mode == "d":
- println(line, _("What do you want to delete?"))
- elif self.mode == "r":
- println(line, _("What do you want to restart?"))
-
- println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1,2,3 or 1-3.")
- line += 2
- else:
- println(line, _("Choose what yout want to do or enter package number."))
- println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % (
- _("delete"), _("move"), _("restart")))
- line += 2
-
- if self.package < 0:
- #print package info
- pack = self.getPackages()
- i = 0
- for value in islice(pack, self.pos, self.pos + 5):
- try:
- println(line, mag(str(value.pid)) + ": " + value.name)
- line += 1
- i += 1
- except Exception, e:
- pass
- for x in range(5 - i):
- println(line, "")
- line += 1
- else:
- #print links info
- pack = self.getLinks()
- i = 0
- for value in islice(pack.links, self.pos, self.pos + 5):
- try:
- println(line, mag(value.fid) + ": %s | %s | %s" % (
- value.name, value.statusmsg, value.plugin))
- line += 1
- i += 1
- except Exception, e:
- pass
- for x in range(5 - i):
- println(line, "")
- line += 1
-
- println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next"))
- println(line + 1, mag("0.") + _(" back to main menu"))
-
- return line + 2
-
-
- def getPackages(self):
- if self.cache and self.time + 2 < time():
- return self.cache
-
- if self.target == Destination.Queue:
- data = self.client.getQueue()
- else:
- data = self.client.getCollector()
-
-
- self.cache = data
- self.time = time()
-
- return data
-
- def getLinks(self):
- if self.links and self.time + 1 < time():
- return self.links
-
- try:
- data = self.client.getPackageData(self.package)
- except:
- data = PackageData(links=[])
-
- self.links = data
- self.time = time()
-
- return data
-
- def parseInput(self, inp, package=True):
- inp = inp.strip()
- if "-" in inp:
- l, n, h = inp.partition("-")
- l = int(l)
- h = int(h)
- r = range(l, h + 1)
-
- ret = []
- if package:
- for p in self.cache:
- if p.pid in r:
- ret.append(p.pid)
- else:
- for l in self.links.links:
- if l.lid in r:
- ret.append(l.lid)
-
- return ret
-
- else:
- return [int(x) for x in inp.split(",")]
diff --git a/module/cli/__init__.py b/module/cli/__init__.py
deleted file mode 100644
index fa8a09291..000000000
--- a/module/cli/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from AddPackage import AddPackage
-from ManageFiles import ManageFiles \ No newline at end of file
diff --git a/module/cli/printer.py b/module/cli/printer.py
deleted file mode 100644
index c62c1800e..000000000
--- a/module/cli/printer.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-def blue(string):
- return "\033[1;34m" + unicode(string) + "\033[0m"
-
-def green(string):
- return "\033[1;32m" + unicode(string) + "\033[0m"
-
-def yellow(string):
- return "\033[1;33m" + unicode(string) + "\033[0m"
-
-def red(string):
- return "\033[1;31m" + unicode(string) + "\033[0m"
-
-def cyan(string):
- return "\033[1;36m" + unicode(string) + "\033[0m"
-
-def mag(string):
- return "\033[1;35m" + unicode(string) + "\033[0m"
-
-def white(string):
- return "\033[1;37m" + unicode(string) + "\033[0m"
-
-def println(line, content):
- print "\033[" + str(line) + ";0H\033[2K" + content \ No newline at end of file
diff --git a/module/common/APIExerciser.py b/module/common/APIExerciser.py
deleted file mode 100644
index 96f5ce9cf..000000000
--- a/module/common/APIExerciser.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import string
-from threading import Thread
-from random import choice, random, sample, randint
-from time import time, sleep
-from math import floor
-import gc
-
-from traceback import print_exc, format_exc
-
-from module.remote.thriftbackend.ThriftClient import ThriftClient, Destination
-
-def createURLs():
- """ create some urls, some may fail """
- urls = []
- for x in range(0, randint(20, 100)):
- name = "DEBUG_API"
- if randint(0, 5) == 5:
- name = "" #this link will fail
-
- urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20))))
-
- return urls
-
-AVOID = (0,3,8)
-
-idPool = 0
-sumCalled = 0
-
-
-def startApiExerciser(core, n):
- for i in range(n):
- APIExerciser(core).start()
-
-class APIExerciser(Thread):
-
-
- def __init__(self, core, thrift=False, user=None, pw=None):
- global idPool
-
- Thread.__init__(self)
- self.setDaemon(True)
- self.core = core
- self.count = 0 #number of methods
- self.time = time()
-
- if thrift:
- self.api = ThriftClient(user=user, password=pw)
- else:
- self.api = core.api
-
-
- self.id = idPool
-
- idPool += 1
-
- #self.start()
-
- def run(self):
-
- self.core.log.info("API Excerciser started %d" % self.id)
-
- out = open("error.log", "ab")
- #core errors are not logged of course
- out.write("\n" + "Starting\n")
- out.flush()
-
- while True:
- try:
- self.testAPI()
- except Exception:
- self.core.log.error("Excerciser %d throw an execption" % self.id)
- print_exc()
- out.write(format_exc() + 2 * "\n")
- out.flush()
-
- if not self.count % 100:
- self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count))
- if not self.count % 1000:
- out.flush()
-
- if not sumCalled % 1000: #not thread safe
- self.core.log.info("Exercisers tested %d api calls" % sumCalled)
- persec = sumCalled / (time() - self.time)
- self.core.log.info("Approx. %.2f calls per second." % persec)
- self.core.log.info("Approx. %.2f ms per call." % (1000 / persec))
- self.core.log.info("Collected garbage: %d" % gc.collect())
-
-
- #sleep(random() / 500)
-
- def testAPI(self):
- global sumCalled
-
- m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles",
- "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting",
- "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices" , "getAccounts", "getAllUserData"]
-
- method = choice(m)
- #print "Testing:", method
-
- if hasattr(self, method):
- res = getattr(self, method)()
- else:
- res = getattr(self.api, method)()
-
- self.count += 1
- sumCalled += 1
-
- #print res
-
- def addPackage(self):
- name = "".join(sample(string.ascii_letters, 10))
- urls = createURLs()
-
- self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]))
-
-
- def deleteFiles(self):
- info = self.api.getQueueData()
- if not info: return
-
- pack = choice(info)
- fids = pack.links
-
- if len(fids):
- fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))]
- self.api.deleteFiles(fids)
-
-
- def deletePackages(self):
- info = choice([self.api.getQueue(), self.api.getCollector()])
- if not info: return
-
- pids = [p.pid for p in info]
- if len(pids):
- pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1)))
- self.api.deletePackages(pids)
-
- def getFileData(self):
- info = self.api.getQueueData()
- if info:
- p = choice(info)
- if p.links:
- self.api.getFileData(choice(p.links).fid)
-
- def getPackageData(self):
- info = self.api.getQueue()
- if info:
- self.api.getPackageData(choice(info).pid)
-
- def getAccounts(self):
- self.api.getAccounts(False)
-
- def getCaptchaTask(self):
- self.api.getCaptchaTask(False) \ No newline at end of file
diff --git a/module/common/ImportDebugger.py b/module/common/ImportDebugger.py
deleted file mode 100644
index a997f7b0c..000000000
--- a/module/common/ImportDebugger.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-
-class ImportDebugger(object):
-
- def __init__(self):
- self.imported = {}
-
- def find_module(self, name, path=None):
-
- if name not in self.imported:
- self.imported[name] = 0
-
- self.imported[name] += 1
-
- print name, path
-
-sys.meta_path.append(ImportDebugger()) \ No newline at end of file
diff --git a/module/common/JsEngine.py b/module/common/JsEngine.py
deleted file mode 100644
index 576be2a1b..000000000
--- a/module/common/JsEngine.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from imp import find_module
-from os.path import join, exists
-from urllib import quote
-
-
-ENGINE = ""
-
-DEBUG = False
-JS = False
-PYV8 = False
-RHINO = False
-
-
-if not ENGINE:
- try:
- import subprocess
-
- subprocess.Popen(["js", "-v"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
- p = subprocess.Popen(["js", "-e", "print(23+19)"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- #integrity check
- if out.strip() == "42":
- ENGINE = "js"
- JS = True
- except:
- pass
-
-if not ENGINE or DEBUG:
- try:
- find_module("PyV8")
- ENGINE = "pyv8"
- PYV8 = True
- except:
- pass
-
-if not ENGINE or DEBUG:
- try:
- path = "" #path where to find rhino
-
- if exists("/usr/share/java/js.jar"):
- path = "/usr/share/java/js.jar"
- elif exists("js.jar"):
- path = "js.jar"
- elif exists(join(pypath, "js.jar")): #may raises an exception, but js.jar wasnt found anyway
- path = join(pypath, "js.jar")
-
- if not path:
- raise Exception
-
- import subprocess
-
- p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", "print(23+19)"],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- #integrity check
- if out.strip() == "42":
- ENGINE = "rhino"
- RHINO = True
- except:
- pass
-
-class JsEngine():
- def __init__(self):
- self.engine = ENGINE
- self.init = False
-
- def __nonzero__(self):
- return False if not ENGINE else True
-
- def eval(self, script):
- if not self.init:
- if ENGINE == "pyv8" or (DEBUG and PYV8):
- import PyV8
- global PyV8
-
- self.init = True
-
- if type(script) == unicode:
- script = script.encode("utf8")
-
- if not ENGINE:
- raise Exception("No JS Engine")
-
- if not DEBUG:
- if ENGINE == "pyv8":
- return self.eval_pyv8(script)
- elif ENGINE == "js":
- return self.eval_js(script)
- elif ENGINE == "rhino":
- return self.eval_rhino(script)
- else:
- results = []
- if PYV8:
- res = self.eval_pyv8(script)
- print "PyV8:", res
- results.append(res)
- if JS:
- res = self.eval_js(script)
- print "JS:", res
- results.append(res)
- if RHINO:
- res = self.eval_rhino(script)
- print "Rhino:", res
- results.append(res)
-
- warning = False
- for x in results:
- for y in results:
- if x != y:
- warning = True
-
- if warning: print "### WARNING ###: Different results"
-
- return results[0]
-
- def eval_pyv8(self, script):
- rt = PyV8.JSContext()
- rt.enter()
- return rt.eval(script)
-
- def eval_js(self, script):
- script = "print(eval(unescape('%s')))" % quote(script)
- p = subprocess.Popen(["js", "-e", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1)
- out, err = p.communicate()
- res = out.strip()
- return res
-
- def eval_rhino(self, script):
- script = "print(eval(unescape('%s')))" % quote(script)
- p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", script],
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1)
- out, err = p.communicate()
- res = out.strip()
- return res.decode("utf8").encode("ISO-8859-1")
-
- def error(self):
- return _("No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino")
-
-if __name__ == "__main__":
- js = JsEngine()
-
- test = u'"Ì"+"À"'
- js.eval(test) \ No newline at end of file
diff --git a/module/common/__init__.py b/module/common/__init__.py
deleted file mode 100644
index de6d13128..000000000
--- a/module/common/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-__author__ = 'christian'
- \ No newline at end of file
diff --git a/module/common/json_layer.py b/module/common/json_layer.py
deleted file mode 100644
index 4d57a9f38..000000000
--- a/module/common/json_layer.py
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# abstraction layer for json operations
-
-try: # since python 2.6
- import json
- from json import loads as json_loads
- from json import dumps as json_dumps
-except ImportError: #use system simplejson if available
- import simplejson as json
- from simplejson import loads as json_loads
- from simplejson import dumps as json_dumps
diff --git a/module/common/packagetools.py b/module/common/packagetools.py
deleted file mode 100644
index 5bfbcba95..000000000
--- a/module/common/packagetools.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python
-
-# JDownloader/src/jd/controlling/LinkGrabberPackager.java
-
-import re
-from urlparse import urlparse
-
-def matchFirst(string, *args):
- """ matches against list of regexp and returns first match"""
- for patternlist in args:
- for pattern in patternlist:
- r = pattern.search(string)
- if r is not None:
- name = r.group(1)
- return name
-
- return string
-
-
-def parseNames(files):
- """ Generates packages names from name, data lists
-
- :param files: list of (name, data)
- :return: packagenames mapt to data lists (eg. urls)
- """
- packs = {}
-
- endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)"
-
- rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I),
- re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I),
- re.compile("(.*)\\.rar$", re.I),
- re.compile("(.*)\\.r\\d+$", re.I),
- re.compile("(.*)(\\.|_|-)\\d+$", re.I)]
-
- zipPats = [re.compile("(.*)\\.zip$", re.I),
- re.compile("(.*)\\.z\\d+$", re.I),
- re.compile("(?is).*\\.7z\\.[\\d]+$", re.I),
- re.compile("(.*)\\.a.$", re.I)]
-
- ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"),
- re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)]
-
- iszPats = [re.compile("(.*)\\.isz$", re.I),
- re.compile("(.*)\\.i\\d{2}$", re.I)]
-
- pat1 = re.compile("(\\.?CD\\d+)", re.I)
- pat2 = re.compile("(\\.?part\\d+)", re.I)
-
- pat3 = re.compile("(.+)[\\.\\-_]+$")
- pat4 = re.compile("(.+)\\.\\d+\\.xtm$")
-
- for file, url in files:
- patternMatch = False
-
- if file is None:
- continue
-
- # remove trailing /
- name = file.rstrip('/')
-
- # extract last path part .. if there is a path
- split = name.rsplit("/", 1)
- if len(split) > 1:
- name = split.pop(1)
-
- #check if a already existing package may be ok for this file
- # found = False
- # for pack in packs:
- # if pack in file:
- # packs[pack].append(url)
- # found = True
- # break
- #
- # if found: continue
-
- # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern
- before = name
- name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats)
- if before != name:
- patternMatch = True
-
- # xtremsplit pattern
- r = pat4.search(name)
- if r is not None:
- name = r.group(1)
-
- # remove part and cd pattern
- r = pat1.search(name)
- if r is not None:
- name = name.replace(r.group(0), "")
- patternMatch = True
-
- r = pat2.search(name)
- if r is not None:
- name = name.replace(r.group(0), "")
- patternMatch = True
-
- # additional checks if extension pattern matched
- if patternMatch:
- # remove extension
- index = name.rfind(".")
- if index <= 0:
- index = name.rfind("_")
- if index > 0:
- length = len(name) - index
- if length <= 4:
- name = name[:-length]
-
- # remove endings like . _ -
- r = pat3.search(name)
- if r is not None:
- name = r.group(1)
-
- # replace . and _ with space
- name = name.replace(".", " ")
- name = name.replace("_", " ")
-
- name = name.strip()
- else:
- name = ""
-
- # fallback: package by hoster
- if not name:
- name = urlparse(file).hostname
- if name: name = name.replace("www.", "")
-
- # fallback : default name
- if not name:
- name = "unknown"
-
- # build mapping
- if name in packs:
- packs[name].append(url)
- else:
- packs[name] = [url]
-
- return packs
-
-
-if __name__ == "__main__":
- from os.path import join
- from pprint import pprint
-
- f = open(join("..", "..", "testlinks2.txt"), "rb")
- urls = [(x.strip(), x.strip()) for x in f.readlines() if x.strip()]
- f.close()
-
- print "Having %d urls." % len(urls)
-
- packs = parseNames(urls)
-
- pprint(packs)
-
- print "Got %d urls." % sum([len(x) for x in packs.itervalues()])
diff --git a/module/common/pylgettext.py b/module/common/pylgettext.py
deleted file mode 100644
index fb36fecee..000000000
--- a/module/common/pylgettext.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from gettext import *
-
-_searchdirs = None
-
-origfind = find
-
-def setpaths(pathlist):
- global _searchdirs
- if isinstance(pathlist, list):
- _searchdirs = pathlist
- else:
- _searchdirs = list(pathlist)
-
-
-def addpath(path):
- global _searchdirs
- if _searchdirs is None:
- _searchdirs = list(path)
- else:
- if path not in _searchdirs:
- _searchdirs.append(path)
-
-
-def delpath(path):
- global _searchdirs
- if _searchdirs is not None:
- if path in _searchdirs:
- _searchdirs.remove(path)
-
-
-def clearpath():
- global _searchdirs
- if _searchdirs is not None:
- _searchdirs = None
-
-
-def find(domain, localedir=None, languages=None, all=False):
- if _searchdirs is None:
- return origfind(domain, localedir, languages, all)
- searches = [localedir] + _searchdirs
- results = list()
- for dir in searches:
- res = origfind(domain, dir, languages, all)
- if all is False:
- results.append(res)
- else:
- results.extend(res)
- if all is False:
- results = filter(lambda x: x is not None, results)
- if len(results) == 0:
- return None
- else:
- return results[0]
- else:
- return results
-
-#Is there a smarter/cleaner pythonic way for this?
-translation.func_globals['find'] = find
diff --git a/module/config/default.conf b/module/config/default.conf
deleted file mode 100644
index 2e9152ba2..000000000
--- a/module/config/default.conf
+++ /dev/null
@@ -1,65 +0,0 @@
-version: 1
-
-remote - "Remote":
- int port : "Port" = 7227
- ip listenaddr : "Adress" = 0.0.0.0
- bool nolocalauth : "No authentication on local connections" = True
- bool activated : "Activated" = True
-ssl - "SSL":
- bool activated : "Activated"= False
- file cert : "SSL Certificate" = ssl.crt
- file key : "SSL Key" = ssl.key
-webinterface - "Webinterface":
- bool activated : "Activated" = True
- builtin;threaded;fastcgi;lightweight server : "Server" = builtin
- bool https : "Use HTTPS" = False
- ip host : "IP" = 0.0.0.0
- int port : "Port" = 8001
- str template : "Template" = default
- str prefix: "Path Prefix" =
-log - "Log":
- bool file_log : "File Log" = True
- folder log_folder : "Folder" = Logs
- int log_count : "Count" = 5
- int log_size : "Size in kb" = 100
- bool log_rotate : "Log Rotate" = True
-general - "General":
- en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR language : "Language" = en
- folder download_folder : "Download Folder" = Downloads
- bool debug_mode : "Debug Mode" = False
- bool checksum : "Use Checksum" = False
- int min_free_space : "Min Free Space (MB)" = 200
- bool folder_per_package : "Create folder for each package" = True
- int renice : "CPU Priority" = 0
-download - "Download":
- int chunks : "Max connections for one download" = 3
- int max_downloads : "Max Parallel Downloads" = 3
- int max_speed : "Max Download Speed in kb/s" = -1
- bool limit_speed : "Limit Download Speed" = False
- str interface : "Download interface to bind (ip or Name)" = None
- bool ipv6 : "Allow IPv6" = False
- bool skip_existing : "Skip already existing files" = False
-permission - "Permissions":
- bool change_user : "Change user of running process" = False
- str user : "Username" = user
- str folder : "Folder Permission mode" = 0755
- bool change_file : "Change file mode of downloads" = False
- str file : "Filemode for Downloads" = 0644
- bool change_group : "Change group of running process" = False
- str group : "Groupname" = users
- bool change_dl : "Change Group and User of Downloads" = False
-reconnect - "Reconnect":
- bool activated : "Use Reconnect" = False
- str method : "Method" = None
- time startTime : "Start" = 0:00
- time endTime : "End" = 0:00
-downloadTime - "Download Time":
- time start : "Start" = 0:00
- time end : "End" = 0:00
-proxy - "Proxy":
- str address : "Address" = "localhost"
- int port : "Port" = 7070
- http;socks4;socks5 type : "Protocol" = http
- str username : "Username" = None
- password password : "Password" = None
- bool proxy : "Use Proxy" = False
diff --git a/module/config/gui_default.xml b/module/config/gui_default.xml
deleted file mode 100644
index 1faed776f..000000000
--- a/module/config/gui_default.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" ?>
-<root>
- <connections>
- <connection default="True" type="local" id="33965310e19b4a869112c43b39a16440">
- <name>Local</name>
- </connection>
- </connections>
- <mainWindow>
- <state></state>
- <geometry></geometry>
- </mainWindow>
- <language>en</language>
-</root>
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py
deleted file mode 100644
index 9530390c3..000000000
--- a/module/database/DatabaseBackend.py
+++ /dev/null
@@ -1,352 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-from threading import Thread
-from threading import Event
-from os import remove
-from os.path import exists
-from shutil import move
-
-from Queue import Queue
-from traceback import print_exc
-
-from module.utils import chmod
-
-try:
- from pysqlite2 import dbapi2 as sqlite3
-except:
- import sqlite3
-
-DB_VERSION = 4
-
-class style():
- db = None
-
- @classmethod
- def setDB(cls, db):
- cls.db = db
-
- @classmethod
- def inner(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return f(cls.db, *args, **kwargs)
- return x
-
- @classmethod
- def queue(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return cls.db.queue(f, *args, **kwargs)
- return x
-
- @classmethod
- def async(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return cls.db.async(f, *args, **kwargs)
- return x
-
-class DatabaseJob():
- def __init__(self, f, *args, **kwargs):
- self.done = Event()
-
- self.f = f
- self.args = args
- self.kwargs = kwargs
-
- self.result = None
- self.exception = False
-
-# import inspect
-# self.frame = inspect.currentframe()
-
- def __repr__(self):
- from os.path import basename
- frame = self.frame.f_back
- output = ""
- for i in range(5):
- output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name)
- frame = frame.f_back
- del frame
- del self.frame
-
- return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result)
-
- def processJob(self):
- try:
- self.result = self.f(*self.args, **self.kwargs)
- except Exception, e:
- print_exc()
- try:
- print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e
- except:
- pass
-
- self.exception = e
- finally:
- self.done.set()
-
- def wait(self):
- self.done.wait()
-
-class DatabaseBackend(Thread):
- subs = []
- def __init__(self, core):
- Thread.__init__(self)
- self.setDaemon(True)
- self.core = core
-
- self.jobs = Queue()
-
- self.setuplock = Event()
-
- style.setDB(self)
-
- def setup(self):
- self.start()
- self.setuplock.wait()
-
- def run(self):
- """main loop, which executes commands"""
- convert = self._checkVersion() #returns None or current version
-
- self.conn = sqlite3.connect("files.db")
- chmod("files.db", 0600)
-
- self.c = self.conn.cursor() #compatibility
-
- if convert is not None:
- self._convertDB(convert)
-
- self._createTables()
- self._migrateUser()
-
- self.conn.commit()
-
- self.setuplock.set()
-
- while True:
- j = self.jobs.get()
- if j == "quit":
- self.c.close()
- self.conn.close()
- break
- j.processJob()
-
- @style.queue
- def shutdown(self):
- self.conn.commit()
- self.jobs.put("quit")
-
- def _checkVersion(self):
- """ check db version and delete it if needed"""
- if not exists("files.version"):
- f = open("files.version", "wb")
- f.write(str(DB_VERSION))
- f.close()
- return
-
- f = open("files.version", "rb")
- v = int(f.read().strip())
- f.close()
- if v < DB_VERSION:
- if v < 2:
- try:
- self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version."))
- except:
- print "Filedatabase was deleted due to incompatible version."
- remove("files.version")
- move("files.db", "files.backup.db")
- f = open("files.version", "wb")
- f.write(str(DB_VERSION))
- f.close()
- return v
-
- def _convertDB(self, v):
- try:
- getattr(self, "_convertV%i" % v)()
- except:
- try:
- self.core.log.error(_("Filedatabase could NOT be converted."))
- except:
- print "Filedatabase could NOT be converted."
-
- #--convert scripts start
-
- def _convertV2(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
- try:
- self.manager.core.log.info(_("Database was converted from v2 to v3."))
- except:
- print "Database was converted from v2 to v3."
- self._convertV3()
-
- def _convertV3(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
- try:
- self.manager.core.log.info(_("Database was converted from v3 to v4."))
- except:
- print "Database was converted from v3 to v4."
-
- #--convert scripts end
-
- def _createTables(self):
- """create tables for database"""
-
- self.c.execute('CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)')
- self.c.execute('CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))')
- self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)')
- self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
- self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
-
- self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \
- SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\
- FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\
- (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \
- FROM packages p JOIN links l ON p.id = l.package AND l.status in (0,4,13) GROUP BY p.id) s ON s.id = p.id \
- GROUP BY p.id')
-
- #try to lower ids
- self.c.execute('SELECT max(id) FROM LINKS')
- fid = self.c.fetchone()[0]
- if fid:
- fid = int(fid)
- else:
- fid = 0
- self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links"))
-
-
- self.c.execute('SELECT max(id) FROM packages')
- pid = self.c.fetchone()[0]
- if pid:
- pid = int(pid)
- else:
- pid = 0
- self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages"))
-
- self.c.execute('VACUUM')
-
-
- def _migrateUser(self):
- if exists("pyload.db"):
- try:
- self.core.log.info(_("Converting old Django DB"))
- except:
- print "Converting old Django DB"
- conn = sqlite3.connect('pyload.db')
- c = conn.cursor()
- c.execute("SELECT username, password, email from auth_user WHERE is_superuser")
- users = []
- for r in c:
- pw = r[1].split("$")
- users.append((r[0], pw[1] + pw[2], r[2]))
- c.close()
- conn.close()
-
- self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users)
- move("pyload.db", "pyload.old.db")
-
- def createCursor(self):
- return self.conn.cursor()
-
- @style.async
- def commit(self):
- self.conn.commit()
-
- @style.queue
- def syncSave(self):
- self.conn.commit()
-
- @style.async
- def rollback(self):
- self.conn.rollback()
-
- def async(self, f, *args, **kwargs):
- args = (self, ) + args
- job = DatabaseJob(f, *args, **kwargs)
- self.jobs.put(job)
-
- def queue(self, f, *args, **kwargs):
- args = (self, ) + args
- job = DatabaseJob(f, *args, **kwargs)
- self.jobs.put(job)
- job.wait()
- return job.result
-
- @classmethod
- def registerSub(cls, klass):
- cls.subs.append(klass)
-
- @classmethod
- def unregisterSub(cls, klass):
- cls.subs.remove(klass)
-
- def __getattr__(self, attr):
- for sub in DatabaseBackend.subs:
- if hasattr(sub, attr):
- return getattr(sub, attr)
-
-if __name__ == "__main__":
- db = DatabaseBackend()
- db.setup()
-
- class Test():
- @style.queue
- def insert(db):
- c = db.createCursor()
- for i in range(1000):
- c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar"))
- @style.async
- def insert2(db):
- c = db.createCursor()
- for i in range(1000*1000):
- c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar"))
-
- @style.queue
- def select(db):
- c = db.createCursor()
- for i in range(10):
- res = c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", ("foo", i))
- print res.fetchone()
-
- @style.queue
- def error(db):
- c = db.createCursor()
- print "a"
- c.execute("SELECT myerror FROM storage WHERE identifier=? AND key=?", ("foo", i))
- print "e"
-
- db.registerSub(Test)
- from time import time
- start = time()
- for i in range(100):
- db.insert()
- end = time()
- print end-start
-
- start = time()
- db.insert2()
- end = time()
- print end-start
-
- db.error()
-
diff --git a/module/database/FileDatabase.py b/module/database/FileDatabase.py
deleted file mode 100644
index 7e7efb028..000000000
--- a/module/database/FileDatabase.py
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-
-from threading import RLock
-from time import time
-
-from module.utils import formatSize, lock
-from module.PullEvents import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
-from module.PyPackage import PyPackage
-from module.PyFile import PyFile
-from module.database import style, DatabaseBackend
-
-try:
- from pysqlite2 import dbapi2 as sqlite3
-except:
- import sqlite3
-
-
-class FileHandler:
- """Handles all request made to obtain information,
- modify status or other request for links or packages"""
-
- def __init__(self, core):
- """Constructor"""
- self.core = core
-
- # translations
- self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"), _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"), _("downloading"), _("processing"), _("unknown")]
-
- self.cache = {} #holds instances for files
- self.packageCache = {} # same for packages
- #@TODO: purge the cache
-
- self.jobCache = {}
-
- self.lock = RLock() #@TODO should be a Lock w/o R
- #self.lock._Verbose__verbose = True
-
- self.filecount = -1 # if an invalid value is set get current value from db
- self.queuecount = -1 #number of package to be loaded
- self.unchanged = False #determines if any changes was made since last call
-
- self.db = self.core.db
-
- def change(func):
- def new(*args):
- args[0].unchanged = False
- args[0].filecount = -1
- args[0].queuecount = -1
- args[0].jobCache = {}
- return func(*args)
- return new
-
- #----------------------------------------------------------------------
- def save(self):
- """saves all data to backend"""
- self.db.commit()
-
- #----------------------------------------------------------------------
- def syncSave(self):
- """saves all data to backend and waits until all data are written"""
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- pyfile.sync()
-
- pypacks = self.packageCache.values()
- for pypack in pypacks:
- pypack.sync()
-
- self.db.syncSave()
-
- @lock
- def getCompleteData(self, queue=1):
- """gets a complete data representation"""
-
- data = self.db.getAllLinks(queue)
- packs = self.db.getAllPackages(queue)
-
- data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()])
-
- for x in self.packageCache.itervalues():
- if x.queue != queue or x.id not in packs: continue
- packs[x.id].update(x.toDict()[x.id])
-
- for key, value in data.iteritems():
- if value["package"] in packs:
- packs[value["package"]]["links"][key] = value
-
- return packs
-
- @lock
- def getInfoData(self, queue=1):
- """gets a data representation without links"""
-
- packs = self.db.getAllPackages(queue)
- for x in self.packageCache.itervalues():
- if x.queue != queue or x.id not in packs: continue
- packs[x.id].update(x.toDict()[x.id])
-
- return packs
-
- @lock
- @change
- def addLinks(self, urls, package):
- """adds links"""
-
- self.core.hookManager.dispatchEvent("linksAdded", urls, package)
-
- data = self.core.pluginManager.parseUrls(urls)
-
- self.db.addLinks(data, package)
- self.core.threadManager.createInfoThread(data, package)
-
- #@TODO change from reloadAll event to package update event
- self.core.pullManager.addEvent(ReloadAllEvent("collector"))
-
- #----------------------------------------------------------------------
- @lock
- @change
- def addPackage(self, name, folder, queue=0):
- """adds a package, default to link collector"""
- lastID = self.db.addPackage(name, folder, queue)
- p = self.db.getPackage(lastID)
- e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue")
- self.core.pullManager.addEvent(e)
- return lastID
-
- #----------------------------------------------------------------------
- @lock
- @change
- def deletePackage(self, id):
- """delete package and all contained links"""
-
- p = self.getPackage(id)
- if not p:
- if id in self.packageCache: del self.packageCache[id]
- return
-
- oldorder = p.order
- queue = p.queue
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
-
- pyfiles = self.cache.values()
-
- for pyfile in pyfiles:
- if pyfile.packageid == id:
- pyfile.abortDownload()
- pyfile.release()
-
- self.db.deletePackage(p)
- self.core.pullManager.addEvent(e)
- self.core.hookManager.dispatchEvent("packageDeleted", id)
-
- if id in self.packageCache:
- del self.packageCache[id]
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue == queue and pack.order > oldorder:
- pack.order -= 1
- pack.notifyChange()
-
- #----------------------------------------------------------------------
- @lock
- @change
- def deleteLink(self, id):
- """deletes links"""
-
- f = self.getFile(id)
- if not f:
- return None
-
- pid = f.packageid
- e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue")
-
- oldorder = f.order
-
- if id in self.core.threadManager.processingIds():
- self.cache[id].abortDownload()
-
- if id in self.cache:
- del self.cache[id]
-
- self.db.deleteLink(f)
-
- self.core.pullManager.addEvent(e)
-
- p = self.getPackage(pid)
- if not len(p.getChildren()):
- p.delete()
-
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid == pid and pyfile.order > oldorder:
- pyfile.order -= 1
- pyfile.notifyChange()
-
- #----------------------------------------------------------------------
- def releaseLink(self, id):
- """removes pyfile from cache"""
- if id in self.cache:
- del self.cache[id]
-
- #----------------------------------------------------------------------
- def releasePackage(self, id):
- """removes package from cache"""
- if id in self.packageCache:
- del self.packageCache[id]
-
- #----------------------------------------------------------------------
- def updateLink(self, pyfile):
- """updates link"""
- self.db.updateLink(pyfile)
-
- e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue")
- self.core.pullManager.addEvent(e)
-
- #----------------------------------------------------------------------
- def updatePackage(self, pypack):
- """updates a package"""
- self.db.updatePackage(pypack)
-
- e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- #----------------------------------------------------------------------
- def getPackage(self, id):
- """return package instance"""
-
- if id in self.packageCache:
- return self.packageCache[id]
- else:
- return self.db.getPackage(id)
-
- #----------------------------------------------------------------------
- def getPackageData(self, id):
- """returns dict with package information"""
- pack = self.getPackage(id)
-
- if not pack:
- return None
-
- pack = pack.toDict()[id]
-
- data = self.db.getPackageData(id)
-
- tmplist = []
-
- cache = self.cache.values()
- for x in cache:
- if int(x.toDbDict()[x.id]["package"]) == int(id):
- tmplist.append((x.id, x.toDbDict()[x.id]))
- data.update(tmplist)
-
- pack["links"] = data
-
- return pack
-
- #----------------------------------------------------------------------
- def getFileData(self, id):
- """returns dict with file information"""
- if id in self.cache:
- return self.cache[id].toDbDict()
-
- return self.db.getLinkData(id)
-
- #----------------------------------------------------------------------
- def getFile(self, id):
- """returns pyfile instance"""
- if id in self.cache:
- return self.cache[id]
- else:
- return self.db.getFile(id)
-
- #----------------------------------------------------------------------
- @lock
- def getJob(self, occ):
- """get suitable job"""
-
- #@TODO clean mess
- #@TODO improve selection of valid jobs
-
- if occ in self.jobCache:
- if self.jobCache[occ]:
- id = self.jobCache[occ].pop()
- if id == "empty":
- pyfile = None
- self.jobCache[occ].append("empty")
- else:
- pyfile = self.getFile(id)
- else:
- jobs = self.db.getJob(occ)
- jobs.reverse()
- if not jobs:
- self.jobCache[occ].append("empty")
- pyfile = None
- else:
- self.jobCache[occ].extend(jobs)
- pyfile = self.getFile(self.jobCache[occ].pop())
-
- else:
- self.jobCache = {} #better not caching to much
- jobs = self.db.getJob(occ)
- jobs.reverse()
- self.jobCache[occ] = jobs
-
- if not jobs:
- self.jobCache[occ].append("empty")
- pyfile = None
- else:
- pyfile = self.getFile(self.jobCache[occ].pop())
-
- #@TODO: maybe the new job has to be approved...
-
-
- #pyfile = self.getFile(self.jobCache[occ].pop())
- return pyfile
-
- @lock
- def getDecryptJob(self):
- """return job for decrypting"""
- if "decrypt" in self.jobCache:
- return None
-
- plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys()
- plugins = str(tuple(plugins))
-
- jobs = self.db.getPluginJob(plugins)
- if jobs:
- return self.getFile(jobs[0])
- else:
- self.jobCache["decrypt"] = "empty"
- return None
-
- def getFileCount(self):
- """returns number of files"""
-
- if self.filecount == -1:
- self.filecount = self.db.filecount(1)
-
- return self.filecount
-
- def getQueueCount(self, force=False):
- """number of files that have to be processed"""
- if self.queuecount == -1 or force:
- self.queuecount = self.db.queuecount(1)
-
- return self.queuecount
-
- def checkAllLinksFinished(self):
- """checks if all files are finished and dispatch event"""
-
- if not self.getQueueCount(True):
- self.core.hookManager.dispatchEvent("allDownloadsFinished")
- self.core.log.debug("All downloads finished")
- return True
-
- return False
-
- def checkAllLinksProcessed(self, fid):
- """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting"""
-
- # reset count so statistic will update (this is called when dl was processed)
- self.resetCount()
-
- if not self.db.processcount(1, fid):
- self.core.hookManager.dispatchEvent("allDownloadsProcessed")
- self.core.log.debug("All downloads processed")
- return True
-
- return False
-
- def resetCount(self):
- self.queuecount = -1
-
- @lock
- @change
- def restartPackage(self, id):
- """restart package"""
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid == id:
- self.restartFile(pyfile.id)
-
- self.db.restartPackage(id)
-
- if id in self.packageCache:
- self.packageCache[id].setFinished = False
-
- e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def restartFile(self, id):
- """ restart file"""
- if id in self.cache:
- self.cache[id].status = 3
- self.cache[id].name = self.cache[id].url
- self.cache[id].error = ""
- self.cache[id].abortDownload()
-
-
- self.db.restartFile(id)
-
- e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def setPackageLocation(self, id, queue):
- """push package to queue"""
-
- p = self.db.getPackage(id)
- oldorder = p.order
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- self.db.clearPackageOrder(p)
-
- p = self.db.getPackage(id)
-
- p.queue = queue
- self.db.updatePackage(p)
-
- self.db.reorderPackage(p, -1, True)
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue != queue and pack.order > oldorder:
- pack.order -= 1
- pack.notifyChange()
-
- self.db.commit()
- self.releasePackage(id)
- p = self.getPackage(id)
-
- e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def reorderPackage(self, id, position):
- p = self.getPackage(id)
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
- self.db.reorderPackage(p, position)
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue != p.queue or pack.order < 0 or pack == p: continue
- if p.order > position:
- if pack.order >= position and pack.order < p.order:
- pack.order += 1
- pack.notifyChange()
- elif p.order < position:
- if pack.order <= position and pack.order > p.order:
- pack.order -= 1
- pack.notifyChange()
-
- p.order = position
- self.db.commit()
-
- e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def reorderFile(self, id, position):
- f = self.getFileData(id)
- f = f[id]
-
- e = RemoveEvent("file", id, "collector" if not self.getPackage(f["package"]).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- self.db.reorderLink(f, position)
-
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid != f["package"] or pyfile.order < 0: continue
- if f["order"] > position:
- if pyfile.order >= position and pyfile.order < f["order"]:
- pyfile.order += 1
- pyfile.notifyChange()
- elif f["order"] < position:
- if pyfile.order <= position and pyfile.order > f["order"]:
- pyfile.order -= 1
- pyfile.notifyChange()
-
- if id in self.cache:
- self.cache[id].order = position
-
- self.db.commit()
-
- e = InsertEvent("file", id, position, "collector" if not self.getPackage(f["package"]).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @change
- def updateFileInfo(self, data, pid):
- """ updates file info (name, size, status, url)"""
- ids = self.db.updateLinkInfo(data)
- e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- def checkPackageFinished(self, pyfile):
- """ checks if package is finished and calls hookmanager """
-
- ids = self.db.getUnfinished(pyfile.packageid)
- if not ids or (pyfile.id in ids and len(ids) == 1):
- if not pyfile.package().setFinished:
- self.core.log.info(_("Package finished: %s") % pyfile.package().name)
- self.core.hookManager.packageFinished(pyfile.package())
- pyfile.package().setFinished = True
-
-
- def reCheckPackage(self, pid):
- """ recheck links in package """
- data = self.db.getPackageData(pid)
-
- urls = []
-
- for pyfile in data.itervalues():
- if pyfile["status"] not in (0, 12, 13):
- urls.append((pyfile["url"], pyfile["plugin"]))
-
- self.core.threadManager.createInfoThread(urls, pid)
-
- @lock
- @change
- def deleteFinishedLinks(self):
- """ deletes finished links and packages, return deleted packages """
-
- old_packs = self.getInfoData(0)
- old_packs.update(self.getInfoData(1))
-
- self.db.deleteFinished()
-
- new_packs = self.db.getAllPackages(0)
- new_packs.update(self.db.getAllPackages(1))
- #get new packages only from db
-
- deleted = []
- for id in old_packs.iterkeys():
- if id not in new_packs:
- deleted.append(id)
- self.deletePackage(int(id))
-
- return deleted
-
- @lock
- @change
- def restartFailed(self):
- """ restart all failed links """
- self.db.restartFailed()
-
-class FileMethods():
- @style.queue
- def filecount(self, queue):
- """returns number of files in queue"""
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?", (queue, ))
- return self.c.fetchone()[0]
-
- @style.queue
- def queuecount(self, queue):
- """ number of files in queue not finished yet"""
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0,4)", (queue, ))
- return self.c.fetchone()[0]
-
- @style.queue
- def processcount(self, queue, fid):
- """ number of files which have to be proccessed """
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2,3,5,7,12) AND l.id != ?", (queue, str(fid)))
- return self.c.fetchone()[0]
-
- @style.inner
- def _nextPackageOrder(self, queue=0):
- self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,))
- max = self.c.fetchone()[0]
- if max is not None:
- return max + 1
- else:
- return 0
-
- @style.inner
- def _nextFileOrder(self, package):
- self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,))
- max = self.c.fetchone()[0]
- if max is not None:
- return max + 1
- else:
- return 0
-
- @style.queue
- def addLink(self, url, name, plugin, package):
- order = self._nextFileOrder(package)
- self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', (url, name, plugin, package, order))
- return self.c.lastrowid
-
- @style.queue
- def addLinks(self, links, package):
- """ links is a list of tupels (url,plugin)"""
- order = self._nextFileOrder(package)
- orders = [order + x for x in range(len(links))]
- links = [(x[0], x[0], x[1], package, o) for x, o in zip(links, orders)]
- self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links)
-
- @style.queue
- def addPackage(self, name, folder, queue):
- order = self._nextPackageOrder(queue)
- self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)', (name, folder, queue, order))
- return self.c.lastrowid
-
- @style.queue
- def deletePackage(self, p):
-
- self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),))
- self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),))
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?', (p.order, p.queue))
-
- @style.queue
- def deleteLink(self, f):
-
- self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),))
- self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?', (f.order, str(f.packageid)))
-
-
- @style.queue
- def getAllLinks(self, q):
- """return information about all links in queue q
-
- q0 queue
- q1 collector
-
- format:
-
- {
- id: {'name': name, ... 'package': id }, ...
- }
-
- """
- self.c.execute('SELECT l.id,l.url,l.name,l.size,l.status,l.error,l.plugin,l.package,l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder', (q,))
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
- @style.queue
- def getAllPackages(self, q):
- """return information about packages in queue q
- (only useful in get all data)
-
- q0 queue
- q1 collector
-
- format:
-
- {
- id: {'name': name ... 'links': {} }, ...
- }
- """
- self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \
- FROM packages p JOIN pstats s ON p.id = s.id \
- WHERE p.queue=? ORDER BY p.packageorder', str(q))
-
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'name': r[1],
- 'folder': r[2],
- 'site': r[3],
- 'password': r[4],
- 'queue': r[5],
- 'order': r[6],
- 'sizetotal': int(r[7]),
- 'sizedone': r[8] if r[8] else 0, #these can be None
- 'linksdone': r[9] if r[9] else 0,
- 'linkstotal': r[10],
- 'links': {}
- }
-
- return data
-
- @style.queue
- def getLinkData(self, id):
- """get link information as dict"""
- self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE id=?', (str(id), ))
- data = {}
- r = self.c.fetchone()
- if not r:
- return None
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
- @style.queue
- def getPackageData(self, id):
- """get data about links for a package"""
- self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE package=? ORDER BY linkorder', (str(id), ))
-
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
-
- @style.async
- def updateLink(self, f):
- self.c.execute('UPDATE links SET url=?,name=?,size=?,status=?,error=?,package=? WHERE id=?', (f.url, f.name, f.size, f.status, f.error, str(f.packageid), str(f.id)))
-
- @style.queue
- def updatePackage(self, p):
- self.c.execute('UPDATE packages SET name=?,folder=?,site=?,password=?,queue=? WHERE id=?', (p.name, p.folder, p.site, p.password, p.queue, str(p.id)))
-
- @style.queue
- def updateLinkInfo(self, data):
- """ data is list of tupels (name, size, status, url) """
- self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1,2,3,14)', data)
- ids = []
- self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data]))
- for r in self.c:
- ids.append(int(r[0]))
- return ids
-
- @style.queue
- def reorderPackage(self, p, position, noMove=False):
- if position == -1:
- position = self._nextPackageOrder(p.queue)
- if not noMove:
- if p.order > position:
- self.c.execute('UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
- elif p.order < position:
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
-
- self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id)))
-
- @style.queue
- def reorderLink(self, f, position):
- """ reorder link with f as dict for pyfile """
- if f["order"] > position:
- self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?', (position, f["order"], f["package"]))
- elif f["order"] < position:
- self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?', (position, f["order"], f["package"]))
-
- self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f["id"]))
-
-
- @style.queue
- def clearPackageOrder(self, p):
- self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id)))
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?', (p.order, p.queue, str(p.id)))
-
- @style.async
- def restartFile(self, id):
- self.c.execute('UPDATE links SET status=3,error="" WHERE id=?', (str(id),))
-
- @style.async
- def restartPackage(self, id):
- self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),))
-
- @style.queue
- def getPackage(self, id):
- """return package instance from id"""
- self.c.execute("SELECT name,folder,site,password,queue,packageorder FROM packages WHERE id=?", (str(id), ))
- r = self.c.fetchone()
- if not r: return None
- return PyPackage(self.manager, id, * r)
-
- #----------------------------------------------------------------------
- @style.queue
- def getFile(self, id):
- """return link instance from id"""
- self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?", (str(id), ))
- r = self.c.fetchone()
- if not r: return None
- return PyFile(self.manager, id, * r)
-
-
- @style.queue
- def getJob(self, occ):
- """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
-
- #@TODO improve this hardcoded method
- pre = "('DLC', 'LinkList', 'SerienjunkiesOrg', 'CCF', 'RSDF')" #plugins which are processed in collector
-
- cmd = "("
- for i, item in enumerate(occ):
- if i: cmd += ", "
- cmd += "'%s'" % item
-
- cmd += ")"
-
- cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre)
-
- self.c.execute(cmd) # very bad!
-
- return [x[0] for x in self.c]
-
- @style.queue
- def getPluginJob(self, plugins):
- """returns pyfile ids with suited plugins"""
- cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins
-
- self.c.execute(cmd) # very bad!
-
- return [x[0] for x in self.c]
-
- @style.queue
- def getUnfinished(self, pid):
- """return list of max length 3 ids with pyfiles in package not finished or processed"""
-
- self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),))
- return [r[0] for r in self.c]
-
- @style.queue
- def deleteFinished(self):
- self.c.execute("DELETE FROM links WHERE status IN (0,4)")
- self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)")
-
- @style.queue
- def restartFailed(self):
- self.c.execute("UPDATE links SET status=3,error='' WHERE status IN (6, 8, 9)")
-
- @style.queue
- def findDuplicates(self, id, folder, filename):
- """ checks if filename exists with different id and same package """
- self.c.execute("SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?", (folder, id, filename))
- return self.c.fetchone()
-
- @style.queue
- def purgeLinks(self):
- self.c.execute("DELETE FROM links;")
- self.c.execute("DELETE FROM packages;")
-
-DatabaseBackend.registerSub(FileMethods)
-
-if __name__ == "__main__":
-
- pypath = "."
- _ = lambda x: x
-
- db = FileHandler(None)
-
- #p = PyFile(db, 5)
- #sleep(0.1)
-
- a = time()
-
- #print db.addPackage("package", "folder" , 1)
-
- pack = db.db.addPackage("package", "folder", 1)
-
- updates = []
-
-
- for x in range(0, 200):
- x = str(x)
- db.db.addLink("http://somehost.com/hoster/file/download?file_id=" + x, x, "BasePlugin", pack)
- updates.append(("new name" + x, 0, 3, "http://somehost.com/hoster/file/download?file_id=" + x))
-
-
- for x in range(0, 100):
- updates.append(("unimportant%s" % x, 0, 3, "a really long non existent url%s" % x))
-
- db.db.commit()
-
- b = time()
- print "adding 200 links, single sql execs, no commit", b-a
-
- print db.getCompleteData(1)
-
- c = time()
-
-
- db.db.updateLinkInfo(updates)
-
- d = time()
-
- print "updates", d-c
-
- print db.getCompleteData(1)
-
-
- e = time()
-
- print "complete data", e-d
diff --git a/module/database/StorageDatabase.py b/module/database/StorageDatabase.py
deleted file mode 100644
index 3ed29625f..000000000
--- a/module/database/StorageDatabase.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from module.database import style
-from module.database import DatabaseBackend
-
-class StorageMethods():
- @style.queue
- def setStorage(db, identifier, key, value):
- db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key))
- if db.c.fetchone() is not None:
- db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key))
- else:
- db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value))
-
- @style.queue
- def getStorage(db, identifier, key=None):
- if key is not None:
- db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key))
- row = db.c.fetchone()
- if row is not None:
- return row[0]
- else:
- db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier, ))
- d = {}
- for row in db.c:
- d[row[0]] = row[1]
- return d
-
- @style.queue
- def delStorage(db, identifier, key):
- db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key))
-
-DatabaseBackend.registerSub(StorageMethods)
diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py
deleted file mode 100644
index 0c781057d..000000000
--- a/module/database/UserDatabase.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from hashlib import sha1
-import random
-
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
-
-class UserMethods():
- @style.queue
- def checkAuth(db, user, password):
- c = db.c
- c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user, ))
- r = c.fetchone()
- if not r:
- return {}
-
- salt = r[2][:5]
- pw = r[2][5:]
- h = sha1(salt + password)
- if h.hexdigest() == pw:
- return {"id": r[0], "name": r[1], "role": r[3],
- "permission": r[4], "template": r[5], "email": r[6]}
- else:
- return {}
-
- @style.queue
- def addUser(db, user, password):
- salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
- h = sha1(salt + password)
- password = salt + h.hexdigest()
-
- c = db.c
- c.execute('SELECT name FROM users WHERE name=?', (user, ))
- if c.fetchone() is not None:
- c.execute('UPDATE users SET password=? WHERE name=?', (password, user))
- else:
- c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
-
-
- @style.queue
- def changePassword(db, user, oldpw, newpw):
- db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user, ))
- r = db.c.fetchone()
- if not r:
- return False
-
- salt = r[2][:5]
- pw = r[2][5:]
- h = sha1(salt + oldpw)
- if h.hexdigest() == pw:
- salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
- h = sha1(salt + newpw)
- password = salt + h.hexdigest()
-
- db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
- return True
-
- return False
-
-
- @style.async
- def setPermission(db, user, perms):
- db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user))
-
- @style.async
- def setRole(db, user, role):
- db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
-
-
- @style.queue
- def listUsers(db):
- db.c.execute('SELECT name FROM users')
- users = []
- for row in db.c:
- users.append(row[0])
- return users
-
- @style.queue
- def getAllUserData(db):
- db.c.execute("SELECT name, permission, role, template, email FROM users")
- user = {}
- for r in db.c:
- user[r[0]] = {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]}
-
- return user
-
- @style.queue
- def removeUser(db, user):
- db.c.execute('DELETE FROM users WHERE name=?', (user, ))
-
-DatabaseBackend.registerSub(UserMethods)
diff --git a/module/database/__init__.py b/module/database/__init__.py
deleted file mode 100644
index 545789c0c..000000000
--- a/module/database/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
-
-from FileDatabase import FileHandler
-from UserDatabase import UserMethods
-from StorageDatabase import StorageMethods \ No newline at end of file
diff --git a/module/debug.py b/module/debug.py
deleted file mode 100644
index 8d1ddd3d0..000000000
--- a/module/debug.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env python
-#coding:utf-8
-
-import re
-import InitHomeDir
-from os import listdir
-
-class Wrapper(object):
- pass
-
-def filter_info(line):
- if "object at 0x" in line:
- return False
- elif " at line " in line:
- return False
- elif " <DownloadThread(" in line:
- return False
- elif "<class '" in line:
- return False
- elif "PyFile " in line:
- return False
- elif " <module '" in line:
- return False
-
- else:
- return True
-
-def appendName(lines, name):
- test = re.compile("^[a-zA-z0-9]+ = ")
-
- for i, line in enumerate(lines):
- if test.match(line):
- lines[i] = name+"."+line
-
- return lines
-
-def initReport():
- reports = []
- for f in listdir("."):
- if f.startswith("debug_"):
- reports.append(f)
-
- for i, f in enumerate(reports):
- print "%s. %s" % (i,f)
-
- choice = raw_input("Choose Report: ")
-
- report = reports[int(choice)]
-
- f = open(report, "rb")
-
- content = f.readlines()
- content = [x.strip() for x in content if x.strip()]
-
- frame = Wrapper()
- plugin = Wrapper()
- pyfile = Wrapper()
-
- frame_c = []
- plugin_c = []
- pyfile_c = []
-
- dest = None
-
- for line in content:
- if line == "FRAMESTACK:":
- dest = frame_c
- continue
- elif line == "PLUGIN OBJECT DUMP:":
- dest = plugin_c
- continue
- elif line == "PYFILE OBJECT DUMP:":
- dest = pyfile_c
- continue
- elif line == "CONFIG:":
- dest = None
-
- if dest is not None:
- dest.append(line)
-
-
- frame_c = filter(filter_info, frame_c)
- plugin_c = filter(filter_info, plugin_c)
- pyfile_c = filter(filter_info, pyfile_c)
-
- frame_c = appendName(frame_c, "frame")
- plugin_c = appendName(plugin_c, "plugin")
- pyfile_c = appendName(pyfile_c, "pyfile")
-
- exec("\n".join(frame_c+plugin_c+pyfile_c) )
-
- return frame, plugin, pyfile
-
-if __name__=='__main__':
- print "No main method, use this module with your python shell" \ No newline at end of file
diff --git a/module/forwarder.py b/module/forwarder.py
deleted file mode 100644
index eacb33c2b..000000000
--- a/module/forwarder.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from sys import argv
-from sys import exit
-
-import socket
-import thread
-
-from traceback import print_exc
-
-class Forwarder():
-
- def __init__(self, extip,extport=9666):
- print "Start portforwarding to %s:%s" % (extip, extport)
- proxy(extip, extport, 9666)
-
-
-def proxy(*settings):
- while True:
- server(*settings)
-
-def server(*settings):
- try:
- dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- dock_socket.bind(("127.0.0.1", settings[2]))
- dock_socket.listen(5)
- while True:
- client_socket = dock_socket.accept()[0]
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.connect((settings[0], settings[1]))
- thread.start_new_thread(forward, (client_socket, server_socket))
- thread.start_new_thread(forward, (server_socket, client_socket))
- except Exception:
- print_exc()
-
-
-def forward(source, destination):
- string = ' '
- while string:
- string = source.recv(1024)
- if string:
- destination.sendall(string)
- else:
- #source.shutdown(socket.SHUT_RD)
- destination.shutdown(socket.SHUT_WR)
-
-if __name__ == "__main__":
- args = argv[1:]
- if not args:
- print "Usage: forwarder.py <remote ip> <remote port>"
- exit()
- if len(args) == 1:
- args.append(9666)
-
- f = Forwarder(args[0], int(args[1]))
- \ No newline at end of file
diff --git a/module/gui/AccountEdit.py b/module/gui/AccountEdit.py
deleted file mode 100644
index b22cfc49f..000000000
--- a/module/gui/AccountEdit.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-class AccountEdit(QWidget):
- """
- account editor widget
- """
-
- def __init__(self):
- QMainWindow.__init__(self)
-
- self.setWindowTitle(_("Edit account"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- self.setLayout(QGridLayout())
- l = self.layout()
-
- typeLabel = QLabel(_("Type"))
- loginLabel = QLabel(_("Login"))
- passwordLabel = QLabel(_("New password"))
- changePw = QCheckBox()
- changePw.setChecked(False)
- self.changePw = changePw
- password = QLineEdit()
- password.setEnabled(False)
- password.setEchoMode(QLineEdit.Password)
- self.password = password
- login = QLineEdit()
- self.login = login
- acctype = QComboBox()
- self.acctype = acctype
-
- save = QPushButton(_("Save"))
-
- self.connect(changePw, SIGNAL("toggled(bool)"), password, SLOT("setEnabled(bool)"))
-
- l.addWidget(save, 3, 0, 1, 3)
- l.addWidget(acctype, 0, 1, 1, 2)
- l.addWidget(login, 1, 1, 1, 2)
- l.addWidget(password, 2, 2)
- l.addWidget(changePw, 2, 1)
- l.addWidget(passwordLabel, 2, 0)
- l.addWidget(loginLabel, 1, 0)
- l.addWidget(typeLabel, 0, 0)
-
- self.connect(save, SIGNAL("clicked()"), self.slotSave)
-
- def slotSave(self):
- """
- save entered data
- """
- data = {"login": str(self.login.text()), "acctype": str(self.acctype.currentText()), "password": False}
- if self.changePw.isChecked():
- data["password"] = str(self.password.text())
- self.emit(SIGNAL("done"), data)
-
- @staticmethod
- def newAccount(types):
- """
- create empty editor instance
- """
- w = AccountEdit()
- w.setWindowTitle(_("Create account"))
-
- w.changePw.setChecked(True)
- w.password.setEnabled(True)
-
- w.acctype.addItems(types)
-
- return w
-
- @staticmethod
- def editAccount(types, base):
- """
- create editor instance with given data
- """
- w = AccountEdit()
-
- w.acctype.addItems(types)
- w.acctype.setCurrentIndex(types.index(base["type"]))
-
- w.login.setText(base["login"])
-
- return w
diff --git a/module/gui/Accounts.py b/module/gui/Accounts.py
deleted file mode 100644
index 8db04dfa9..000000000
--- a/module/gui/Accounts.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from time import strftime, gmtime
-
-class AccountModel(QAbstractItemModel):
- """
- model for account view
- """
-
- def __init__(self, view, connector):
- QAbstractItemModel.__init__(self)
- self.connector = connector
- self.view = view
- self._data = []
- self.cols = 4
- self.mutex = QMutex()
-
- def reloadData(self, force=False):
- """
- reload account list
- """
- accounts = self.connector.proxy.getAccounts(False)
-
- if self._data == accounts:
- return
-
- if len(self._data) > 0:
- self.beginRemoveRows(QModelIndex(), 0, len(self._data)-1)
- self._data = []
- self.endRemoveRows()
-
- if len(accounts) > 0:
- self.beginInsertRows(QModelIndex(), 0, len(accounts)-1)
- self._data = accounts
- self.endInsertRows()
-
- def toData(self, index):
- """
- return index pointer
- """
- return index.internalPointer()
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(self.toData(index).type)
- elif index.column() == 1:
- return QVariant(self.toData(index).login)
- elif index.column() == 2:
- if not self.toData(index).valid:
- return QVariant(_("not valid"))
- if not self.toData(index).validuntil:
- return QVariant(_("n/a"))
- until = int(self.toData(index).validuntil)
- if until > 0:
- fmtime = strftime(_("%a, %d %b %Y %H:%M"), gmtime(until))
- return QVariant(fmtime)
- else:
- return QVariant(_("unlimited"))
- #elif role == Qt.EditRole:
- # if index.column() == 0:
- # return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def index(self, row, column, parent=QModelIndex()):
- """
- create index with data pointer
- """
- if parent == QModelIndex() and len(self._data) > row:
- pointer = self._data[row]
- index = self.createIndex(row, column, pointer)
- elif parent.isValid():
- pointer = parent.internalPointer().children[row]
- index = self.createIndex(row, column, pointer)
- else:
- index = QModelIndex()
- return index
-
- def parent(self, index):
- """
- no parents, everything on top level
- """
- return QModelIndex()
-
- def rowCount(self, parent=QModelIndex()):
- """
- account count
- """
- if parent == QModelIndex():
- return len(self._data)
- return 0
-
- def columnCount(self, parent=QModelIndex()):
- return self.cols
-
- def hasChildren(self, parent=QModelIndex()):
- """
- everything on top level
- """
- if parent == QModelIndex():
- return True
- else:
- return False
-
- def canFetchMore(self, parent):
- return False
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Type"))
- elif section == 1:
- return QVariant(_("Login"))
- elif section == 2:
- return QVariant(_("Valid until"))
- elif section == 3:
- return QVariant(_("Traffic left"))
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
-class AccountView(QTreeView):
- """
- view component for accounts
- """
-
- def __init__(self, connector):
- QTreeView.__init__(self)
- self.setModel(AccountModel(self, connector))
-
- self.setColumnWidth(0, 150)
- self.setColumnWidth(1, 150)
- self.setColumnWidth(2, 150)
- self.setColumnWidth(3, 150)
-
- self.setEditTriggers(QAbstractItemView.NoEditTriggers)
-
- self.delegate = AccountDelegate(self, self.model())
- self.setItemDelegateForColumn(3, self.delegate)
-
-class AccountDelegate(QItemDelegate):
- """
- used to display a progressbar for the traffic in the traffic cell
- """
-
- def __init__(self, parent, model):
- QItemDelegate.__init__(self, parent)
- self.model = model
-
- def paint(self, painter, option, index):
- """
- paint the progressbar
- """
- if not index.isValid():
- return
- if index.column() == 3:
- data = self.model.toData(index)
- opts = QStyleOptionProgressBarV2()
- opts.minimum = 0
- if data.trafficleft:
- if data.trafficleft == -1 or data.trafficleft is None:
- opts.maximum = opts.progress = 1
- else:
- opts.maximum = opts.progress = data.trafficleft
- if data.maxtraffic:
- opts.maximum = data.maxtraffic
-
- opts.rect = option.rect
- opts.rect.setRight(option.rect.right()-1)
- opts.rect.setHeight(option.rect.height()-1)
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- if data.trafficleft and data.trafficleft == -1:
- opts.text = QString(_("unlimited"))
- elif data.trafficleft is None:
- opts.text = QString(_("n/a"))
- else:
- opts.text = QString.number(round(float(opts.progress)/1024/1024, 2)) + " GB"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
- return
- QItemDelegate.paint(self, painter, option, index)
-
diff --git a/module/gui/CNLServer.py b/module/gui/CNLServer.py
deleted file mode 100644
index 5ecac8277..000000000
--- a/module/gui/CNLServer.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-import re
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-from threading import Thread
-from cgi import FieldStorage
-from os.path import abspath, dirname, join
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-try:
- from module.common import JsEngine
-except ImportError:
- import sys
- sys.path.append(join(abspath(dirname(__file__)), "..", ".."))
- from module.common import JsEngine
-
-js = JsEngine.JsEngine()
-core = None
-
-class CNLServer(Thread):
- def __init__(self):
- Thread.__init__(self)
- self.setDaemon(True)
-
- self.stop = False
- self.stopped = False
-
- def run(self):
- server_address = ('127.0.0.1', 9666)
- try:
- httpd = HTTPServer(server_address, CNLHandler)
- except:
- self.stopped = True
- return
-
- self.stopped = False
- while self.keep_running():
- httpd.handle_request()
- self.stopped = True
-
- def keep_running(self):
- return not self.stop
-
-
-class CNLHandler(BaseHTTPRequestHandler):
-
- #def log_message(self, *args):
- # pass
-
- def add_package(self, name, urls, queue=0):
- print "name", name
- print "urls", urls
- print "queue", queue
-
- def get_post(self, name, default=""):
- if name in self.post:
- return self.post[name]
- else:
- return default
-
- def start_response(self, string):
-
- self.send_response(200)
-
- self.send_header("Content-Length", len(string))
- self.send_header("Content-Language", "de")
- self.send_header("Vary", "Accept-Language, Cookie")
- self.send_header("Cache-Control", "no-cache, must-revalidate")
- self.send_header("Content-type", "text/html")
- self.end_headers()
-
- def do_GET(self):
- path = self.path.strip("/").lower()
- #self.wfile.write(path+"\n")
-
- self.map = [ (r"add$", self.add),
- (r"addcrypted$", self.addcrypted),
- (r"addcrypted2$", self.addcrypted2),
- (r"flashgot", self.flashgot),
- (r"crossdomain\.xml", self.crossdomain),
- (r"checkSupportForUrl", self.checksupport),
- (r"jdcheck.js", self.jdcheck),
- (r"", self.flash) ]
-
- func = None
- for r, f in self.map:
- if re.match(r"(flash(got)?/?)?"+r, path):
- func = f
- break
-
- if func:
- try:
- resp = func()
- if not resp: resp = "success"
- resp += "\r\n"
- self.start_response(resp)
- self.wfile.write(resp)
- except Exception,e :
- self.send_error(500, str(e))
- else:
- self.send_error(404, "Not Found")
-
- def do_POST(self):
- form = FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD':'POST',
- 'CONTENT_TYPE':self.headers['Content-Type'],
- })
-
- self.post = {}
- for name in form.keys():
- self.post[name] = form[name].value
-
- return self.do_GET()
-
- def flash(self):
- return "JDownloader"
-
- def add(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, 0)
-
- def addcrypted(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- dlc = self.get_post('crypted').replace(" ", "+")
-
- core.upload_container(package, dlc)
-
- def addcrypted2(self):
- package = self.get_post("source", "ClickAndLoad Package")
- crypted = self.get_post("crypted")
- jk = self.get_post("jk")
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- jk = "%s f()" % jk
- jk = js.eval(jk)
- Key = unhexlify(jk)
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- self.add_package(package, result, 0)
-
-
- def flashgot(self):
- autostart = int(self.get_post('autostart', 0))
- package = self.get_post('package', "FlashGot")
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, autostart)
-
- def crossdomain(self):
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
- def checksupport(self):
- pass
-
- def jdcheck(self):
- rep = "jdownloader=true;\n"
- rep += "var version='10629';\n"
- return rep
-
-
-if __name__ == "__main__":
- import xmlrpclib
- from module import InitHomeDir
- from module.ConfigParser import ConfigParser
-
- config = ConfigParser()
-
- ssl = ""
- if config.get("ssl", "activated"):
- ssl = "s"
-
- server_url = "http%s://%s:%s@%s:%s/" % (
- ssl,
- config.username,
- config.password,
- config.get("remote", "listenaddr"),
- config.get("remote", "port")
- )
-
- core = xmlrpclib.ServerProxy(server_url, allow_none=True)
-
- s = CNLServer()
- s.start()
- while not s.stopped:
- try:
- s.join(1)
- except KeyboardInterrupt:
- s.stop = True
- s.stopped = True
- print "quiting.."
diff --git a/module/gui/CaptchaDock.py b/module/gui/CaptchaDock.py
deleted file mode 100644
index b88cb53ca..000000000
--- a/module/gui/CaptchaDock.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class CaptchaDock(QDockWidget):
- """
- dock widget for captcha input
- """
-
- def __init__(self):
- QDockWidget.__init__(self, _("Captcha"))
- self.setObjectName("Captcha Dock")
- self.widget = CaptchaDockWidget(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.BottomDockWidgetArea)
- self.setFeatures(QDockWidget.NoDockWidgetFeatures)
- self.hide()
- self.processing = False
- self.currentID = None
- self.connect(self, SIGNAL("setTask"), self.setTask)
-
- def isFree(self):
- return not self.processing
-
- def setTask(self, tid, img, imgType):
- self.processing = True
- data = QByteArray(img)
- self.currentID = tid
- self.widget.emit(SIGNAL("setImage"), data)
- self.widget.input.setText("")
- self.show()
-
-class CaptchaDockWidget(QWidget):
- """
- widget for the input widgets
- """
-
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QHBoxLayout())
- layout = self.layout()
-
- imgLabel = QLabel()
- captchaInput = QLineEdit()
- okayButton = QPushButton(_("OK"))
- cancelButton = QPushButton(_("Cancel"))
-
- layout.addStretch()
- layout.addWidget(imgLabel)
- layout.addWidget(captchaInput)
- layout.addWidget(okayButton)
- layout.addWidget(cancelButton)
- layout.addStretch()
-
- self.input = captchaInput
-
- self.connect(okayButton, SIGNAL("clicked()"), self.slotSubmit)
- self.connect(captchaInput, SIGNAL("returnPressed()"), self.slotSubmit)
- self.connect(self, SIGNAL("setImage"), self.setImg)
- self.connect(self, SIGNAL("setPixmap(const QPixmap &)"), imgLabel, SLOT("setPixmap(const QPixmap &)"))
-
- def setImg(self, data):
- pixmap = QPixmap()
- pixmap.loadFromData(data)
- self.emit(SIGNAL("setPixmap(const QPixmap &)"), pixmap)
- self.input.setFocus(Qt.OtherFocusReason)
-
- def slotSubmit(self):
- text = self.input.text()
- tid = self.dock.currentID
- self.dock.currentID = None
- self.dock.emit(SIGNAL("done"), tid, str(text))
- self.dock.hide()
- self.dock.processing = False
-
diff --git a/module/gui/Collector.py b/module/gui/Collector.py
deleted file mode 100644
index 3ec4262f1..000000000
--- a/module/gui/Collector.py
+++ /dev/null
@@ -1,407 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from module.PyFile import statusMap
-from module.utils import formatSize
-
-from module.remote.thriftbackend.ThriftClient import Destination, FileDoesNotExists, ElementType
-
-statusMapReverse = dict((v,k) for k, v in statusMap.iteritems())
-
-translatedStatusMap = {} # -> CollectorModel.__init__
-
-class CollectorModel(QAbstractItemModel):
- """
- model for the collector view
- """
-
- def __init__(self, view, connector):
- QAbstractItemModel.__init__(self)
- self.connector = connector
- self.view = view
- self._data = []
- self.cols = 4
- self.interval = 1
- self.mutex = QMutex()
-
- global translatedStatusMap # workaround because i18n is not running at import time
- translatedStatusMap = {
- "finished": _("finished"),
- "offline": _("offline"),
- "online": _("online"),
- "queued": _("queued"),
- "skipped": _("skipped"),
- "waiting": _("waiting"),
- "temp. offline": _("temp. offline"),
- "starting": _("starting"),
- "failed": _("failed"),
- "aborted": _("aborted"),
- "decrypting": _("decrypting"),
- "custom": _("custom"),
- "downloading": _("downloading"),
- "processing": _("processing")
- }
-
- def translateStatus(self, string):
- """
- used to convert to locale specific status
- """
- return translatedStatusMap[string]
-
- def addEvent(self, event):
- """
- called from main loop, pass events to the correct methods
- """
- QMutexLocker(self.mutex)
- if event.eventname == "reload":
- self.fullReload()
- elif event.eventname == "remove":
- self.removeEvent(event)
- elif event.eventname == "insert":
- self.insertEvent(event)
- elif event.eventname == "update":
- self.updateEvent(event)
-
- def fullReload(self):
- """
- reload whole model, used at startup to load initial data
- """
- self._data = []
- order = self.connector.getPackageOrder(Destination.Collector)
- self.beginInsertRows(QModelIndex(), 0, len(order.values()))
- for position, pid in order.iteritems():
- pack = self.connector.getPackageData(pid)
- package = Package(pack)
- self._data.append(package)
- self._data = sorted(self._data, key=lambda p: p.data["order"])
- self.endInsertRows()
-
- def removeEvent(self, event):
- """
- remove an element from model
- """
- if event.type == ElementType.File:
- for p, package in enumerate(self._data):
- for k, child in enumerate(package.children):
- if child.id == event.id:
- self.beginRemoveRows(self.index(p, 0), k, k)
- del package.children[k]
- self.endRemoveRows()
- break
- else:
- for k, package in enumerate(self._data):
- if package.id == event.id:
- self.beginRemoveRows(QModelIndex(), k, k)
- del self._data[k]
- self.endRemoveRows()
- break
-
- def insertEvent(self, event):
- """
- inserts a new element in the model
- """
- if event.type == ElementType.File:
- try:
- info = self.connector.getFileData(event.id)
- except FileDoesNotExists:
- return
-
- for k, package in enumerate(self._data):
- if package.id == info.package:
- if package.getChild(info.fid):
- self.updateEvent(event)
- break
- self.beginInsertRows(self.index(k, 0), info.order, info.order)
- package.addChild(info)
- self.endInsertRows()
- break
- else:
- data = self.connector.getPackageData(event.id)
- package = Package(data)
- self.beginInsertRows(QModelIndex(), data.order, data.order)
- self._data.insert(data.order, package)
- self.endInsertRows()
-
- def updateEvent(self, event):
- """
- update an element in the model
- """
- if event.type == ElementType.File:
- try:
- info = self.connector.proxy.getFileData(event.id)
- except FileDoesNotExists:
- return
- for p, package in enumerate(self._data):
- if package.id == info.packageID:
- for k, child in enumerate(package.children):
- if child.id == event.id:
- child.update(info)
- if not info.status == 12:
- child.data["downloading"] = None
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
- break
- else:
- data = self.connector.getPackageData(event.id)
- if not data:
- return
- for p, package in enumerate(self._data):
- if package.id == event.id:
- package.update(data)
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(p, 0), self.index(p, self.cols))
- break
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- elif index.column() == 1:
- item = index.internalPointer()
- plugins = []
- if isinstance(item, Package):
- for child in item.children:
- if not child.data["plugin"] in plugins:
- plugins.append(child.data["plugin"])
- else:
- plugins.append(item.data["plugin"])
- return QVariant(", ".join(plugins))
- elif index.column() == 2:
- item = index.internalPointer()
- status = 0
- if isinstance(item, Package):
- for child in item.children:
- if child.data["status"] > status:
- status = child.data["status"]
- else:
- status = item.data["status"]
- return QVariant(self.translateStatus(statusMapReverse[status]))
- elif index.column() == 3:
- item = index.internalPointer()
- if isinstance(item, Link):
- return QVariant(formatSize(item.data["size"]))
- else:
- ms = 0
- for c in item.children:
- ms += c.data["size"]
- return QVariant(formatSize(ms))
- elif role == Qt.EditRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def index(self, row, column, parent=QModelIndex()):
- """
- creates a cell index with pointer to the data
- """
- if parent == QModelIndex() and len(self._data) > row:
- pointer = self._data[row]
- index = self.createIndex(row, column, pointer)
- elif parent.isValid():
- try:
- pointer = parent.internalPointer().children[row]
- except:
- return QModelIndex()
- index = self.createIndex(row, column, pointer)
- else:
- index = QModelIndex()
- return index
-
- def parent(self, index):
- """
- return index of parent element
- only valid for links
- """
- if index == QModelIndex():
- return QModelIndex()
- if index.isValid():
- link = index.internalPointer()
- if isinstance(link, Link):
- for k, pack in enumerate(self._data):
- if pack == link.package:
- return self.createIndex(k, 0, link.package)
- return QModelIndex()
-
- def rowCount(self, parent=QModelIndex()):
- """
- returns row count for the element
- """
- if parent == QModelIndex():
- #return package count
- return len(self._data)
- else:
- if parent.isValid():
- #index is valid
- pack = parent.internalPointer()
- if isinstance(pack, Package):
- #index points to a package
- #return len of children
- return len(pack.children)
- else:
- #index is invalid
- return False
- #files have no children
- return 0
-
- def columnCount(self, parent=QModelIndex()):
- return self.cols
-
- def hasChildren(self, parent=QModelIndex()):
- if not parent.isValid():
- return True
- return self.rowCount(parent) > 0
-
- def canFetchMore(self, parent):
- return False
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Name"))
- elif section == 1:
- return QVariant(_("Plugin"))
- elif section == 2:
- return QVariant(_("Status"))
- elif section == 3:
- return QVariant(_("Size"))
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- if index.column() == 0 and self.parent(index) == QModelIndex():
- return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
- def setData(self, index, value, role=Qt.EditRole):
- """
- called if package name editing is finished, sets new name
- """
- if index.column() == 0 and self.parent(index) == QModelIndex() and role == Qt.EditRole:
- self.connector.setPackageName(index.internalPointer().id, str(value.toString()))
- return True
-
-class Package(object):
- """
- package object in the model
- """
-
- def __init__(self, pack):
- self.id = pack.pid
- self.children = []
- for f in pack.links:
- self.addChild(f)
- self.data = {}
- self.update(pack)
-
- def update(self, pack):
- """
- update data dict from thift object
- """
- data = {
- "name": pack.name,
- "folder": pack.folder,
- "site": pack.site,
- "password": pack.password,
- "order": pack.order,
- }
- self.data.update(data)
-
- def addChild(self, f):
- """
- add child (Link) to package
- """
- self.children.insert(f.order, Link(f, self))
- self.children = sorted(self.children, key=lambda l: l.data["order"])
-
- def getChild(self, fid):
- """
- get child from package
- """
- for child in self.children:
- if child.id == int(fid):
- return child
- return None
-
- def getChildKey(self, fid):
- """
- get child index
- """
- for k, child in enumerate(self.children):
- if child.id == int(fid):
- return k
- return None
-
- def removeChild(self, fid):
- """
- remove child
- """
- for k, child in enumerate(self.children):
- if child.id == int(fid):
- del self.children[k]
-
-class Link(object):
- def __init__(self, f, pack):
- self.data = {"downloading": None}
- self.update(f)
- self.id = f.fid
- self.package = pack
-
- def update(self, f):
- """
- update data dict from thift object
- """
- data = {
- "url": f.url,
- "name": f.name,
- "plugin": f.plugin,
- "size": f.size,
- "format_size": f.format_size,
- "status": f.status,
- "statusmsg": f.statusmsg,
- "package": f.packageID,
- "error": f.error,
- "order": f.order,
- }
- self.data.update(data)
-
-class CollectorView(QTreeView):
- """
- view component for collector
- """
-
- def __init__(self, connector):
- QTreeView.__init__(self)
- self.setModel(CollectorModel(self, connector))
- self.setColumnWidth(0, 500)
- self.setColumnWidth(1, 100)
- self.setColumnWidth(2, 200)
- self.setColumnWidth(3, 100)
-
- self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
-
diff --git a/module/gui/ConnectionManager.py b/module/gui/ConnectionManager.py
deleted file mode 100644
index def575abc..000000000
--- a/module/gui/ConnectionManager.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-from uuid import uuid4 as uuid
-
-class ConnectionManager(QWidget):
-
-
- warningShown = False
-
- def __init__(self):
- QWidget.__init__(self)
-
- if not self.warningShown:
- QMessageBox.warning(self, 'Warning',
- "We are sorry but the GUI is not stable yet. Please use the webinterface for much better experience. \n", QMessageBox.Ok)
- ConnectionManager.warningShown = True
-
- mainLayout = QHBoxLayout()
- buttonLayout = QVBoxLayout()
-
- self.setWindowTitle(_("pyLoad ConnectionManager"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- connList = QListWidget()
-
- new = QPushButton(_("New"))
- edit = QPushButton(_("Edit"))
- remove = QPushButton(_("Remove"))
- connect = QPushButton(_("Connect"))
-
- #box = QFrame()
- boxLayout = QVBoxLayout()
- #box.setLayout(boxLayout)
-
- boxLayout.addWidget(QLabel(_("Connect:")))
- boxLayout.addWidget(connList)
-
- line = QFrame()
- #line.setFixedWidth(100)
- line.setFrameShape(line.HLine)
- line.setFrameShadow(line.Sunken)
- line.setFixedHeight(10)
-
- boxLayout.addWidget(line)
-
- form = QFormLayout()
- form.setMargin(5)
- form.setSpacing(20)
-
- form.setAlignment(Qt.AlignRight)
- checkbox = QCheckBox()
- form.addRow(_("Use internal Core:"), checkbox)
-
- boxLayout.addLayout(form)
-
- mainLayout.addLayout(boxLayout)
- mainLayout.addLayout(buttonLayout)
-
- buttonLayout.addWidget(new)
- buttonLayout.addWidget(edit)
- buttonLayout.addWidget(remove)
- buttonLayout.addStretch()
- buttonLayout.addWidget(connect)
-
- self.setLayout(mainLayout)
-
- self.internal = checkbox
- self.new = new
- self.connectb = connect
- self.remove = remove
- self.editb = edit
- self.connList = connList
- self.edit = self.EditWindow()
- self.connectSignals()
-
- self.defaultStates = {}
-
- def connectSignals(self):
- self.connect(self, SIGNAL("setConnections"), self.setConnections)
- self.connect(self.new, SIGNAL("clicked()"), self.slotNew)
- self.connect(self.editb, SIGNAL("clicked()"), self.slotEdit)
- self.connect(self.remove, SIGNAL("clicked()"), self.slotRemove)
- self.connect(self.connectb, SIGNAL("clicked()"), self.slotConnect)
- self.connect(self.edit, SIGNAL("save"), self.slotSave)
- self.connect(self.connList, SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.slotItemDoubleClicked)
- self.connect(self.internal, SIGNAL("clicked()"), self.slotInternal)
-
- def setConnections(self, connections):
- self.connList.clear()
- for conn in connections:
- item = QListWidgetItem()
- item.setData(Qt.DisplayRole, QVariant(conn["name"]))
- item.setData(Qt.UserRole, QVariant(conn))
- self.connList.addItem(item)
- if conn["default"]:
- item.setData(Qt.DisplayRole, QVariant(_("%s (Default)") % conn["name"]))
- self.connList.setCurrentItem(item)
-
- def slotNew(self):
- data = {"id":uuid().hex, "type":"remote", "default":False, "name":"", "host":"", "port":"7228", "user":"admin", "password":""}
- self.edit.setData(data)
- self.edit.show()
-
- def slotEdit(self):
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.edit.setData(data)
- self.edit.show()
-
- def slotRemove(self):
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.emit(SIGNAL("removeConnection"), data)
-
- def slotConnect(self):
- if self.internal.checkState() == 2:
- data = {"type": "internal"}
- self.emit(SIGNAL("connect"), data)
- else:
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.emit(SIGNAL("connect"), data)
-
- def cleanDict(self, data):
- tmp = {}
- for k, d in data.items():
- tmp[str(k)] = d
- return tmp
-
- def slotSave(self, data):
- self.emit(SIGNAL("saveConnection"), data)
-
- def slotItemDoubleClicked(self, defaultItem):
- data = defaultItem.data(Qt.UserRole).toPyObject()
- self.setDefault(data, True)
- did = self.cleanDict(data)["id"]
- #allItems = self.connList.findItems("*", Qt.MatchWildcard)
- count = self.connList.count()
- for i in range(count):
- item = self.connList.item(i)
- data = item.data(Qt.UserRole).toPyObject()
- if self.cleanDict(data)["id"] == did:
- continue
- self.setDefault(data, False)
-
- def slotInternal(self):
- if self.internal.checkState() == 2:
- self.connList.clearSelection()
-
- def setDefault(self, data, state):
- data = self.cleanDict(data)
- self.edit.setData(data)
- data = self.edit.getData()
- data["default"] = state
- self.edit.emit(SIGNAL("save"), data)
-
- class EditWindow(QWidget):
- def __init__(self):
- QWidget.__init__(self)
-
- self.setWindowTitle(_("pyLoad ConnectionManager"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- grid = QGridLayout()
-
- nameLabel = QLabel(_("Name:"))
- hostLabel = QLabel(_("Host:"))
- localLabel = QLabel(_("Local:"))
- userLabel = QLabel(_("User:"))
- pwLabel = QLabel(_("Password:"))
- portLabel = QLabel(_("Port:"))
-
- name = QLineEdit()
- host = QLineEdit()
- local = QCheckBox()
- user = QLineEdit()
- password = QLineEdit()
- password.setEchoMode(QLineEdit.Password)
- port = QSpinBox()
- port.setRange(1,10000)
-
- save = QPushButton(_("Save"))
- cancel = QPushButton(_("Cancel"))
-
- grid.addWidget(nameLabel, 0, 0)
- grid.addWidget(name, 0, 1)
- grid.addWidget(localLabel, 1, 0)
- grid.addWidget(local, 1, 1)
- grid.addWidget(hostLabel, 2, 0)
- grid.addWidget(host, 2, 1)
- grid.addWidget(portLabel, 3, 0)
- grid.addWidget(port, 3, 1)
- grid.addWidget(userLabel, 4, 0)
- grid.addWidget(user, 4, 1)
- grid.addWidget(pwLabel, 5, 0)
- grid.addWidget(password, 5, 1)
- grid.addWidget(cancel, 6, 0)
- grid.addWidget(save, 6, 1)
-
- self.setLayout(grid)
- self.controls = {"name": name,
- "host": host,
- "local": local,
- "user": user,
- "password": password,
- "port": port,
- "save": save,
- "cancel": cancel}
-
- self.connect(cancel, SIGNAL("clicked()"), self.hide)
- self.connect(save, SIGNAL("clicked()"), self.slotDone)
- self.connect(local, SIGNAL("stateChanged(int)"), self.slotLocalChanged)
-
- self.id = None
- self.default = None
-
- def setData(self, data):
- if not data: return
-
- self.id = data["id"]
- self.default = data["default"]
- self.controls["name"].setText(data["name"])
- if data["type"] == "local":
- data["local"] = True
- else:
- data["local"] = False
- self.controls["local"].setChecked(data["local"])
- if not data["local"]:
- self.controls["user"].setText(data["user"])
- self.controls["password"].setText(data["password"])
- self.controls["port"].setValue(int(data["port"]))
- self.controls["host"].setText(data["host"])
- self.controls["user"].setDisabled(False)
- self.controls["password"].setDisabled(False)
- self.controls["port"].setDisabled(False)
- self.controls["host"].setDisabled(False)
- else:
- self.controls["user"].setText("")
- self.controls["port"].setValue(1)
- self.controls["host"].setText("")
- self.controls["user"].setDisabled(True)
- self.controls["password"].setDisabled(True)
- self.controls["port"].setDisabled(True)
- self.controls["host"].setDisabled(True)
-
- def slotLocalChanged(self, val):
- if val == 2:
- self.controls["user"].setDisabled(True)
- self.controls["password"].setDisabled(True)
- self.controls["port"].setDisabled(True)
- self.controls["host"].setDisabled(True)
- elif val == 0:
- self.controls["user"].setDisabled(False)
- self.controls["password"].setDisabled(False)
- self.controls["port"].setDisabled(False)
- self.controls["host"].setDisabled(False)
-
- def getData(self):
- d = {}
- d["id"] = self.id
- d["default"] = self.default
- d["name"] = self.controls["name"].text()
- d["local"] = self.controls["local"].isChecked()
- d["user"] = self.controls["user"].text()
- d["password"] = self.controls["password"].text()
- d["host"] = self.controls["host"].text()
- d["port"] = self.controls["port"].value()
- if d["local"]:
- d["type"] = "local"
- else:
- d["type"] = "remote"
- return d
-
- def slotDone(self):
- data = self.getData()
- self.hide()
- self.emit(SIGNAL("save"), data)
-
diff --git a/module/gui/CoreConfigParser.py b/module/gui/CoreConfigParser.py
deleted file mode 100644
index 0d1d298c6..000000000
--- a/module/gui/CoreConfigParser.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-from os.path import exists
-from os.path import join
-
-
-CONF_VERSION = 1
-
-########################################################################
-class ConfigParser:
-
- #----------------------------------------------------------------------
- def __init__(self, configdir):
- """Constructor"""
- self.configdir = configdir
- self.config = {}
-
- if self.checkVersion():
- self.readConfig()
-
- #----------------------------------------------------------------------
- def checkVersion(self):
-
- if not exists(join(self.configdir, "pyload.conf")):
- return False
- f = open(join(self.configdir, "pyload.conf"), "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":")+1:].strip()
-
- if int(v) < CONF_VERSION:
- return False
-
- return True
-
- #----------------------------------------------------------------------
- def readConfig(self):
- """reads the config file"""
-
- self.config = self.parseConfig(join(self.configdir, "pyload.conf"))
-
-
- #----------------------------------------------------------------------
- def parseConfig(self, config):
- """parses a given configfile"""
-
- f = open(config)
-
- config = f.read()
-
- config = config.split("\n")[1:]
-
- conf = {}
-
- section, option, value, typ, desc = "","","","",""
-
- listmode = False
-
- for line in config:
-
- line = line.rpartition("#") # removes comments
-
- if line[1]:
- line = line[0]
- else:
- line = line[2]
-
- line = line.strip()
-
- try:
-
- if line == "":
- continue
- elif line.endswith(":"):
- section, none, desc = line[:-1].partition('-')
- section = section.strip()
- desc = desc.replace('"', "").strip()
- conf[section] = { "desc" : desc }
- else:
- if listmode:
-
- if line.endswith("]"):
- listmode = False
- line = line.replace("]","")
-
- value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
-
- if not listmode:
- conf[section][option] = { "desc" : desc,
- "type" : typ,
- "value" : value}
-
-
- else:
- content, none, value = line.partition("=")
-
- content, none, desc = content.partition(":")
-
- desc = desc.replace('"', "").strip()
-
- typ, option = content.split()
-
- value = value.strip()
-
- if value.startswith("["):
- if value.endswith("]"):
- listmode = False
- value = value[:-1]
- else:
- listmode = True
-
- value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
- else:
- value = self.cast(typ, value)
-
- if not listmode:
- conf[section][option] = { "desc" : desc,
- "type" : typ,
- "value" : value}
-
- except:
- pass
-
-
- f.close()
- return conf
-
- #----------------------------------------------------------------------
- def cast(self, typ, value):
- """cast value to given format"""
- if type(value) not in (str, unicode):
- return value
-
- if typ == "int":
- return int(value)
- elif typ == "bool":
- return True if value.lower() in ("1","true", "on", "an","yes") else False
- else:
- return value
-
- #----------------------------------------------------------------------
- def get(self, section, option):
- """get value"""
- return self.config[section][option]["value"]
-
- #----------------------------------------------------------------------
- def __getitem__(self, section):
- """provides dictonary like access: c['section']['option']"""
- return Section(self, section)
-
-########################################################################
-class Section:
- """provides dictionary like access for configparser"""
-
- #----------------------------------------------------------------------
- def __init__(self, parser, section):
- """Constructor"""
- self.parser = parser
- self.section = section
-
- #----------------------------------------------------------------------
- def __getitem__(self, item):
- """getitem"""
- return self.parser.get(self.section, item)
diff --git a/module/gui/LinkDock.py b/module/gui/LinkDock.py
deleted file mode 100644
index ac2d4aae5..000000000
--- a/module/gui/LinkDock.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class NewLinkDock(QDockWidget):
- def __init__(self):
- QDockWidget.__init__(self, "New Links")
- self.setObjectName("New Links Dock")
- self.widget = NewLinkWindow(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea)
- self.hide()
-
- def slotDone(self):
- text = str(self.widget.box.toPlainText())
- lines = text.splitlines()
- self.emit(SIGNAL("done"), lines)
- self.widget.box.clear()
- self.hide()
-
-class NewLinkWindow(QWidget):
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QVBoxLayout())
- layout = self.layout()
-
- explanationLabel = QLabel("Select a package and then click Add button.")
- boxLabel = QLabel("Paste URLs here:")
- self.box = QTextEdit()
-
- save = QPushButton("Add")
-
- layout.addWidget(explanationLabel)
- layout.addWidget(boxLabel)
- layout.addWidget(self.box)
- layout.addWidget(save)
-
- self.connect(save, SIGNAL("clicked()"), self.dock.slotDone)
diff --git a/module/gui/MainWindow.py b/module/gui/MainWindow.py
deleted file mode 100644
index c71112e9b..000000000
--- a/module/gui/MainWindow.py
+++ /dev/null
@@ -1,697 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-from module.gui.PackageDock import *
-from module.gui.LinkDock import *
-from module.gui.CaptchaDock import CaptchaDock
-from module.gui.SettingsWidget import SettingsWidget
-
-from module.gui.Collector import CollectorView, Package, Link
-from module.gui.Queue import QueueView
-from module.gui.Overview import OverviewView
-from module.gui.Accounts import AccountView
-from module.gui.AccountEdit import AccountEdit
-
-from module.remote.thriftbackend.ThriftClient import AccountInfo
-
-class MainWindow(QMainWindow):
- def __init__(self, connector):
- """
- set up main window
- """
- QMainWindow.__init__(self)
- #window stuff
- self.setWindowTitle(_("pyLoad Client"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
- self.resize(1000,600)
-
- #layout version
- self.version = 3
-
- #init docks
- self.newPackDock = NewPackageDock()
- self.addDockWidget(Qt.RightDockWidgetArea, self.newPackDock)
- self.connect(self.newPackDock, SIGNAL("done"), self.slotAddPackage)
- self.captchaDock = CaptchaDock()
- self.addDockWidget(Qt.BottomDockWidgetArea, self.captchaDock)
- self.newLinkDock = NewLinkDock()
- self.addDockWidget(Qt.RightDockWidgetArea, self.newLinkDock)
- self.connect(self.newLinkDock, SIGNAL("done"), self.slotAddLinksToPackage)
-
- #central widget, layout
- self.masterlayout = QVBoxLayout()
- lw = QWidget()
- lw.setLayout(self.masterlayout)
- self.setCentralWidget(lw)
-
- #status
- self.statusw = QFrame()
- self.statusw.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
- self.statusw.setLineWidth(2)
- self.statusw.setLayout(QGridLayout())
- #palette = self.statusw.palette()
- #palette.setColor(QPalette.Window, QColor(255, 255, 255))
- #self.statusw.setPalette(palette)
- #self.statusw.setAutoFillBackground(True)
- l = self.statusw.layout()
-
- class BoldLabel(QLabel):
- def __init__(self, text):
- QLabel.__init__(self, text)
- f = self.font()
- f.setBold(True)
- self.setFont(f)
- self.setAlignment(Qt.AlignRight)
-
- class Seperator(QFrame):
- def __init__(self):
- QFrame.__init__(self)
- self.setFrameShape(QFrame.VLine)
- self.setFrameShadow(QFrame.Sunken)
-
- l.addWidget(BoldLabel(_("Packages:")), 0, 0)
- self.packageCount = QLabel("0")
- l.addWidget(self.packageCount, 0, 1)
-
- l.addWidget(BoldLabel(_("Files:")), 0, 2)
- self.fileCount = QLabel("0")
- l.addWidget(self.fileCount, 0, 3)
-
- l.addWidget(BoldLabel(_("Status:")), 0, 4)
- self.status = QLabel("running")
- l.addWidget(self.status, 0, 5)
-
- l.addWidget(BoldLabel(_("Space:")), 0, 6)
- self.space = QLabel("")
- l.addWidget(self.space, 0, 7)
-
- l.addWidget(BoldLabel(_("Speed:")), 0, 8)
- self.speed = QLabel("")
- l.addWidget(self.speed, 0, 9)
-
- #l.addWidget(BoldLabel(_("Max. downloads:")), 0, 9)
- #l.addWidget(BoldLabel(_("Max. chunks:")), 1, 9)
- #self.maxDownloads = QSpinBox()
- #self.maxDownloads.setEnabled(False)
- #self.maxChunks = QSpinBox()
- #self.maxChunks.setEnabled(False)
- #l.addWidget(self.maxDownloads, 0, 10)
- #l.addWidget(self.maxChunks, 1, 10)
-
- #set menubar and statusbar
- self.menubar = self.menuBar()
- #self.statusbar = self.statusBar()
- #self.connect(self.statusbar, SIGNAL("showMsg"), self.statusbar.showMessage)
- #self.serverStatus = QLabel(_("Status: Not Connected"))
- #self.statusbar.addPermanentWidget(self.serverStatus)
-
- #menu
- self.menus = {"file": self.menubar.addMenu(_("File")),
- "connections": self.menubar.addMenu(_("Connections"))}
-
- #menu actions
- self.mactions = {"exit": QAction(_("Exit"), self.menus["file"]),
- "manager": QAction(_("Connection manager"), self.menus["connections"])}
-
- #add menu actions
- self.menus["file"].addAction(self.mactions["exit"])
- self.menus["connections"].addAction(self.mactions["manager"])
-
- #toolbar
- self.actions = {}
- self.init_toolbar()
-
- #tabs
- self.tabw = QTabWidget()
- self.tabs = {"overview": {"w": QWidget()},
- "queue": {"w": QWidget()},
- "collector": {"w": QWidget()},
- "accounts": {"w": QWidget()},
- "settings": {}}
- #self.tabs["settings"]["s"] = QScrollArea()
- self.tabs["settings"]["w"] = SettingsWidget()
- #self.tabs["settings"]["s"].setWidgetResizable(True)
- #self.tabs["settings"]["s"].setWidget(self.tabs["settings"]["w"])
- self.tabs["log"] = {"w":QWidget()}
- self.tabw.addTab(self.tabs["overview"]["w"], _("Overview"))
- self.tabw.addTab(self.tabs["queue"]["w"], _("Queue"))
- self.tabw.addTab(self.tabs["collector"]["w"], _("Collector"))
- self.tabw.addTab(self.tabs["accounts"]["w"], _("Accounts"))
- self.tabw.addTab(self.tabs["settings"]["w"], _("Settings"))
- self.tabw.addTab(self.tabs["log"]["w"], _("Log"))
-
- #init tabs
- self.init_tabs(connector)
-
- #context menus
- self.init_context()
-
- #layout
- self.masterlayout.addWidget(self.tabw)
- self.masterlayout.addWidget(self.statusw)
-
- #signals..
- self.connect(self.mactions["manager"], SIGNAL("triggered()"), self.slotShowConnector)
-
- self.connect(self.tabs["queue"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotQueueContextMenu)
- self.connect(self.tabs["collector"]["package_view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotCollectorContextMenu)
- self.connect(self.tabs["accounts"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotAccountContextMenu)
-
- self.connect(self.tabw, SIGNAL("currentChanged(int)"), self.slotTabChanged)
-
- self.lastAddedID = None
-
- self.connector = connector
-
- def init_toolbar(self):
- """
- create toolbar
- """
- self.toolbar = self.addToolBar(_("Hide Toolbar"))
- self.toolbar.setObjectName("Main Toolbar")
- self.toolbar.setIconSize(QSize(30,30))
- self.toolbar.setMovable(False)
- self.actions["toggle_status"] = self.toolbar.addAction(_("Toggle Pause/Resume"))
- pricon = QIcon()
- pricon.addFile(join(pypath, "icons","toolbar_start.png"), QSize(), QIcon.Normal, QIcon.Off)
- pricon.addFile(join(pypath, "icons","toolbar_pause.png"), QSize(), QIcon.Normal, QIcon.On)
- self.actions["toggle_status"].setIcon(pricon)
- self.actions["toggle_status"].setCheckable(True)
- self.actions["status_stop"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_stop.png")), _("Stop"))
- self.toolbar.addSeparator()
- self.actions["add"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_add.png")), _("Add"))
- self.toolbar.addSeparator()
- self.actions["clipboard"] = self.toolbar.addAction(QIcon(join(pypath, "icons","clipboard.png")), _("Check Clipboard"))
- self.actions["clipboard"].setCheckable(True)
-
- self.connect(self.actions["toggle_status"], SIGNAL("toggled(bool)"), self.slotToggleStatus)
- self.connect(self.actions["clipboard"], SIGNAL("toggled(bool)"), self.slotToggleClipboard)
- self.connect(self.actions["status_stop"], SIGNAL("triggered()"), self.slotStatusStop)
- self.addMenu = QMenu()
- packageAction = self.addMenu.addAction(_("Package"))
- containerAction = self.addMenu.addAction(_("Container"))
- accountAction = self.addMenu.addAction(_("Account"))
- linksAction = self.addMenu.addAction(_("Links"))
- self.connect(self.actions["add"], SIGNAL("triggered()"), self.slotAdd)
- self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage)
- self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer)
- self.connect(accountAction, SIGNAL("triggered()"), self.slotNewAccount)
- self.connect(linksAction, SIGNAL("triggered()"), self.slotShowAddLinks)
-
- def init_tabs(self, connector):
- """
- create tabs
- """
- #overview
- self.tabs["overview"]["l"] = QGridLayout()
- self.tabs["overview"]["w"].setLayout(self.tabs["overview"]["l"])
- self.tabs["overview"]["view"] = OverviewView(connector)
- self.tabs["overview"]["l"].addWidget(self.tabs["overview"]["view"])
-
- #queue
- self.tabs["queue"]["l"] = QGridLayout()
- self.tabs["queue"]["w"].setLayout(self.tabs["queue"]["l"])
- self.tabs["queue"]["view"] = QueueView(connector)
- self.tabs["queue"]["l"].addWidget(self.tabs["queue"]["view"])
-
- #collector
- toQueue = QPushButton(_("Push selected packages to queue"))
- self.tabs["collector"]["l"] = QGridLayout()
- self.tabs["collector"]["w"].setLayout(self.tabs["collector"]["l"])
- self.tabs["collector"]["package_view"] = CollectorView(connector)
- self.tabs["collector"]["l"].addWidget(self.tabs["collector"]["package_view"], 0, 0)
- self.tabs["collector"]["l"].addWidget(toQueue, 1, 0)
- self.connect(toQueue, SIGNAL("clicked()"), self.slotPushPackageToQueue)
- self.tabs["collector"]["package_view"].setContextMenuPolicy(Qt.CustomContextMenu)
- self.tabs["queue"]["view"].setContextMenuPolicy(Qt.CustomContextMenu)
-
- #log
- self.tabs["log"]["l"] = QGridLayout()
- self.tabs["log"]["w"].setLayout(self.tabs["log"]["l"])
- self.tabs["log"]["text"] = QTextEdit()
- self.tabs["log"]["text"].logOffset = 0
- self.tabs["log"]["text"].setReadOnly(True)
- self.connect(self.tabs["log"]["text"], SIGNAL("append(QString)"), self.tabs["log"]["text"].append)
- self.tabs["log"]["l"].addWidget(self.tabs["log"]["text"])
-
- #accounts
- self.tabs["accounts"]["view"] = AccountView(connector)
- self.tabs["accounts"]["w"].setLayout(QVBoxLayout())
- self.tabs["accounts"]["w"].layout().addWidget(self.tabs["accounts"]["view"])
- newbutton = QPushButton(_("New Account"))
- self.tabs["accounts"]["w"].layout().addWidget(newbutton)
- self.connect(newbutton, SIGNAL("clicked()"), self.slotNewAccount)
- self.tabs["accounts"]["view"].setContextMenuPolicy(Qt.CustomContextMenu)
-
- def init_context(self):
- """
- create context menus
- """
- self.activeMenu = None
- #queue
- self.queueContext = QMenu()
- self.queueContext.buttons = {}
- self.queueContext.item = (None, None)
- self.queueContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.queueContext)
- self.queueContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.queueContext)
- self.queueContext.buttons["pull"] = QAction(QIcon(join(pypath, "icons","pull_small.png")), _("Pull out"), self.queueContext)
- self.queueContext.buttons["abort"] = QAction(QIcon(join(pypath, "icons","abort.png")), _("Abort"), self.queueContext)
- self.queueContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.queueContext)
- self.queueContext.addAction(self.queueContext.buttons["pull"])
- self.queueContext.addAction(self.queueContext.buttons["edit"])
- self.queueContext.addAction(self.queueContext.buttons["remove"])
- self.queueContext.addAction(self.queueContext.buttons["restart"])
- self.queueContext.addAction(self.queueContext.buttons["abort"])
- self.connect(self.queueContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload)
- self.connect(self.queueContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload)
- self.connect(self.queueContext.buttons["pull"], SIGNAL("triggered()"), self.slotPullOutPackage)
- self.connect(self.queueContext.buttons["abort"], SIGNAL("triggered()"), self.slotAbortDownload)
- self.connect(self.queueContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage)
-
- #collector
- self.collectorContext = QMenu()
- self.collectorContext.buttons = {}
- self.collectorContext.item = (None, None)
- self.collectorContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.collectorContext)
- self.collectorContext.buttons["push"] = QAction(QIcon(join(pypath, "icons","push_small.png")), _("Push to queue"), self.collectorContext)
- self.collectorContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.collectorContext)
- self.collectorContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.collectorContext)
- self.collectorContext.buttons["refresh"] = QAction(QIcon(join(pypath, "icons","refresh1_small.png")),_("Refresh Status"), self.collectorContext)
- self.collectorContext.addAction(self.collectorContext.buttons["push"])
- self.collectorContext.addSeparator()
- self.collectorContext.buttons["add"] = self.collectorContext.addMenu(QIcon(join(pypath, "icons","add_small.png")), _("Add"))
- self.collectorContext.addAction(self.collectorContext.buttons["edit"])
- self.collectorContext.addAction(self.collectorContext.buttons["remove"])
- self.collectorContext.addAction(self.collectorContext.buttons["restart"])
- self.collectorContext.addSeparator()
- self.collectorContext.addAction(self.collectorContext.buttons["refresh"])
- packageAction = self.collectorContext.buttons["add"].addAction(_("Package"))
- containerAction = self.collectorContext.buttons["add"].addAction(_("Container"))
- linkAction = self.collectorContext.buttons["add"].addAction(_("Links"))
- self.connect(self.collectorContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload)
- self.connect(self.collectorContext.buttons["push"], SIGNAL("triggered()"), self.slotPushPackageToQueue)
- self.connect(self.collectorContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage)
- self.connect(self.collectorContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload)
- self.connect(self.collectorContext.buttons["refresh"], SIGNAL("triggered()"), self.slotRefreshPackage)
- self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage)
- self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer)
- self.connect(linkAction, SIGNAL("triggered()"), self.slotShowAddLinks)
-
- self.accountContext = QMenu()
- self.accountContext.buttons = {}
- self.accountContext.buttons["add"] = QAction(QIcon(join(pypath, "icons","add_small.png")), _("Add"), self.accountContext)
- self.accountContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.accountContext)
- self.accountContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit"), self.accountContext)
- self.accountContext.addAction(self.accountContext.buttons["add"])
- self.accountContext.addAction(self.accountContext.buttons["edit"])
- self.accountContext.addAction(self.accountContext.buttons["remove"])
- self.connect(self.accountContext.buttons["add"], SIGNAL("triggered()"), self.slotNewAccount)
- self.connect(self.accountContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditAccount)
- self.connect(self.accountContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveAccount)
-
- def slotToggleStatus(self, status):
- """
- pause/start toggle (toolbar)
- """
- self.emit(SIGNAL("setDownloadStatus"), status)
-
- def slotStatusStop(self):
- """
- stop button (toolbar)
- """
- self.emit(SIGNAL("stopAllDownloads"))
-
- def slotAdd(self):
- """
- add button (toolbar)
- show context menu (choice: links/package)
- """
- self.addMenu.exec_(QCursor.pos())
-
- def slotShowAddPackage(self):
- """
- action from add-menu
- show new-package dock
- """
- self.tabw.setCurrentIndex(1)
- self.newPackDock.show()
-
- def slotShowAddLinks(self):
- """
- action from add-menu
- show new-links dock
- """
- self.tabw.setCurrentIndex(1)
- self.newLinkDock.show()
-
- def slotShowConnector(self):
- """
- connectionmanager action triggered
- let main to the stuff
- """
- self.emit(SIGNAL("connector"))
-
- def slotAddPackage(self, name, links, password=None):
- """
- new package
- let main to the stuff
- """
- self.emit(SIGNAL("addPackage"), name, links, password)
-
- def slotAddLinksToPackage(self, links):
- """
- adds links to currently selected package
- only in collector
- """
- if self.tabw.currentIndex() != 1:
- return
-
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.connector.proxy.addFiles(item.id, links)
- break
-
- def slotShowAddContainer(self):
- """
- action from add-menu
- show file selector, emit upload
- """
- typeStr = ";;".join([
- _("All Container Types (%s)") % "*.dlc *.ccf *.rsdf *.txt",
- _("DLC (%s)") % "*.dlc",
- _("CCF (%s)") % "*.ccf",
- _("RSDF (%s)") % "*.rsdf",
- _("Text Files (%s)") % "*.txt"
- ])
- fileNames = QFileDialog.getOpenFileNames(self, _("Open container"), "", typeStr)
- for name in fileNames:
- self.emit(SIGNAL("addContainer"), str(name))
-
- def slotPushPackageToQueue(self):
- """
- push collector pack to queue
- get child ids
- let main to the rest
- """
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.emit(SIGNAL("pushPackageToQueue"), item.id)
- else:
- self.emit(SIGNAL("pushPackageToQueue"), item.package.id)
-
- def saveWindow(self):
- """
- get window state/geometry
- pass data to main
- """
- state_raw = self.saveState(self.version)
- geo_raw = self.saveGeometry()
-
- state = str(state_raw.toBase64())
- geo = str(geo_raw.toBase64())
-
- self.emit(SIGNAL("saveMainWindow"), state, geo)
-
- def closeEvent(self, event):
- """
- somebody wants to close me!
- let me first save my state
- """
- self.saveWindow()
- event.ignore()
- self.hide()
- self.emit(SIGNAL("hidden"))
-
- # quit when no tray is available
- if not QSystemTrayIcon.isSystemTrayAvailable():
- self.emit(SIGNAL("Quit"))
-
- def restoreWindow(self, state, geo):
- """
- restore window state/geometry
- """
- state = QByteArray(state)
- geo = QByteArray(geo)
-
- state_raw = QByteArray.fromBase64(state)
- geo_raw = QByteArray.fromBase64(geo)
-
- self.restoreState(state_raw, self.version)
- self.restoreGeometry(geo_raw)
-
- def slotQueueContextMenu(self, pos):
- """
- custom context menu in queue view requested
- """
- globalPos = self.tabs["queue"]["view"].mapToGlobal(pos)
- i = self.tabs["queue"]["view"].indexAt(pos)
- if not i:
- return
- item = i.internalPointer()
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.activeMenu = self.queueContext
- showAbort = False
- if isinstance(item, Link) and item.data["downloading"]:
- showAbort = True
- elif isinstance(item, Package):
- for child in item.children:
- if child.data["downloading"]:
- showAbort = True
- break
- if showAbort:
- self.queueContext.buttons["abort"].setEnabled(True)
- else:
- self.queueContext.buttons["abort"].setEnabled(False)
- if isinstance(item, Package):
- self.queueContext.index = i
- #self.queueContext.buttons["remove"].setEnabled(True)
- #self.queueContext.buttons["restart"].setEnabled(True)
- self.queueContext.buttons["pull"].setEnabled(True)
- self.queueContext.buttons["edit"].setEnabled(True)
- elif isinstance(item, Link):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(True)
- else:
- self.queueContext.index = None
- #self.queueContext.buttons["remove"].setEnabled(False)
- #self.queueContext.buttons["restart"].setEnabled(False)
- self.queueContext.buttons["pull"].setEnabled(False)
- self.queueContext.buttons["edit"].setEnabled(False)
- self.queueContext.exec_(menuPos)
-
- def slotCollectorContextMenu(self, pos):
- """
- custom context menu in package collector view requested
- """
- globalPos = self.tabs["collector"]["package_view"].mapToGlobal(pos)
- i = self.tabs["collector"]["package_view"].indexAt(pos)
- if not i:
- return
- item = i.internalPointer()
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.activeMenu = self.collectorContext
- if isinstance(item, Package):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(True)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(True)
- self.collectorContext.buttons["restart"].setEnabled(True)
- elif isinstance(item, Link):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(True)
- else:
- self.collectorContext.index = None
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(False)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(False)
- self.collectorContext.exec_(menuPos)
-
- def slotLinkCollectorContextMenu(self, pos):
- """
- custom context menu in link collector view requested
- """
- pass
-
- def slotRestartDownload(self):
- """
- restart download action is triggered
- """
- smodel = self.tabs["queue"]["view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("restartDownload"), item.id, isinstance(item, Package))
-
- def slotRemoveDownload(self):
- """
- remove download action is triggered
- """
- if self.activeMenu == self.queueContext:
- view = self.tabs["queue"]["view"]
- else:
- view = self.tabs["collector"]["package_view"]
- smodel = view.selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("removeDownload"), item.id, isinstance(item, Package))
-
- def slotToggleClipboard(self, status):
- """
- check clipboard (toolbar)
- """
- self.emit(SIGNAL("setClipboardStatus"), status)
-
- def slotEditPackage(self):
- # in Queue, only edit name
- if self.activeMenu == self.queueContext:
- view = self.tabs["queue"]["view"]
- else:
- view = self.tabs["collector"]["package_view"]
- view.edit(self.activeMenu.index)
-
- def slotEditCommit(self, editor):
- self.emit(SIGNAL("changePackageName"), self.activeMenu.index.internalPointer().id, editor.text())
-
- def slotPullOutPackage(self):
- """
- pull package out of the queue
- """
- smodel = self.tabs["queue"]["view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.emit(SIGNAL("pullOutPackage"), item.id)
- else:
- self.emit(SIGNAL("pullOutPackage"), item.package.id)
-
- def slotAbortDownload(self):
- view = self.tabs["queue"]["view"]
- smodel = view.selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("abortDownload"), item.id, isinstance(item, Package))
-
- # TODO disabled because changing desktop on linux, main window disappears
- #def changeEvent(self, e):
- # if e.type() == QEvent.WindowStateChange and self.isMinimized():
- # e.ignore()
- # self.hide()
- # self.emit(SIGNAL("hidden"))
- # else:
- # super(MainWindow, self).changeEvent(e)
-
- def slotTabChanged(self, index):
- if index == 2:
- self.emit(SIGNAL("reloadAccounts"))
- elif index == 3:
- self.tabs["settings"]["w"].loadConfig()
-
- def slotRefreshPackage(self):
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- pid = item.id
- if isinstance(item, Link):
- pid = item.package.id
- self.emit(SIGNAL("refreshStatus"), pid)
-
- def slotNewAccount(self):
- types = self.connector.proxy.getAccountTypes()
- self.accountEdit = AccountEdit.newAccount(types)
-
- #TODO make more easy n1, n2, n3
- def save(data):
- if data["password"]:
- self.accountEdit.close()
- n1 = data["acctype"]
- n2 = data["login"]
- n3 = data["password"]
- self.connector.updateAccount(n1, n2, n3, None)
-
- self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save)
- self.accountEdit.show()
-
- def slotEditAccount(self):
- types = self.connector.getAccountTypes()
-
- data = self.tabs["accounts"]["view"].selectedIndexes()
- if len(data) < 1:
- return
-
- data = data[0].internalPointer()
-
- self.accountEdit = AccountEdit.editAccount(types, data)
-
- #TODO make more easy n1, n2, n3
- #TODO reload accounts tab after insert of edit account
- #TODO if account does not exist give error
- def save(data):
- self.accountEdit.close()
- n1 = data["acctype"]
- n2 = data["login"]
- if data["password"]:
- n3 = data["password"]
- self.connector.updateAccount(n1, n2, n3, None)
-
- self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save)
- self.accountEdit.show()
-
- def slotRemoveAccount(self):
- data = self.tabs["accounts"]["view"].selectedIndexes()
- if len(data) < 1:
- return
-
- data = data[0].internalPointer()
-
- self.connector.removeAccount(data.type, data.login)
-
- def slotAccountContextMenu(self, pos):
- globalPos = self.tabs["accounts"]["view"].mapToGlobal(pos)
- i = self.tabs["accounts"]["view"].indexAt(pos)
- if not i:
- return
-
- data = i.internalPointer()
-
- if data is None:
- self.accountContext.buttons["edit"].setEnabled(False)
- self.accountContext.buttons["remove"].setEnabled(False)
- else:
- self.accountContext.buttons["edit"].setEnabled(True)
- self.accountContext.buttons["remove"].setEnabled(True)
-
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.accountContext.exec_(menuPos)
diff --git a/module/gui/Overview.py b/module/gui/Overview.py
deleted file mode 100644
index 183383b5e..000000000
--- a/module/gui/Overview.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from module.utils import formatSpeed, formatSize
-
-class OverviewModel(QAbstractListModel):
- PackageName = 10
- Progress = 11
- PartsFinished = 12
- Parts = 13
- ETA = 14
- Speed = 15
- CurrentSize = 16
- MaxSize = 17
- Status = 18
-
- def __init__(self, view, connector):
- QAbstractListModel.__init__(self)
-
- self.packages = []
-
- def queueChanged(self): #dirty..
- self.beginResetModel()
-
- self.packages = []
-
- def partsFinished(p):
- f = 0
- for c in p.children:
- if c.data["status"] == 0:
- f += 1
- return f
-
- def maxSize(p):
- ms = 0
- cs = 0
- for c in p.children:
- try:
- s = c.data["downloading"]["size"]
- except:
- s = c.data["size"]
- if c.data["downloading"]:
- cs += s - c.data["downloading"]["bleft"]
- elif self.queue.getProgress(c, False) == 100:
- cs += s
- ms += s
- return ms, cs
-
- def getProgress(p):
- for c in p.children:
- if c.data["status"] == 13:
- pass # TODO return _("Unpacking"), int(c.data["progress"])
- return _("Downloading"), self.queue.getProgress(p)
-
- d = self.queue._data
- for p in d:
- status, progress = getProgress(p)
- maxsize, currentsize = maxSize(p)
- speed = self.queue.getSpeed(p)
- if speed:
- eta = (maxsize - (maxsize * (progress/100.0)))/speed
- else:
- eta = 0
- if not speed and not progress:
- status = _("Queued")
- info = {
- OverviewModel.PackageName: p.data["name"],
- OverviewModel.Progress: progress,
- OverviewModel.PartsFinished: partsFinished(p),
- OverviewModel.Parts: len(p.children),
- OverviewModel.ETA: int(eta),
- OverviewModel.Speed: speed,
- OverviewModel.CurrentSize: currentsize,
- OverviewModel.MaxSize: maxsize,
- OverviewModel.Status: status,
- }
-
- self.packages.append(info)
-
- self.endResetModel()
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- return QVariant(_("Package"))
-
- def rowCount(self, parent=QModelIndex()):
- return len(self.packages)
-
- def data(self, index, role=Qt.DisplayRole):
- if role in [OverviewModel.PackageName, OverviewModel.Progress, OverviewModel.PartsFinished, OverviewModel.Parts, OverviewModel.ETA, OverviewModel.Speed, OverviewModel.CurrentSize, OverviewModel.MaxSize, OverviewModel.Status]:
- return QVariant(self.packages[index.row()][role])
- return QVariant()
-
-class OverviewView(QListView):
- def __init__(self, connector):
- QListView.__init__(self)
- self.setModel(OverviewModel(self, connector))
-
- self.setAlternatingRowColors(True)
- self.delegate = OverviewDelegate(self)
- self.setItemDelegate(self.delegate)
-
-class OverviewDelegate(QItemDelegate):
- def __init__(self, parent):
- QItemDelegate.__init__(self, parent)
- self.parent = parent
- self.model = parent.model()
-
- def paint(self, painter, option, index):
- option.rect.setHeight(59+16)
- option.rect.setWidth(self.parent.width()-20)
-
- #if option.state & QStyle.State_Selected:
- # painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
-
- packagename = index.data(OverviewModel.PackageName).toString()
- partsf = index.data(OverviewModel.PartsFinished).toString()
- parts = index.data(OverviewModel.Parts).toString()
- eta = int(index.data(OverviewModel.ETA).toString())
- speed = index.data(OverviewModel.Speed).toString() or 0
- progress = int(index.data(OverviewModel.Progress).toString())
- currentSize = int(index.data(OverviewModel.CurrentSize).toString())
- maxSize = int(index.data(OverviewModel.MaxSize).toString())
- status = index.data(OverviewModel.Status).toString()
-
- def formatEta(seconds): #TODO add to utils
- if seconds <= 0: return ""
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return _("ETA: ") + "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- statusline = QString(_("Parts: ") + "%s/%s" % (partsf, parts))
- if partsf == parts:
- speedline = _("Finished")
- elif not status == _("Downloading"):
- speedline = QString(status)
- else:
- speedline = QString(formatEta(eta) + " " + _("Speed: %s") % formatSpeed(speed))
-
- if progress in (0,100):
- sizeline = QString(_("Size:") + "%s" % formatSize(maxSize))
- else:
- sizeline = QString(_("Size:") + "%s / %s" % (formatSize(currentSize), formatSize(maxSize)))
-
- f = painter.font()
- f.setPointSize(12)
- f.setBold(True)
- painter.setFont(f)
-
- r = option.rect.adjusted(4, 4, -4, -4)
- painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename)
- newr = painter.boundingRect(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename)
-
- f.setPointSize(10)
- f.setBold(False)
- painter.setFont(f)
-
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline)
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignHCenter, sizeline)
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignRight, speedline)
- newr = painter.boundingRect(r.left(), newr.bottom()+2, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline)
- newr.setTop(newr.bottom()+8)
- newr.setBottom(newr.top()+20)
- newr.setRight(self.parent.width()-25)
-
- f.setPointSize(10)
- painter.setFont(f)
-
- opts = QStyleOptionProgressBarV2()
- opts.maximum = 100
- opts.minimum = 0
- opts.progress = progress
- opts.rect = newr
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- opts.text = QString.number(opts.progress) + "%"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
-
- def sizeHint(self, option, index):
- return QSize(self.parent.width()-22, 59+16)
diff --git a/module/gui/PackageDock.py b/module/gui/PackageDock.py
deleted file mode 100644
index 73db8f177..000000000
--- a/module/gui/PackageDock.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-import re
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class NewPackageDock(QDockWidget):
- def __init__(self):
- QDockWidget.__init__(self, _("New Package"))
- self.setObjectName("New Package Dock")
- self.widget = NewPackageWindow(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea)
- self.hide()
-
- def slotDone(self):
- text = str(self.widget.box.toPlainText())
- pw = str(self.widget.passwordInput.text())
- if not pw:
- pw = None
- lines = []
- for line in text.splitlines():
- line = line.strip()
- if not line:
- continue
- lines.append(line)
- self.emit(SIGNAL("done"), str(self.widget.nameInput.text()), lines, pw)
- self.widget.nameInput.setText("")
- self.widget.passwordInput.setText("")
- self.widget.box.clear()
- self.hide()
-
- def parseUri(self):
-
- text=str(self.widget.box.toPlainText())
- self.widget.box.setText("")
- result = re.findall(r"(?:ht|f)tps?:\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}", text)
- for url in result:
- if "\n" or "\t" or "\r" or "\"" or "<" or "'" in url:
- url = url[:-1]
- self.widget.box.append("%s " % url)
-
-class NewPackageWindow(QWidget):
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QGridLayout())
- layout = self.layout()
-
- nameLabel = QLabel(_("Name"))
- nameInput = QLineEdit()
- passwordLabel = QLabel(_("Password"))
- passwordInput = QLineEdit()
-
- linksLabel = QLabel(_("Links in this Package"))
-
- self.box = QTextEdit()
- self.nameInput = nameInput
- self.passwordInput = passwordInput
-
- save = QPushButton(_("Create"))
- parseUri = QPushButton(_("Filter URLs"))
-
- layout.addWidget(nameLabel, 0, 0)
- layout.addWidget(nameInput, 0, 1)
- layout.addWidget(passwordLabel, 1, 0)
- layout.addWidget(passwordInput, 1, 1)
- layout.addWidget(linksLabel, 2, 0, 1, 2)
- layout.addWidget(self.box, 3, 0, 1, 2)
- layout.addWidget(parseUri, 4, 0, 1, 2)
- layout.addWidget(save, 5, 0, 1, 2)
-
- self.connect(save, SIGNAL("clicked()"), self.dock.slotDone)
- self.connect(parseUri, SIGNAL("clicked()"), self.dock.parseUri) \ No newline at end of file
diff --git a/module/gui/Queue.py b/module/gui/Queue.py
deleted file mode 100644
index 0a0cbb810..000000000
--- a/module/gui/Queue.py
+++ /dev/null
@@ -1,390 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from time import time
-
-from module.remote.thriftbackend.ThriftClient import Destination
-from module.gui.Collector import CollectorModel, Package, Link, CollectorView, statusMapReverse
-from module.utils import formatSize, formatSpeed
-
-class QueueModel(CollectorModel):
- """
- model for the queue view, inherits from CollectorModel
- """
-
- def __init__(self, view, connector):
- CollectorModel.__init__(self, view, connector)
- self.cols = 6
- self.wait_dict = {}
-
- self.updater = self.QueueUpdater(self.interval)
- self.connect(self.updater, SIGNAL("update()"), self.update)
-
- class QueueUpdater(QObject):
- """
- timer which emits signal for a download status reload
- @TODO: make intervall configurable
- """
-
- def __init__(self, interval):
- QObject.__init__(self)
-
- self.interval = interval
- self.timer = QTimer()
- self.timer.connect(self.timer, SIGNAL("timeout()"), self, SIGNAL("update()"))
-
- def start(self):
- self.timer.start(1000)
-
- def stop(self):
- self.timer.stop()
-
- def start(self):
- self.updater.start()
-
- def stop(self):
- self.updater.stop()
-
- def fullReload(self):
- """
- reimplements CollectorModel.fullReload, because we want the Queue data
- """
- self._data = []
- order = self.connector.getPackageOrder(Destination.Queue)
- self.beginInsertRows(QModelIndex(), 0, len(order.values()))
- for position, pid in order.iteritems():
- pack = self.connector.getPackageData(pid)
- package = Package(pack)
- self._data.append(package)
- self._data = sorted(self._data, key=lambda p: p.data["order"])
- self.endInsertRows()
- self.updateCount()
-
- def insertEvent(self, event):
- """
- wrap CollectorModel.insertEvent to update the element count
- """
- CollectorModel.insertEvent(self, event)
- self.updateCount()
-
- def removeEvent(self, event):
- """
- wrap CollectorModel.removeEvent to update the element count
- """
- CollectorModel.removeEvent(self, event)
- self.updateCount()
-
- def updateEvent(self, event):
- """
- wrap CollectorModel.updateEvent to update the element count
- """
- CollectorModel.updateEvent(self, event)
- self.updateCount()
-
- def updateCount(self):
- """
- calculate package- and filecount for statusbar,
- ugly?: Overview connects to this signal for updating
- """
- packageCount = len(self._data)
- fileCount = 0
- for p in self._data:
- fileCount += len(p.children)
- self.mutex.unlock()
- self.emit(SIGNAL("updateCount"), packageCount, fileCount)
- self.mutex.lock()
-
- def update(self):
- """
- update slot for download status updating
- """
- locker = QMutexLocker(self.mutex)
- downloading = self.connector.statusDownloads()
- if not downloading:
- return
- for p, pack in enumerate(self._data):
- for d in downloading:
- child = pack.getChild(d.fid)
- if child:
- dd = {
- "name": d.name,
- "speed": d.speed,
- "eta": d.eta,
- "format_eta": d.format_eta,
- "bleft": d.bleft,
- "size": d.size,
- "format_size": d.format_size,
- "percent": d.percent,
- "status": d.status,
- "statusmsg": d.statusmsg,
- "format_wait": d.format_wait,
- "wait_until": d.wait_until
- }
- child.data["downloading"] = dd
- k = pack.getChildKey(d.fid)
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
- self.updateCount()
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Name"))
- elif section == 2:
- return QVariant(_("Status"))
- elif section == 1:
- return QVariant(_("Plugin"))
- elif section == 3:
- return QVariant(_("Size"))
- elif section == 4:
- return QVariant(_("ETA"))
- elif section == 5:
- return QVariant(_("Progress"))
- return QVariant()
-
- def getWaitingProgress(self, item):
- """
- returns time to wait, caches startingtime to provide progress
- """
- locker = QMutexLocker(self.mutex)
- if isinstance(item, Link):
- if item.data["status"] == 5 and item.data["downloading"]:
- until = float(item.data["downloading"]["wait_until"])
- try:
- since, until_old = self.wait_dict[item.id]
- if not until == until_old:
- raise Exception
- except:
- since = time()
- self.wait_dict[item.id] = since, until
- since = float(since)
- max_wait = float(until-since)
- rest = int(until-time())
- if rest < 0:
- return 0, None
- res = 100/max_wait
- perc = rest*res
- return perc, rest
- return None
-
- def getProgress(self, item, locked=True):
- """
- return download progress, locks by default
- since it's used in already locked calls,
- it provides an option to not lock
- """
- if locked:
- locker = QMutexLocker(self.mutex)
- if isinstance(item, Link):
- try:
- if item.data["status"] == 0:
- return 100
- return int(item.data["downloading"]["percent"])
- except:
- return 0
- elif isinstance(item, Package):
- count = len(item.children)
- perc_sum = 0
- for child in item.children:
- try:
- if child.data["status"] == 0: #completed
- perc_sum += 100
- perc_sum += int(child.data["downloading"]["percent"])
- except:
- pass
- if count == 0:
- return 0
- return perc_sum/count
- return 0
-
- def getSpeed(self, item):
- """
- calculate download speed
- """
- if isinstance(item, Link):
- if item.data["downloading"]:
- return int(item.data["downloading"]["speed"])
- elif isinstance(item, Package):
- count = len(item.children)
- speed_sum = 0
- all_waiting = True
- running = False
- for child in item.children:
- val = 0
- if child.data["downloading"]:
- if not child.data["statusmsg"] == "waiting":
- all_waiting = False
- val = int(child.data["downloading"]["speed"])
- running = True
- speed_sum += val
- if count == 0 or not running or all_waiting:
- return None
- return speed_sum
- return None
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- elif index.column() == 1:
- item = index.internalPointer()
- plugins = []
- if isinstance(item, Package):
- for child in item.children:
- if not child.data["plugin"] in plugins:
- plugins.append(child.data["plugin"])
- else:
- plugins.append(item.data["plugin"])
- return QVariant(", ".join(plugins))
- elif index.column() == 2:
- item = index.internalPointer()
- status = 0
- speed = self.getSpeed(item)
- if isinstance(item, Package):
- for child in item.children:
- if child.data["status"] > status:
- status = child.data["status"]
- else:
- status = item.data["status"]
-
- if speed is None or status == 7 or status == 10 or status == 5:
- return QVariant(self.translateStatus(statusMapReverse[status]))
- else:
- return QVariant("%s (%s)" % (self.translateStatus(statusMapReverse[status]), formatSpeed(speed)))
- elif index.column() == 3:
- item = index.internalPointer()
- if isinstance(item, Link):
- if item.data["status"] == 0: #TODO needs change??
- #self.getProgress(item, False) == 100:
- return QVariant(formatSize(item.data["size"]))
- elif self.getProgress(item, False) == 0:
- try:
- return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"])))
- except:
- return QVariant("0 B / %s" % formatSize(item.data["size"]))
- else:
- try:
- return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"])))
- except:
- return QVariant("? / %s" % formatSize(item.data["size"]))
- else:
- ms = 0
- cs = 0
- for c in item.children:
- try:
- s = c.data["downloading"]["size"]
- except:
- s = c.data["size"]
- if c.data["downloading"]:
- cs += s - c.data["downloading"]["bleft"]
- elif self.getProgress(c, False) == 100:
- cs += s
- ms += s
- if cs == 0 or cs == ms:
- return QVariant(formatSize(ms))
- else:
- return QVariant("%s / %s" % (formatSize(cs), formatSize(ms)))
- elif index.column() == 4:
- item = index.internalPointer()
- if isinstance(item, Link):
- if item.data["downloading"]:
- return QVariant(item.data["downloading"]["format_eta"])
- elif role == Qt.EditRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- if index.column() == 0 and self.parent(index) == QModelIndex():
- return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
-class QueueView(CollectorView):
- """
- view component for queue
- """
-
- def __init__(self, connector):
- CollectorView.__init__(self, connector)
- self.setModel(QueueModel(self, connector))
-
- self.setColumnWidth(0, 300)
- self.setColumnWidth(1, 100)
- self.setColumnWidth(2, 140)
- self.setColumnWidth(3, 180)
- self.setColumnWidth(4, 70)
-
- self.setEditTriggers(QAbstractItemView.NoEditTriggers)
-
- self.delegate = QueueProgressBarDelegate(self, self.model())
- self.setItemDelegateForColumn(5, self.delegate)
-
-class QueueProgressBarDelegate(QItemDelegate):
- """
- used to display a progressbar in the progress cell
- """
-
- def __init__(self, parent, queue):
- QItemDelegate.__init__(self, parent)
- self.queue = queue
-
- def paint(self, painter, option, index):
- """
- paint the progressbar
- """
- if not index.isValid():
- return
- if index.column() == 5:
- item = index.internalPointer()
- w = self.queue.getWaitingProgress(item)
- wait = None
- if w:
- progress = w[0]
- wait = w[1]
- else:
- progress = self.queue.getProgress(item)
- opts = QStyleOptionProgressBarV2()
- opts.maximum = 100
- opts.minimum = 0
- opts.progress = progress
- opts.rect = option.rect
- opts.rect.setRight(option.rect.right()-1)
- opts.rect.setHeight(option.rect.height()-1)
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- if not wait is None:
- opts.text = QString(_("waiting %d seconds") % (wait,))
- else:
- opts.text = QString.number(opts.progress) + "%"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
- return
- QItemDelegate.paint(self, painter, option, index)
-
diff --git a/module/gui/SettingsWidget.py b/module/gui/SettingsWidget.py
deleted file mode 100644
index cd22a7b9e..000000000
--- a/module/gui/SettingsWidget.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from sip import delete
-
-
-class SettingsWidget(QWidget):
- def __init__(self):
- QWidget.__init__(self)
- self.connector = None
- self.sections = {}
- self.psections = {}
- self.data = None
- self.pdata = None
- self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
-
- def setConnector(self, connector):
- self.connector = connector
-
- def loadConfig(self):
- if self.sections and self.psections:
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
-
- self.reloadSection(self.sections, self.data)
- self.reloadSection(self.psections, self.pdata)
-
- return
-
- if self.layout():
- delete(self.layout())
-
- for s in self.sections.values()+self.psections.values():
- delete(s)
-
- self.sections = {}
- self.setLayout(QVBoxLayout())
- self.clearConfig()
- layout = self.layout()
- layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
-
- general = QTabWidget()
- self.general = general
-
- plugins = QTabWidget()
- self.plugins = plugins
-
- tab = QTabWidget()
- self.tab = tab
-
- gw = QWidget()
- gw.setLayout(QVBoxLayout())
- gw.layout().addWidget(self.general)
- pw = QWidget()
- pw.setLayout(QVBoxLayout())
- pw.layout().addWidget(self.plugins)
- tab.addTab(gw, _("General"))
- tab.addTab(pw, _("Plugins"))
-
- layout.addWidget(tab)
-
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
- for k, section in self.data.iteritems():
- s = Section(section, general)
- self.sections[k] = s
-
- for k, section in self.pdata.iteritems():
- s = Section(section, plugins, "plugin")
- self.psections[k] = s
-
- rel = QPushButton(_("Reload"))
- save = QPushButton(_("Save"))
-
- layout.addWidget(save)
-
- cont = QHBoxLayout()
- cont.addWidget(rel)
- cont.addWidget(save)
-
- layout.addLayout(cont)
-
- self.connect(save, SIGNAL("clicked()"), self.saveConfig)
- self.connect(rel, SIGNAL("clicked()"), self.loadConfig)
-
- def clearConfig(self):
- self.sections = {}
-
- def reloadSection(self, sections, pdata):
-
- for k, section in enumerate(pdata):
- if k in sections:
- widget = sections[k]
- for item in section.items:
- if item.name in widget.inputs:
- i = widget.inputs[item.name]
-
- if item.type == "int":
- i.setValue(int(item.value))
- elif not item.type.find(";") == -1:
- i.setCurrentIndex(i.findText(item.value))
- elif item.type == "bool":
- if True if item.value.lower() in ("1","true", "on", "an","yes") else False:
- i.setCurrentIndex(0)
- else:
- i.setCurrentIndex(1)
- else:
- i.setText(item.value)
-
-
- def saveConfig(self):
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
-
- self.saveSection(self.sections, self.data)
- self.saveSection(self.psections, self.pdata, "plugin")
-
-
- def saveSection(self, sections, pdata, sec="core"):
- for k, section in enumerate(pdata):
- if k in sections:
- widget = sections[k]
- for item in section.items:
- if item.name in widget.inputs:
- i = widget.inputs[item.name]
-
- #TODO : unresolved reference: option
-
- if item.type == "int":
- if i.value() != int(item.value):
- self.connector.setConfigValue(k, option, i.value(), sec)
- elif not item.type.find(";") == -1:
- if i.currentText() != item.value:
- self.connector.setConfigValue(k, option, i.currentText(), sec)
- elif item.type == "bool":
- if (True if item.value.lower() in ("1","true", "on", "an","yes") else False) ^ (not i.currentIndex()):
- self.connector.setConfigValue(k, option, not i.currentIndex(), sec)
- else:
- if i.text() != item.value:
- self.connector.setConfigValue(k, option, str(i.text()), sec)
-
-class Section(QGroupBox):
- def __init__(self, data, parent, ctype="core"):
- self.data = data
- QGroupBox.__init__(self, data.description, parent)
- self.labels = {}
- self.inputs = {}
- self.ctype = ctype
- layout = QFormLayout(self)
- self.setLayout(layout)
-
- sw = QWidget()
- sw.setLayout(QVBoxLayout())
- sw.layout().addWidget(self)
-
- sa = QScrollArea()
- sa.setWidgetResizable(True)
- sa.setWidget(sw)
- sa.setFrameShape(sa.NoFrame)
-
- parent.addTab(sa, data.description)
-
- for option in self.data.items:
- if option.type == "int":
- i = QSpinBox(self)
- i.setMaximum(999999)
- i.setValue(int(option.value))
- elif not option.type.find(";") == -1:
- choices = option.type.split(";")
- i = QComboBox(self)
- i.addItems(choices)
- i.setCurrentIndex(i.findText(option.value))
- elif option.type == "bool":
- i = QComboBox(self)
- i.addItem(_("Yes"), QVariant(True))
- i.addItem(_("No"), QVariant(False))
- if True if option.value.lower() in ("1","true", "on", "an","yes") else False:
- i.setCurrentIndex(0)
- else:
- i.setCurrentIndex(1)
- else:
- i = QLineEdit(self)
- i.setText(option.value)
- layout.addRow(option.description, i)
- layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
diff --git a/module/gui/XMLParser.py b/module/gui/XMLParser.py
deleted file mode 100644
index 5e3b7bf65..000000000
--- a/module/gui/XMLParser.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-from __future__ import with_statement
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4.QtXml import *
-
-import os
-
-class XMLParser():
- def __init__(self, data, dfile=""):
- self.mutex = QMutex()
- self.mutex.lock()
- self.xml = QDomDocument()
- self.file = data
- self.dfile = dfile
- self.mutex.unlock()
- self.loadData()
- self.root = self.xml.documentElement()
-
- def loadData(self):
- self.mutex.lock()
- f = self.file
- if not os.path.exists(f):
- f = self.dfile
- with open(f, 'r') as fh:
- content = fh.read()
- self.xml.setContent(content)
- self.mutex.unlock()
-
- def saveData(self):
- self.mutex.lock()
- content = self.xml.toString()
- with open(self.file, 'w') as fh:
- fh.write(content)
- self.mutex.unlock()
- return content
-
- def parseNode(self, node, ret_type="list"):
- if ret_type == "dict":
- childNodes = {}
- else:
- childNodes = []
- child = node.firstChild()
- while True:
- n = child.toElement()
- if n.isNull():
- break
- else:
- if ret_type == "dict":
- childNodes[str(n.tagName())] = n
- else:
- childNodes.append(n)
- child = child.nextSibling()
- return childNodes
diff --git a/module/gui/__init__.py b/module/gui/__init__.py
deleted file mode 100644
index 8d1c8b69c..000000000
--- a/module/gui/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/module/gui/connector.py b/module/gui/connector.py
deleted file mode 100644
index c16ccd08e..000000000
--- a/module/gui/connector.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-SERVER_VERSION = "0.4.9"
-
-from time import sleep
-from uuid import uuid4 as uuid
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-import socket
-
-from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin, NoSSL, NoConnection
-from thrift.Thrift import TException
-
-class Connector(QObject):
- """
- manages the connection to the pyload core via thrift
- """
-
- firstAttempt = True
-
- def __init__(self):
- QObject.__init__(self)
- self.mutex = QMutex()
- self.connectionID = None
- self.host = None
- self.port = None
- self.user = None
- self.password = None
- self.ssl = None
- self.running = True
- self.internal = False
- self.proxy = self.Dummy()
-
- def setConnectionData(self, host, port, user, password, ssl=False):
- """
- set connection data for connection attempt, called from slotConnect
- """
- self.host = host
- self.port = port
- self.user = user
- self.password = password
- self.ssl = ssl
-
- def connectProxy(self):
- """
- initialize thrift rpc client,
- check for ssl, check auth,
- setup dispatcher,
- connect error signals,
- check server version
- """
- if self.internal: return True
-
- err = None
- try:
- client = ThriftClient(self.host, self.port, self.user, self.password)
- except WrongLogin:
- err = _("bad login credentials")
- except NoSSL:
- err = _("no ssl support")
- except NoConnection:
- err = _("can't connect to host")
- if err:
- if not Connector.firstAttempt:
- self.emit(SIGNAL("errorBox"), err)
- Connector.firstAttempt = False
- return False
-
- self.proxy = DispatchRPC(self.mutex, client)
- self.connect(self.proxy, SIGNAL("connectionLost"), self, SIGNAL("connectionLost"))
-
- server_version = self.proxy.getServerVersion()
- self.connectionID = uuid().hex
-
- if not server_version == SERVER_VERSION:
- self.emit(SIGNAL("errorBox"), _("server is version %(new)s client accepts version %(current)s") % { "new": server_version, "current": SERVER_VERSION})
- return False
-
- return True
-
- def __getattr__(self, attr):
- """
- redirect rpc calls to dispatcher
- """
- return getattr(self.proxy, attr)
-
- class Dummy(object):
- """
- dummy rpc proxy, to prevent errors
- """
- def __nonzero__(self):
- return False
-
- def __getattr__(self, attr):
- def dummy(*args, **kwargs):
- return None
- return dummy
-
-class DispatchRPC(QObject):
- """
- wraps the thrift client, to catch critical exceptions (connection lost)
- adds thread safety
- """
-
- def __init__(self, mutex, server):
- QObject.__init__(self)
- self.mutex = mutex
- self.server = server
-
- def __getattr__(self, attr):
- """
- redirect and wrap call in Wrapper instance, locks dispatcher
- """
- self.mutex.lock()
- self.fname = attr
- f = self.Wrapper(getattr(self.server, attr), self.mutex, self)
- return f
-
- class Wrapper(object):
- """
- represents a rpc call
- """
-
- def __init__(self, f, mutex, dispatcher):
- self.f = f
- self.mutex = mutex
- self.dispatcher = dispatcher
-
- def __call__(self, *args, **kwargs):
- """
- instance is called, rpc is executed
- exceptions are processed
- finally dispatcher is unlocked
- """
- lost = False
- try:
- return self.f(*args, **kwargs)
- except socket.error: #necessary?
- lost = True
- except TException:
- lost = True
- finally:
- self.mutex.unlock()
- if lost:
- from traceback import print_exc
- print_exc()
- self.dispatcher.emit(SIGNAL("connectionLost"))
diff --git a/module/lib/beaker/ext/__init__.py b/module/lib/beaker/ext/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/lib/beaker/ext/__init__.py
+++ /dev/null
diff --git a/module/network/Browser.py b/module/network/Browser.py
deleted file mode 100644
index d68a23687..000000000
--- a/module/network/Browser.py
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from logging import getLogger
-
-from HTTPRequest import HTTPRequest
-from HTTPDownload import HTTPDownload
-
-
-class Browser(object):
- __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl")
-
- def __init__(self, bucket=None, options={}):
- self.log = getLogger("log")
-
- self.options = options #holds pycurl options
- self.bucket = bucket
-
- self.cj = None # needs to be setted later
- self._size = 0
-
- self.renewHTTPRequest()
- self.dl = None
-
-
- def renewHTTPRequest(self):
- if hasattr(self, "http"): self.http.close()
- self.http = HTTPRequest(self.cj, self.options)
-
- def setLastURL(self, val):
- self.http.lastURL = val
-
- # tunnel some attributes from HTTP Request to Browser
- lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL)
- lastURL = property(lambda self: self.http.lastURL, setLastURL)
- code = property(lambda self: self.http.code)
- cookieJar = property(lambda self: self.cj)
-
- def setCookieJar(self, cj):
- self.cj = cj
- self.http.cj = cj
-
- @property
- def speed(self):
- if self.dl:
- return self.dl.speed
- return 0
-
- @property
- def size(self):
- if self._size:
- return self._size
- if self.dl:
- return self.dl.size
- return 0
-
- @property
- def arrived(self):
- if self.dl:
- return self.dl.arrived
- return 0
-
- @property
- def percent(self):
- if not self.size: return 0
- return (self.arrived * 100) / self.size
-
- def clearCookies(self):
- if self.cj:
- self.cj.clear()
- self.http.clearCookies()
-
- def clearReferer(self):
- self.http.lastURL = None
-
- def abortDownloads(self):
- self.http.abort = True
- if self.dl:
- self._size = self.dl.size
- self.dl.abort = True
-
- def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False,
- progressNotify=None, disposition=False):
- """ this can also download ftp """
- self._size = 0
- self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None,
- self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition)
- name = self.dl.download(chunks, resume)
- self._size = self.dl.size
-
- self.dl = None
-
- return name
-
- def load(self, *args, **kwargs):
- """ retrieves page """
- return self.http.load(*args, **kwargs)
-
- def putHeader(self, name, value):
- """ add a header to the request """
- self.http.putHeader(name, value)
-
- def addAuth(self, pwd):
- """Adds user and pw for http auth
-
- :param pwd: string, user:password
- """
- self.options["auth"] = pwd
- self.renewHTTPRequest() #we need a new request
-
- def removeAuth(self):
- if "auth" in self.options: del self.options["auth"]
- self.renewHTTPRequest()
-
- def setOption(self, name, value):
- """Adds an option to the request, see HTTPRequest for existing ones"""
- self.options[name] = value
-
- def deleteOption(self, name):
- if name in self.options: del self.options[name]
-
- def clearHeaders(self):
- self.http.clearHeaders()
-
- def close(self):
- """ cleanup """
- if hasattr(self, "http"):
- self.http.close()
- del self.http
- if hasattr(self, "dl"):
- del self.dl
- if hasattr(self, "cj"):
- del self.cj
-
-if __name__ == "__main__":
- browser = Browser()#proxies={"socks5": "localhost:5000"})
- ip = "http://www.whatismyip.com/automation/n09230945.asp"
- #browser.getPage("http://google.com/search?q=bar")
- #browser.getPage("https://encrypted.google.com/")
- #print browser.getPage(ip)
- #print browser.getRedirectLocation("http://google.com/")
- #browser.getPage("https://encrypted.google.com/")
- #browser.getPage("http://google.com/search?q=bar")
-
- browser.httpDownload("http://speedtest.netcologne.de/test_10mb.bin", "test_10mb.bin")
-
diff --git a/module/network/Bucket.py b/module/network/Bucket.py
deleted file mode 100644
index 69da277ae..000000000
--- a/module/network/Bucket.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from time import time
-from threading import Lock
-
-class Bucket:
- def __init__(self):
- self.rate = 0
- self.tokens = 0
- self.timestamp = time()
- self.lock = Lock()
-
- def __nonzero__(self):
- return False if self.rate < 10240 else True
-
- def setRate(self, rate):
- self.lock.acquire()
- self.rate = int(rate)
- self.lock.release()
-
- def consumed(self, amount):
- """ return time the process have to sleep, after consumed specified amount """
- if self.rate < 10240: return 0 #min. 10kb, may become unresponsive otherwise
- self.lock.acquire()
-
- self.calc_tokens()
- self.tokens -= amount
-
- if self.tokens < 0:
- time = -self.tokens/float(self.rate)
- else:
- time = 0
-
-
- self.lock.release()
- return time
-
- def calc_tokens(self):
- if self.tokens < self.rate:
- now = time()
- delta = self.rate * (now - self.timestamp)
- self.tokens = min(self.rate, self.tokens + delta)
- self.timestamp = now
-
diff --git a/module/network/CookieJar.py b/module/network/CookieJar.py
deleted file mode 100644
index c05812334..000000000
--- a/module/network/CookieJar.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from time import time
-
-class CookieJar():
- def __init__(self, pluginname, account=None):
- self.cookies = {}
- self.plugin = pluginname
- self.account = account
-
- def addCookies(self, clist):
- for c in clist:
- name = c.split("\t")[5]
- self.cookies[name] = c
-
- def getCookies(self):
- return self.cookies.values()
-
- def parseCookie(self, name):
- if name in self.cookies:
- return self.cookies[name].split("\t")[6]
- else:
- return None
-
- def getCookie(self, name):
- return self.parseCookie(name)
-
- def setCookie(self, domain, name, value, path="/", exp=time()+3600*24*180):
- s = ".%s TRUE %s FALSE %s %s %s" % (domain, path, exp, name, value)
- self.cookies[name] = s
-
- def clear(self):
- self.cookies = {}
diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py
deleted file mode 100644
index b637aef32..000000000
--- a/module/network/HTTPChunk.py
+++ /dev/null
@@ -1,293 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from os import remove, stat, fsync
-from os.path import exists
-from time import sleep
-from re import search
-from module.utils import fs_encode
-import codecs
-import pycurl
-
-from HTTPRequest import HTTPRequest
-
-class WrongFormat(Exception):
- pass
-
-
-class ChunkInfo():
- def __init__(self, name):
- self.name = unicode(name)
- self.size = 0
- self.resume = False
- self.chunks = []
-
- def __repr__(self):
- ret = "ChunkInfo: %s, %s\n" % (self.name, self.size)
- for i, c in enumerate(self.chunks):
- ret += "%s# %s\n" % (i, c[1])
-
- return ret
-
- def setSize(self, size):
- self.size = int(size)
-
- def addChunk(self, name, range):
- self.chunks.append((name, range))
-
- def clear(self):
- self.chunks = []
-
- def createChunks(self, chunks):
- self.clear()
- chunk_size = self.size / chunks
-
- current = 0
- for i in range(chunks):
- end = self.size - 1 if (i == chunks - 1) else current + chunk_size
- self.addChunk("%s.chunk%s" % (self.name, i), (current, end))
- current += chunk_size + 1
-
-
- def save(self):
- fs_name = fs_encode("%s.chunks" % self.name)
- fh = codecs.open(fs_name, "w", "utf_8")
- fh.write("name:%s\n" % self.name)
- fh.write("size:%s\n" % self.size)
- for i, c in enumerate(self.chunks):
- fh.write("#%d:\n" % i)
- fh.write("\tname:%s\n" % c[0])
- fh.write("\trange:%i-%i\n" % c[1])
- fh.close()
-
- @staticmethod
- def load(name):
- fs_name = fs_encode("%s.chunks" % name)
- if not exists(fs_name):
- raise IOError()
- fh = codecs.open(fs_name, "r", "utf_8")
- name = fh.readline()[:-1]
- size = fh.readline()[:-1]
- if name.startswith("name:") and size.startswith("size:"):
- name = name[5:]
- size = size[5:]
- else:
- fh.close()
- raise WrongFormat()
- ci = ChunkInfo(name)
- ci.loaded = True
- ci.setSize(size)
- while True:
- if not fh.readline(): #skip line
- break
- name = fh.readline()[1:-1]
- range = fh.readline()[1:-1]
- if name.startswith("name:") and range.startswith("range:"):
- name = name[5:]
- range = range[6:].split("-")
- else:
- raise WrongFormat()
-
- ci.addChunk(name, (long(range[0]), long(range[1])))
- fh.close()
- return ci
-
- def remove(self):
- fs_name = fs_encode("%s.chunks" % self.name)
- if exists(fs_name): remove(fs_name)
-
- def getCount(self):
- return len(self.chunks)
-
- def getChunkName(self, index):
- return self.chunks[index][0]
-
- def getChunkRange(self, index):
- return self.chunks[index][1]
-
-
-class HTTPChunk(HTTPRequest):
- def __init__(self, id, parent, range=None, resume=False):
- self.id = id
- self.p = parent # HTTPDownload instance
- self.range = range # tuple (start, end)
- self.resume = resume
- self.log = parent.log
-
- self.size = range[1] - range[0] if range else -1
- self.arrived = 0
- self.lastURL = self.p.referer
-
- self.c = pycurl.Curl()
-
- self.header = ""
- self.headerParsed = False #indicates if the header has been processed
-
- self.fp = None #file handle
-
- self.initHandle()
- self.setInterface(self.p.options)
-
- self.BOMChecked = False # check and remove byte order mark
-
- self.rep = None
-
- self.sleep = 0.000
- self.lastSize = 0
-
- def __repr__(self):
- return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived)
-
- @property
- def cj(self):
- return self.p.cj
-
- def getHandle(self):
- """ returns a Curl handle ready to use for perform/multiperform """
-
- self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj)
- self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody)
- self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
-
- # request all bytes, since some servers in russia seems to have a defect arihmetic unit
-
- fs_name = fs_encode(self.p.info.getChunkName(self.id))
- if self.resume:
- self.fp = open(fs_name, "ab")
- self.arrived = self.fp.tell()
- if not self.arrived:
- self.arrived = stat(fs_name).st_size
-
- if self.range:
- #do nothing if chunk already finished
- if self.arrived + self.range[0] >= self.range[1]: return None
-
- if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything
- range = "%i-" % (self.arrived + self.range[0])
- else:
- range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1))
-
- self.log.debug("Chunked resume with range %s" % range)
- self.c.setopt(pycurl.RANGE, range)
- else:
- self.log.debug("Resume File from %i" % self.arrived)
- self.c.setopt(pycurl.RESUME_FROM, self.arrived)
-
- else:
- if self.range:
- if self.id == len(self.p.info.chunks) - 1: # see above
- range = "%i-" % self.range[0]
- else:
- range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1))
-
- self.log.debug("Chunked with range %s" % range)
- self.c.setopt(pycurl.RANGE, range)
-
- self.fp = open(fs_name, "wb")
-
- return self.c
-
- def writeHeader(self, buf):
- self.header += buf
- #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
- # as first chunk, we will parse the headers
- if not self.range and self.header.endswith("\r\n\r\n"):
- self.parseHeader()
- elif not self.range and buf.startswith("150") and "data connection" in buf: #ftp file size parsing
- size = search(r"(\d+) bytes", buf)
- if size:
- self.p.size = int(size.group(1))
- self.p.chunkSupport = True
-
- self.headerParsed = True
-
- def writeBody(self, buf):
- #ignore BOM, it confuses unrar
- if not self.BOMChecked:
- if [ord(b) for b in buf[:3]] == [239, 187, 191]:
- buf = buf[3:]
- self.BOMChecked = True
-
- size = len(buf)
-
- self.arrived += size
-
- self.fp.write(buf)
-
- if self.p.bucket:
- sleep(self.p.bucket.consumed(size))
- else:
- # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller
- # otherwise reduce sleep time percentual (values are based on tests)
- # So in general cpu time is saved without reducing bandwith too much
-
- if size < self.lastSize:
- self.sleep += 0.002
- else:
- self.sleep *= 0.7
-
- self.lastSize = size
-
- sleep(self.sleep)
-
- if self.range and self.arrived > self.size:
- return 0 #close if we have enough data
-
-
- def parseHeader(self):
- """parse data from recieved header"""
- for orgline in self.decodeResponse(self.header).splitlines():
- line = orgline.strip().lower()
- if line.startswith("accept-ranges") and "bytes" in line:
- self.p.chunkSupport = True
-
- if line.startswith("content-disposition") and "filename=" in line:
- name = orgline.partition("filename=")[2]
- name = name.replace('"', "").replace("'", "").replace(";", "").strip()
- self.p.nameDisposition = name
- self.log.debug("Content-Disposition: %s" % name)
-
- if not self.resume and line.startswith("content-length"):
- self.p.size = int(line.split(":")[1])
-
- self.headerParsed = True
-
- def stop(self):
- """The download will not proceed after next call of writeBody"""
- self.range = [0,0]
- self.size = 0
-
- def resetRange(self):
- """ Reset the range, so the download will load all data available """
- self.range = None
-
- def setRange(self, range):
- self.range = range
- self.size = range[1] - range[0]
-
- def flushFile(self):
- """ flush and close file """
- self.fp.flush()
- fsync(self.fp.fileno()) #make sure everything was written to disk
- self.fp.close() #needs to be closed, or merging chunks will fail
-
- def close(self):
- """ closes everything, unusable after this """
- if self.fp: self.fp.close()
- self.c.close()
- if hasattr(self, "p"): del self.p \ No newline at end of file
diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py
deleted file mode 100644
index fe8075539..000000000
--- a/module/network/HTTPDownload.py
+++ /dev/null
@@ -1,340 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from os import remove, fsync
-from os.path import dirname
-from time import sleep, time
-from shutil import move
-from logging import getLogger
-
-import pycurl
-
-from HTTPChunk import ChunkInfo, HTTPChunk
-from HTTPRequest import BadHeader
-
-from module.plugins.Plugin import Abort
-from module.utils import save_join, fs_encode
-
-class HTTPDownload():
- """ loads a url http + ftp """
-
- def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
- options={}, progressNotify=None, disposition=False):
- self.url = url
- self.filename = filename #complete file destination, not only name
- self.get = get
- self.post = post
- self.referer = referer
- self.cj = cj #cookiejar if cookies are needed
- self.bucket = bucket
- self.options = options
- self.disposition = disposition
- # all arguments
-
- self.abort = False
- self.size = 0
- self.nameDisposition = None #will be parsed from content disposition
-
- self.chunks = []
-
- self.log = getLogger("log")
-
- try:
- self.info = ChunkInfo.load(filename)
- self.info.resume = True #resume is only possible with valid info file
- self.size = self.info.size
- self.infoSaved = True
- except IOError:
- self.info = ChunkInfo(filename)
-
- self.chunkSupport = None
- self.m = pycurl.CurlMulti()
-
- #needed for speed calculation
- self.lastArrived = []
- self.speeds = []
- self.lastSpeeds = [0, 0]
-
- self.progressNotify = progressNotify
-
- @property
- def speed(self):
- last = [sum(x) for x in self.lastSpeeds if x]
- return (sum(self.speeds) + sum(last)) / (1 + len(last))
-
- @property
- def arrived(self):
- return sum([c.arrived for c in self.chunks])
-
- @property
- def percent(self):
- if not self.size: return 0
- return (self.arrived * 100) / self.size
-
- def _copyChunks(self):
- init = fs_encode(self.info.getChunkName(0)) #initial chunk name
-
- if self.info.getCount() > 1:
- fo = open(init, "rb+") #first chunkfile
- for i in range(1, self.info.getCount()):
- #input file
- fo.seek(
- self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks
- fname = fs_encode("%s.chunk%d" % (self.filename, i))
- fi = open(fname, "rb")
- buf = 32 * 1024
- while True: #copy in chunks, consumes less memory
- data = fi.read(buf)
- if not data:
- break
- fo.write(data)
- fi.close()
- if fo.tell() < self.info.getChunkRange(i)[1]:
- fo.close()
- remove(init)
- self.info.remove() #there are probably invalid chunks
- raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.")
- remove(fname) #remove chunk
- fo.close()
-
- if self.nameDisposition and self.disposition:
- self.filename = save_join(dirname(self.filename), self.nameDisposition)
-
- move(init, fs_encode(self.filename))
- self.info.remove() #remove info file
-
- def download(self, chunks=1, resume=False):
- """ returns new filename or None """
-
- chunks = max(1, chunks)
- resume = self.info.resume and resume
-
- try:
- self._download(chunks, resume)
- except pycurl.error, e:
- #code 33 - no resume
- code = e.args[0]
- if code == 33:
- # try again without resume
- self.log.debug("Errno 33 -> Restart without resume")
-
- #remove old handles
- for chunk in self.chunks:
- self.closeChunk(chunk)
-
- return self._download(chunks, False)
- else:
- raise
- finally:
- self.close()
-
- if self.nameDisposition and self.disposition: return self.nameDisposition
- return None
-
- def _download(self, chunks, resume):
- if not resume:
- self.info.clear()
- self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #create an initial entry
-
- self.chunks = []
-
- init = HTTPChunk(0, self, None, resume) #initial chunk that will load complete file (if needed)
-
- self.chunks.append(init)
- self.m.add_handle(init.getHandle())
-
- lastFinishCheck = 0
- lastTimeCheck = 0
- chunksDone = set() # list of curl handles that are finished
- chunksCreated = False
- done = False
- if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can
- self.chunkSupport = True
-
- while 1:
- #need to create chunks
- if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk
-
- if not resume:
- self.info.setSize(self.size)
- self.info.createChunks(chunks)
- self.info.save()
-
- chunks = self.info.getCount()
-
- init.setRange(self.info.getChunkRange(0))
-
- for i in range(1, chunks):
- c = HTTPChunk(i, self, self.info.getChunkRange(i), resume)
-
- handle = c.getHandle()
- if handle:
- self.chunks.append(c)
- self.m.add_handle(handle)
- else:
- #close immediatly
- self.log.debug("Invalid curl handle -> closed")
- c.close()
-
- chunksCreated = True
-
- while 1:
- ret, num_handles = self.m.perform()
- if ret != pycurl.E_CALL_MULTI_PERFORM:
- break
-
- t = time()
-
- # reduce these calls
- while lastFinishCheck + 0.5 < t:
- # list of failed curl handles
- failed = []
- ex = None # save only last exception, we can only raise one anyway
-
- num_q, ok_list, err_list = self.m.info_read()
- for c in ok_list:
- chunk = self.findChunk(c)
- try: # check if the header implies success, else add it to failed list
- chunk.verifyHeader()
- except BadHeader, e:
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
- failed.append(chunk)
- ex = e
- else:
- chunksDone.add(c)
-
- for c in err_list:
- curl, errno, msg = c
- chunk = self.findChunk(curl)
- #test if chunk was finished
- if errno != 23 or "0 !=" not in msg:
- failed.append(chunk)
- ex = pycurl.error(errno, msg)
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex)))
- continue
-
- try: # check if the header implies success, else add it to failed list
- chunk.verifyHeader()
- except BadHeader, e:
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
- failed.append(chunk)
- ex = e
- else:
- chunksDone.add(curl)
- if not num_q: # no more infos to get
-
- # check if init is not finished so we reset download connections
- # note that other chunks are closed and downloaded with init too
- if failed and init not in failed and init.c not in chunksDone:
- self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex))))
-
- #list of chunks to clean and remove
- to_clean = filter(lambda x: x is not init, self.chunks)
- for chunk in to_clean:
- self.closeChunk(chunk)
- self.chunks.remove(chunk)
- remove(fs_encode(self.info.getChunkName(chunk.id)))
-
- #let first chunk load the rest and update the info file
- init.resetRange()
- self.info.clear()
- self.info.addChunk("%s.chunk0" % self.filename, (0, self.size))
- self.info.save()
- elif failed:
- raise ex
-
- lastFinishCheck = t
-
- if len(chunksDone) >= len(self.chunks):
- if len(chunksDone) > len(self.chunks):
- self.log.warning("Finished download chunks size incorrect, please report bug.")
- done = True #all chunks loaded
-
- break
-
- if done:
- break #all chunks loaded
-
- # calc speed once per second, averaging over 3 seconds
- if lastTimeCheck + 1 < t:
- diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in
- enumerate(self.chunks)]
-
- self.lastSpeeds[1] = self.lastSpeeds[0]
- self.lastSpeeds[0] = self.speeds
- self.speeds = [float(a) / (t - lastTimeCheck) for a in diff]
- self.lastArrived = [c.arrived for c in self.chunks]
- lastTimeCheck = t
- self.updateProgress()
-
- if self.abort:
- raise Abort()
-
- #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize
- self.m.select(1)
-
- for chunk in self.chunks:
- chunk.flushFile() #make sure downloads are written to disk
-
- self._copyChunks()
-
- def updateProgress(self):
- if self.progressNotify:
- self.progressNotify(self.percent)
-
- def findChunk(self, handle):
- """ linear search to find a chunk (should be ok since chunk size is usually low) """
- for chunk in self.chunks:
- if chunk.c == handle: return chunk
-
- def closeChunk(self, chunk):
- try:
- self.m.remove_handle(chunk.c)
- except pycurl.error, e:
- self.log.debug("Error removing chunk: %s" % str(e))
- finally:
- chunk.close()
-
- def close(self):
- """ cleanup """
- for chunk in self.chunks:
- self.closeChunk(chunk)
-
- self.chunks = []
- if hasattr(self, "m"):
- self.m.close()
- del self.m
- if hasattr(self, "cj"):
- del self.cj
- if hasattr(self, "info"):
- del self.info
-
-if __name__ == "__main__":
- url = "http://speedtest.netcologne.de/test_100mb.bin"
-
- from Bucket import Bucket
-
- bucket = Bucket()
- bucket.setRate(200 * 1024)
- bucket = None
-
- print "starting"
-
- dwnld = HTTPDownload(url, "test_100mb.bin", bucket=bucket)
- dwnld.download(chunks=3, resume=True)
diff --git a/module/network/HTTPRequest.py b/module/network/HTTPRequest.py
deleted file mode 100644
index 4747d937f..000000000
--- a/module/network/HTTPRequest.py
+++ /dev/null
@@ -1,306 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-import pycurl
-
-from codecs import getincrementaldecoder, lookup, BOM_UTF8
-from urllib import quote, urlencode
-from httplib import responses
-from logging import getLogger
-from cStringIO import StringIO
-
-from module.plugins.Plugin import Abort
-
-def myquote(url):
- return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
-
-def myurlencode(data):
- data = dict(data)
- return urlencode(dict((x.encode('utf_8') if isinstance(x, unicode) else x, \
- y.encode('utf_8') if isinstance(y, unicode) else y ) for x, y in data.iteritems()))
-
-bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
-
-class BadHeader(Exception):
- def __init__(self, code, content=""):
- Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)]))
- self.code = code
- self.content = content
-
-
-class HTTPRequest():
- def __init__(self, cookies=None, options=None):
- self.c = pycurl.Curl()
- self.rep = StringIO()
-
- self.cj = cookies #cookiejar
-
- self.lastURL = None
- self.lastEffectiveURL = None
- self.abort = False
- self.code = 0 # last http code
-
- self.header = ""
-
- self.headers = [] #temporary request header
-
- self.initHandle()
- self.setInterface(options)
-
- self.c.setopt(pycurl.WRITEFUNCTION, self.write)
- self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
-
- self.log = getLogger("log")
-
-
- def initHandle(self):
- """ sets common options to curl handle """
- self.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.c.setopt(pycurl.MAXREDIRS, 5)
- self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
- self.c.setopt(pycurl.NOSIGNAL, 1)
- self.c.setopt(pycurl.NOPROGRESS, 1)
- if hasattr(pycurl, "AUTOREFERER"):
- self.c.setopt(pycurl.AUTOREFERER, 1)
- self.c.setopt(pycurl.SSL_VERIFYPEER, 0)
- self.c.setopt(pycurl.LOW_SPEED_TIME, 30)
- self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
-
- #self.c.setopt(pycurl.VERBOSE, 1)
-
- self.c.setopt(pycurl.USERAGENT,
- "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0")
- if pycurl.version_info()[7]:
- self.c.setopt(pycurl.ENCODING, "gzip, deflate")
- self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*",
- "Accept-Language: en-US,en",
- "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
- "Connection: keep-alive",
- "Keep-Alive: 300",
- "Expect:"])
-
- def setInterface(self, options):
-
- interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"]
-
- if interface and interface.lower() != "none":
- self.c.setopt(pycurl.INTERFACE, str(interface))
-
- if proxy:
- if proxy["type"] == "socks4":
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
- elif proxy["type"] == "socks5":
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
- else:
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
-
- self.c.setopt(pycurl.PROXY, str(proxy["address"]))
- self.c.setopt(pycurl.PROXYPORT, proxy["port"])
-
- if proxy["username"]:
- self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"])))
-
- if ipv6:
- self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)
- else:
- self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
-
- if "auth" in options:
- self.c.setopt(pycurl.USERPWD, str(options["auth"]))
-
- if "timeout" in options:
- self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"])
-
-
- def addCookies(self):
- """ put cookies from curl handle to cj """
- if self.cj:
- self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST))
-
- def getCookies(self):
- """ add cookies from cj to curl handle """
- if self.cj:
- for c in self.cj.getCookies():
- self.c.setopt(pycurl.COOKIELIST, c)
- return
-
- def clearCookies(self):
- self.c.setopt(pycurl.COOKIELIST, "")
-
- def setRequestContext(self, url, get, post, referer, cookies, multipart=False):
- """ sets everything needed for the request """
-
- url = myquote(url)
-
- if get:
- get = urlencode(get)
- url = "%s?%s" % (url, get)
-
- self.c.setopt(pycurl.URL, url)
- self.c.lastUrl = url
-
- if post:
- self.c.setopt(pycurl.POST, 1)
- if not multipart:
- if type(post) == unicode:
- post = str(post) #unicode not allowed
- elif type(post) == str:
- pass
- else:
- post = myurlencode(post)
-
- self.c.setopt(pycurl.POSTFIELDS, post)
- else:
- post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()]
- self.c.setopt(pycurl.HTTPPOST, post)
- else:
- self.c.setopt(pycurl.POST, 0)
-
- if referer and self.lastURL:
- self.c.setopt(pycurl.REFERER, str(self.lastURL))
-
- if cookies:
- self.c.setopt(pycurl.COOKIEFILE, "")
- self.c.setopt(pycurl.COOKIEJAR, "")
- self.getCookies()
-
-
- def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False):
- """ load and returns a given page """
-
- self.setRequestContext(url, get, post, referer, cookies, multipart)
-
- self.header = ""
-
- self.c.setopt(pycurl.HTTPHEADER, self.headers)
-
- if just_header:
- self.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.c.setopt(pycurl.NOBODY, 1)
- self.c.perform()
- rep = self.header
-
- self.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.c.setopt(pycurl.NOBODY, 0)
-
- else:
- self.c.perform()
- rep = self.getResponse()
-
- self.c.setopt(pycurl.POSTFIELDS, "")
- self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL)
- self.code = self.verifyHeader()
-
- self.addCookies()
-
- if decode:
- rep = self.decodeResponse(rep)
-
- return rep
-
- def verifyHeader(self):
- """ raise an exceptions on bad headers """
- code = int(self.c.getinfo(pycurl.RESPONSE_CODE))
- if code in bad_headers:
- #404 will NOT raise an exception
- raise BadHeader(code, self.getResponse())
- return code
-
- def checkHeader(self):
- """ check if header indicates failure"""
- return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers
-
- def getResponse(self):
- """ retrieve response from string io """
- if self.rep is None: return ""
- value = self.rep.getvalue()
- self.rep.close()
- self.rep = StringIO()
- return value
-
- def decodeResponse(self, rep):
- """ decode with correct encoding, relies on header """
- header = self.header.splitlines()
- encoding = "utf8" # default encoding
-
- for line in header:
- line = line.lower().replace(" ", "")
- if not line.startswith("content-type:") or\
- ("text" not in line and "application" not in line):
- continue
-
- none, delemiter, charset = line.rpartition("charset=")
- if delemiter:
- charset = charset.split(";")
- if charset:
- encoding = charset[0]
-
- try:
- #self.log.debug("Decoded %s" % encoding )
- if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8):
- encoding = 'utf-8-sig'
-
- decoder = getincrementaldecoder(encoding)("replace")
- rep = decoder.decode(rep, True)
-
- #TODO: html_unescape as default
-
- except LookupError:
- self.log.debug("No Decoder foung for %s" % encoding)
- except Exception:
- self.log.debug("Error when decoding string from %s." % encoding)
-
- return rep
-
- def write(self, buf):
- """ writes response """
- if self.rep.tell() > 1000000 or self.abort:
- rep = self.getResponse()
- if self.abort: raise Abort()
- f = open("response.dump", "wb")
- f.write(rep)
- f.close()
- raise Exception("Loaded Url exceeded limit")
-
- self.rep.write(buf)
-
- def writeHeader(self, buf):
- """ writes header """
- self.header += buf
-
- def putHeader(self, name, value):
- self.headers.append("%s: %s" % (name, value))
-
- def clearHeaders(self):
- self.headers = []
-
- def close(self):
- """ cleanup, unusable after this """
- self.rep.close()
- if hasattr(self, "cj"):
- del self.cj
- if hasattr(self, "c"):
- self.c.close()
- del self.c
-
-if __name__ == "__main__":
- url = "http://pyload.org"
- c = HTTPRequest()
- print c.load(url)
-
diff --git a/module/network/RequestFactory.py b/module/network/RequestFactory.py
deleted file mode 100644
index 5b1528281..000000000
--- a/module/network/RequestFactory.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from threading import Lock
-
-from Browser import Browser
-from Bucket import Bucket
-from HTTPRequest import HTTPRequest
-from CookieJar import CookieJar
-
-from XDCCRequest import XDCCRequest
-
-class RequestFactory():
- def __init__(self, core):
- self.lock = Lock()
- self.core = core
- self.bucket = Bucket()
- self.updateBucket()
- self.cookiejars = {}
-
- def iface(self):
- return self.core.config["download"]["interface"]
-
- def getRequest(self, pluginName, account=None, type="HTTP"):
- self.lock.acquire()
-
- if type == "XDCC":
- return XDCCRequest(proxies=self.getProxies())
-
- req = Browser(self.bucket, self.getOptions())
-
- if account:
- cj = self.getCookieJar(pluginName, account)
- req.setCookieJar(cj)
- else:
- req.setCookieJar(CookieJar(pluginName))
-
- self.lock.release()
- return req
-
- def getHTTPRequest(self, **kwargs):
- """ returns a http request, dont forget to close it ! """
- options = self.getOptions()
- options.update(kwargs) # submit kwargs as additional options
- return HTTPRequest(CookieJar(None), options)
-
- def getURL(self, *args, **kwargs):
- """ see HTTPRequest for argument list """
- h = HTTPRequest(None, self.getOptions())
- try:
- rep = h.load(*args, **kwargs)
- finally:
- h.close()
-
- return rep
-
- def getCookieJar(self, pluginName, account=None):
- if (pluginName, account) in self.cookiejars:
- return self.cookiejars[(pluginName, account)]
-
- cj = CookieJar(pluginName, account)
- self.cookiejars[(pluginName, account)] = cj
- return cj
-
- def getProxies(self):
- """ returns a proxy list for the request classes """
- if not self.core.config["proxy"]["proxy"]:
- return {}
- else:
- type = "http"
- setting = self.core.config["proxy"]["type"].lower()
- if setting == "socks4": type = "socks4"
- elif setting == "socks5": type = "socks5"
-
- username = None
- if self.core.config["proxy"]["username"] and self.core.config["proxy"]["username"].lower() != "none":
- username = self.core.config["proxy"]["username"]
-
- pw = None
- if self.core.config["proxy"]["password"] and self.core.config["proxy"]["password"].lower() != "none":
- pw = self.core.config["proxy"]["password"]
-
- return {
- "type": type,
- "address": self.core.config["proxy"]["address"],
- "port": self.core.config["proxy"]["port"],
- "username": username,
- "password": pw,
- }
-
- def getOptions(self):
- """returns options needed for pycurl"""
- return {"interface": self.iface(),
- "proxies": self.getProxies(),
- "ipv6": self.core.config["download"]["ipv6"]}
-
- def updateBucket(self):
- """ set values in the bucket according to settings"""
- if not self.core.config["download"]["limit_speed"]:
- self.bucket.setRate(-1)
- else:
- self.bucket.setRate(self.core.config["download"]["max_speed"] * 1024)
-
-# needs pyreq in global namespace
-def getURL(*args, **kwargs):
- return pyreq.getURL(*args, **kwargs)
-
-
-def getRequest(*args, **kwargs):
- return pyreq.getHTTPRequest()
diff --git a/module/network/XDCCRequest.py b/module/network/XDCCRequest.py
deleted file mode 100644
index f03798c17..000000000
--- a/module/network/XDCCRequest.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: jeix
-"""
-
-import socket
-import re
-
-from os import remove
-from os.path import exists
-
-from time import time
-
-import struct
-from select import select
-
-from module.plugins.Plugin import Abort
-
-
-class XDCCRequest():
- def __init__(self, timeout=30, proxies={}):
-
- self.proxies = proxies
- self.timeout = timeout
-
- self.filesize = 0
- self.recv = 0
- self.speed = 0
-
- self.abort = False
-
-
- def createSocket(self):
- # proxytype = None
- # proxy = None
- # if self.proxies.has_key("socks5"):
- # proxytype = socks.PROXY_TYPE_SOCKS5
- # proxy = self.proxies["socks5"]
- # elif self.proxies.has_key("socks4"):
- # proxytype = socks.PROXY_TYPE_SOCKS4
- # proxy = self.proxies["socks4"]
- # if proxytype:
- # sock = socks.socksocket()
- # t = _parse_proxy(proxy)
- # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2])
- # else:
- # sock = socket.socket()
- # return sock
-
- return socket.socket()
-
- def download(self, ip, port, filename, irc, progressNotify=None):
-
- ircbuffer = ""
- lastUpdate = time()
- cumRecvLen = 0
-
- dccsock = self.createSocket()
-
- dccsock.settimeout(self.timeout)
- dccsock.connect((ip, port))
-
- if exists(filename):
- i = 0
- nameParts = filename.rpartition(".")
- while True:
- newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2])
- i += 1
-
- if not exists(newfilename):
- filename = newfilename
- break
-
- fh = open(filename, "wb")
-
- # recv loop for dcc socket
- while True:
- if self.abort:
- dccsock.close()
- fh.close()
- remove(filename)
- raise Abort()
-
- self._keepAlive(irc, ircbuffer)
-
- data = dccsock.recv(4096)
- dataLen = len(data)
- self.recv += dataLen
-
- cumRecvLen += dataLen
-
- now = time()
- timespan = now - lastUpdate
- if timespan > 1:
- self.speed = cumRecvLen / timespan
- cumRecvLen = 0
- lastUpdate = now
-
- if progressNotify:
- progressNotify(self.percent)
-
-
- if not data:
- break
-
- fh.write(data)
-
- # acknowledge data by sending number of recceived bytes
- dccsock.send(struct.pack('!I', self.recv))
-
- dccsock.close()
- fh.close()
-
- return filename
-
- def _keepAlive(self, sock, readbuffer):
- fdset = select([sock], [], [], 0)
- if sock not in fdset[0]:
- return
-
- readbuffer += sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- line = line.rstrip()
- first = line.split()
- if first[0] == "PING":
- sock.send("PONG %s\r\n" % first[1])
-
- def abortDownloads(self):
- self.abort = True
-
- @property
- def size(self):
- return self.filesize
-
- @property
- def arrived(self):
- return self.recv
-
- @property
- def percent(self):
- if not self.filesize: return 0
- return (self.recv * 100) / self.filesize
-
- def close(self):
- pass
diff --git a/module/network/__init__.py b/module/network/__init__.py
deleted file mode 100644
index 8b1378917..000000000
--- a/module/network/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/module/plugins/Account.py b/module/plugins/Account.py
deleted file mode 100644
index c147404e0..000000000
--- a/module/plugins/Account.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from random import choice
-from time import time
-from traceback import print_exc
-from threading import RLock
-
-from Plugin import Base
-from module.utils import compare_time, parseFileSize, lock
-
-class WrongPassword(Exception):
- pass
-
-
-class Account(Base):
- """
- Base class for every Account plugin.
- Just overwrite `login` and cookies will be stored and account becomes accessible in\
- associated hoster plugin. Plugin should also provide `loadAccountInfo`
- """
- __name__ = "Account"
- __version__ = "0.2"
- __type__ = "account"
- __description__ = """Account Plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
-
- #: after that time [in minutes] pyload will relogin the account
- login_timeout = 600
- #: account data will be reloaded after this time
- info_threshold = 600
-
-
- def __init__(self, manager, accounts):
- Base.__init__(self, manager.core)
-
- self.manager = manager
- self.accounts = {}
- self.infos = {} # cache for account information
- self.lock = RLock()
-
- self.timestamps = {}
- self.setAccounts(accounts)
- self.init()
-
- def init(self):
- pass
-
- def login(self, user, data, req):
- """login into account, the cookies will be saved so user can be recognized
-
- :param user: loginname
- :param data: data dictionary
- :param req: `Request` instance
- """
- pass
-
- @lock
- def _login(self, user, data):
- # set timestamp for login
- self.timestamps[user] = time()
-
- req = self.getAccountRequest(user)
- try:
- self.login(user, data, req)
- except WrongPassword:
- self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": _("Wrong Password")})
- data["valid"] = False
-
- except Exception, e:
- self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": e})
- data["valid"] = False
- if self.core.debug:
- print_exc()
- finally:
- if req: req.close()
-
- def relogin(self, user):
- req = self.getAccountRequest(user)
- if req:
- req.cj.clear()
- req.close()
- if user in self.infos:
- del self.infos[user] #delete old information
-
- self._login(user, self.accounts[user])
-
- def setAccounts(self, accounts):
- self.accounts = accounts
- for user, data in self.accounts.iteritems():
- self._login(user, data)
- self.infos[user] = {}
-
- def updateAccounts(self, user, password=None, options={}):
- """ updates account and return true if anything changed """
-
- if user in self.accounts:
- self.accounts[user]["valid"] = True #do not remove or accounts will not login
- if password:
- self.accounts[user]["password"] = password
- self.relogin(user)
- return True
- if options:
- before = self.accounts[user]["options"]
- self.accounts[user]["options"].update(options)
- return self.accounts[user]["options"] != before
- else:
- self.accounts[user] = {"password": password, "options": options, "valid": True}
- self._login(user, self.accounts[user])
- return True
-
- def removeAccount(self, user):
- if user in self.accounts:
- del self.accounts[user]
- if user in self.infos:
- del self.infos[user]
- if user in self.timestamps:
- del self.timestamps[user]
-
- @lock
- def getAccountInfo(self, name, force=False):
- """retrieve account infos for an user, do **not** overwrite this method!\\
- just use it to retrieve infos in hoster plugins. see `loadAccountInfo`
-
- :param name: username
- :param force: reloads cached account information
- :return: dictionary with information
- """
- data = Account.loadAccountInfo(self, name)
-
- if force or name not in self.infos:
- self.logDebug("Get Account Info for %s" % name)
- req = self.getAccountRequest(name)
-
- try:
- infos = self.loadAccountInfo(name, req)
- if not type(infos) == dict:
- raise Exception("Wrong return format")
- except Exception, e:
- infos = {"error": str(e)}
-
- if req: req.close()
-
- self.logDebug("Account Info: %s" % str(infos))
-
- infos["timestamp"] = time()
- self.infos[name] = infos
- elif "timestamp" in self.infos[name] and self.infos[name][
- "timestamp"] + self.info_threshold * 60 < time():
- self.logDebug("Reached timeout for account data")
- self.scheduleRefresh(name)
-
- data.update(self.infos[name])
- return data
-
- def isPremium(self, user):
- info = self.getAccountInfo(user)
- return info["premium"]
-
- def loadAccountInfo(self, name, req=None):
- """this should be overwritten in account plugin,\
- and retrieving account information for user
-
- :param name:
- :param req: `Request` instance
- :return:
- """
- return {
- "validuntil": None, # -1 for unlimited
- "login": name,
- #"password": self.accounts[name]["password"], #@XXX: security
- "options": self.accounts[name]["options"],
- "valid": self.accounts[name]["valid"],
- "trafficleft": None, # in kb, -1 for unlimited
- "maxtraffic": None,
- "premium": True, #useful for free accounts
- "timestamp": 0, #time this info was retrieved
- "type": self.__name__,
- }
-
- def getAllAccounts(self, force=False):
- return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()]
-
- def getAccountRequest(self, user=None):
- if not user:
- user, data = self.selectAccount()
- if not user:
- return None
-
- req = self.core.requestFactory.getRequest(self.__name__, user)
- return req
-
- def getAccountCookies(self, user=None):
- if not user:
- user, data = self.selectAccount()
- if not user:
- return None
-
- cj = self.core.requestFactory.getCookieJar(self.__name__, user)
- return cj
-
- def getAccountData(self, user):
- return self.accounts[user]
-
- def selectAccount(self):
- """ returns an valid account name and data"""
- usable = []
- for user, data in self.accounts.iteritems():
- if not data["valid"]: continue
-
- if "time" in data["options"] and data["options"]["time"]:
- time_data = ""
- try:
- time_data = data["options"]["time"][0]
- start, end = time_data.split("-")
- if not compare_time(start.split(":"), end.split(":")):
- continue
- except:
- self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data)
-
- if user in self.infos:
- if "validuntil" in self.infos[user]:
- if self.infos[user]["validuntil"] > 0 and time() > self.infos[user]["validuntil"]:
- continue
- if "trafficleft" in self.infos[user]:
- if self.infos[user]["trafficleft"] == 0:
- continue
-
- usable.append((user, data))
-
- if not usable: return None, None
- return choice(usable)
-
- def canUse(self):
- return False if self.selectAccount() == (None, None) else True
-
- def parseTraffic(self, string): #returns kbyte
- return parseFileSize(string) / 1024
-
- def wrongPassword(self):
- raise WrongPassword
-
- def empty(self, user):
- if user in self.infos:
- self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user)
-
- self.infos[user].update({"trafficleft": 0})
- self.scheduleRefresh(user, 30 * 60)
-
- def expired(self, user):
- if user in self.infos:
- self.logWarning(_("Account %s is expired, checking again in 1h") % user)
-
- self.infos[user].update({"validuntil": time() - 1})
- self.scheduleRefresh(user, 60 * 60)
-
- def scheduleRefresh(self, user, time=0, force=True):
- """ add task to refresh account info to sheduler """
- self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time))
- self.core.scheduler.addJob(time, self.getAccountInfo, [user, force])
-
- @lock
- def checkLogin(self, user):
- """ checks if user is still logged in """
- if user in self.timestamps:
- if self.timestamps[user] + self.login_timeout * 60 < time():
- self.logDebug("Reached login timeout for %s" % user)
- self.relogin(user)
- return False
-
- return True
diff --git a/module/plugins/AccountManager.py b/module/plugins/AccountManager.py
deleted file mode 100644
index fc521d36c..000000000
--- a/module/plugins/AccountManager.py
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from os.path import exists
-from shutil import copy
-
-from threading import Lock
-
-from module.PullEvents import AccountUpdateEvent
-from module.utils import chmod, lock
-
-ACC_VERSION = 1
-
-class AccountManager():
- """manages all accounts"""
-
- #----------------------------------------------------------------------
- def __init__(self, core):
- """Constructor"""
-
- self.core = core
- self.lock = Lock()
-
- self.initPlugins()
- self.saveAccounts() # save to add categories to conf
-
- def initPlugins(self):
- self.accounts = {} # key = ( plugin )
- self.plugins = {}
-
- self.initAccountPlugins()
- self.loadAccounts()
-
-
- def getAccountPlugin(self, plugin):
- """get account instance for plugin or None if anonymous"""
- if plugin in self.accounts:
- if plugin not in self.plugins:
- self.plugins[plugin] = self.core.pluginManager.loadClass("accounts", plugin)(self, self.accounts[plugin])
-
- return self.plugins[plugin]
- else:
- return None
-
- def getAccountPlugins(self):
- """ get all account instances"""
-
- plugins = []
- for plugin in self.accounts.keys():
- plugins.append(self.getAccountPlugin(plugin))
-
- return plugins
- #----------------------------------------------------------------------
- def loadAccounts(self):
- """loads all accounts available"""
-
- if not exists("accounts.conf"):
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION))
- f.close()
-
- f = open("accounts.conf", "rb")
- content = f.readlines()
- version = content[0].split(":")[1].strip() if content else ""
- f.close()
-
- if not version or int(version) < ACC_VERSION:
- copy("accounts.conf", "accounts.backup")
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION))
- f.close()
- self.core.log.warning(_("Account settings deleted, due to new config format."))
- return
-
-
-
- plugin = ""
- name = ""
-
- for line in content[1:]:
- line = line.strip()
-
- if not line: continue
- if line.startswith("#"): continue
- if line.startswith("version"): continue
-
- if line.endswith(":") and line.count(":") == 1:
- plugin = line[:-1]
- self.accounts[plugin] = {}
-
- elif line.startswith("@"):
- try:
- option = line[1:].split()
- self.accounts[plugin][name]["options"][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:])
- except:
- pass
-
- elif ":" in line:
- name, sep, pw = line.partition(":")
- self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True}
- #----------------------------------------------------------------------
- def saveAccounts(self):
- """save all account information"""
-
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION) + "\n")
-
- for plugin, accounts in self.accounts.iteritems():
- f.write("\n")
- f.write(plugin+":\n")
-
- for name,data in accounts.iteritems():
- f.write("\n\t%s:%s\n" % (name,data["password"]) )
- if data["options"]:
- for option, values in data["options"].iteritems():
- f.write("\t@%s %s\n" % (option, " ".join(values)))
-
- f.close()
- chmod(f.name, 0600)
-
-
- #----------------------------------------------------------------------
- def initAccountPlugins(self):
- """init names"""
- for name in self.core.pluginManager.getAccountPlugins():
- self.accounts[name] = {}
-
- @lock
- def updateAccount(self, plugin , user, password=None, options={}):
- """add or update account"""
- if plugin in self.accounts:
- p = self.getAccountPlugin(plugin)
- updated = p.updateAccounts(user, password, options)
- #since accounts is a ref in plugin self.accounts doesnt need to be updated here
-
- self.saveAccounts()
- if updated: p.scheduleRefresh(user, force=False)
-
- @lock
- def removeAccount(self, plugin, user):
- """remove account"""
-
- if plugin in self.accounts:
- p = self.getAccountPlugin(plugin)
- p.removeAccount(user)
-
- self.saveAccounts()
-
- @lock
- def getAccountInfos(self, force=True, refresh=False):
- data = {}
-
- if refresh:
- self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
- force = False
-
- for p in self.accounts.keys():
- if self.accounts[p]:
- p = self.getAccountPlugin(p)
- data[p.__name__] = p.getAllAccounts(force)
- else:
- data[p] = []
- e = AccountUpdateEvent()
- self.core.pullManager.addEvent(e)
- return data
-
- def sendChange(self):
- e = AccountUpdateEvent()
- self.core.pullManager.addEvent(e)
diff --git a/module/plugins/Container.py b/module/plugins/Container.py
deleted file mode 100644
index c233d3710..000000000
--- a/module/plugins/Container.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from module.plugins.Crypter import Crypter
-
-from os.path import join, exists, basename
-from os import remove
-import re
-
-class Container(Crypter):
- __name__ = "Container"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "container"
- __description__ = """Base container plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
-
-
- def preprocessing(self, thread):
- """prepare"""
-
- self.setup()
- self.thread = thread
-
- self.loadToDisk()
-
- self.decrypt(self.pyfile)
- self.deleteTmp()
-
- self.createPackages()
-
-
- def loadToDisk(self):
- """loads container to disk if its stored remotely and overwrite url,
- or check existent on several places at disk"""
-
- if self.pyfile.url.startswith("http"):
- self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1]
- content = self.load(self.pyfile.url)
- self.pyfile.url = join(self.config["general"]["download_folder"], self.pyfile.name)
- f = open(self.pyfile.url, "wb" )
- f.write(content)
- f.close()
-
- else:
- self.pyfile.name = basename(self.pyfile.url)
- if not exists(self.pyfile.url):
- if exists(join(pypath, self.pyfile.url)):
- self.pyfile.url = join(pypath, self.pyfile.url)
- else:
- self.fail(_("File not exists."))
-
-
- def deleteTmp(self):
- if self.pyfile.name.startswith("tmp_"):
- remove(self.pyfile.url)
-
-
diff --git a/module/plugins/Crypter.py b/module/plugins/Crypter.py
deleted file mode 100644
index d1549fe80..000000000
--- a/module/plugins/Crypter.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from module.plugins.Plugin import Plugin
-
-class Crypter(Plugin):
- __name__ = "Crypter"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "container"
- __description__ = """Base crypter plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
-
- def __init__(self, pyfile):
- Plugin.__init__(self, pyfile)
-
- #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder )
- self.packages = []
-
- #: List of urls, pyLoad will generate packagenames
- self.urls = []
-
- self.multiDL = True
- self.limitDL = 0
-
-
- def preprocessing(self, thread):
- """prepare"""
- self.setup()
- self.thread = thread
-
- self.decrypt(self.pyfile)
-
- self.createPackages()
-
-
- def decrypt(self, pyfile):
- raise NotImplementedError
-
- def createPackages(self):
- """ create new packages from self.packages """
- for pack in self.packages:
-
- self.log.debug("Parsed package %(name)s with %(len)d links" % { "name" : pack[0], "len" : len(pack[1]) } )
-
- links = [x.decode("utf-8") for x in pack[1]]
-
- pid = self.core.api.addPackage(pack[0], links, self.pyfile.package().queue)
-
- if self.pyfile.package().password:
- self.core.api.setPackageData(pid, {"password": self.pyfile.package().password})
-
- if self.urls:
- self.core.api.generateAndAddPackages(self.urls)
-
diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py
deleted file mode 100644
index 5efd08bae..000000000
--- a/module/plugins/Hook.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
- @interface-version: 0.2
-"""
-
-from traceback import print_exc
-
-from Plugin import Base
-
-class Expose(object):
- """ used for decoration to declare rpc services """
-
- def __new__(cls, f, *args, **kwargs):
- hookManager.addRPC(f.__module__, f.func_name, f.func_doc)
- return f
-
-def threaded(f):
- def run(*args,**kwargs):
- hookManager.startThread(f, *args, **kwargs)
- return run
-
-class Hook(Base):
- """
- Base class for hook plugins.
- """
- __name__ = "Hook"
- __version__ = "0.2"
- __type__ = "hook"
- __threaded__ = []
- __config__ = [ ("name", "type", "desc" , "default") ]
- __description__ = """interface for hook"""
- __author_name__ = ("mkaay", "RaNaN")
- __author_mail__ = ("mkaay@mkaay.de", "RaNaN@pyload.org")
-
- #: automatically register event listeners for functions, attribute will be deleted dont use it yourself
- event_map = None
-
- # Alternative to event_map
- #: List of events the plugin can handle, name the functions exactly like eventname.
- event_list = None # dont make duplicate entries in event_map
-
-
- #: periodic call interval in secondc
- interval = 60
-
- def __init__(self, core, manager):
- Base.__init__(self, core)
-
- #: Provide information in dict here, usable by API `getInfo`
- self.info = None
-
- #: Callback of periodical job task, used by hookmanager
- self.cb = None
-
- #: `HookManager`
- self.manager = manager
-
- #register events
- if self.event_map:
- for event, funcs in self.event_map.iteritems():
- if type(funcs) in (list, tuple):
- for f in funcs:
- self.manager.addEvent(event, getattr(self,f))
- else:
- self.manager.addEvent(event, getattr(self,funcs))
-
- #delete for various reasons
- self.event_map = None
-
- if self.event_list:
- for f in self.event_list:
- self.manager.addEvent(f, getattr(self,f))
-
- self.event_list = None
-
- self.initPeriodical()
- self.setup()
-
- def initPeriodical(self):
- if self.interval >=1:
- self.cb = self.core.scheduler.addJob(0, self._periodical, threaded=False)
-
- def _periodical(self):
- try:
- if self.isActivated(): self.periodical()
- except Exception, e:
- self.core.log.error(_("Error executing hooks: %s") % str(e))
- if self.core.debug:
- print_exc()
-
- self.cb = self.core.scheduler.addJob(self.interval, self._periodical, threaded=False)
-
-
- def __repr__(self):
- return "<Hook %s>" % self.__name__
-
- def setup(self):
- """ more init stuff if needed """
- pass
-
- def unload(self):
- """ called when hook was deactivated """
- pass
-
- def isActivated(self):
- """ checks if hook is activated"""
- return self.config.getPlugin(self.__name__, "activated")
-
-
- #event methods - overwrite these if needed
- def coreReady(self):
- pass
-
- def coreExiting(self):
- pass
-
- def downloadPreparing(self, pyfile):
- pass
-
- def downloadFinished(self, pyfile):
- pass
-
- def downloadFailed(self, pyfile):
- pass
-
- def packageFinished(self, pypack):
- pass
-
- def beforeReconnecting(self, ip):
- pass
-
- def afterReconnecting(self, ip):
- pass
-
- def periodical(self):
- pass
-
- def newCaptchaTask(self, task):
- """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """
- pass
-
- def captchaCorrect(self, task):
- pass
-
- def captchaInvalid(self, task):
- pass \ No newline at end of file
diff --git a/module/plugins/Hoster.py b/module/plugins/Hoster.py
deleted file mode 100644
index 814a70949..000000000
--- a/module/plugins/Hoster.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from module.plugins.Plugin import Plugin
-
-def getInfo(self):
- #result = [ .. (name, size, status, url) .. ]
- return
-
-class Hoster(Plugin):
- __name__ = "Hoster"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "hoster"
- __description__ = """Base hoster plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
diff --git a/module/plugins/Plugin.py b/module/plugins/Plugin.py
deleted file mode 100644
index 15bf3971f..000000000
--- a/module/plugins/Plugin.py
+++ /dev/null
@@ -1,617 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN, spoob, mkaay
-"""
-
-from time import time, sleep
-from random import randint
-
-import os
-from os import remove, makedirs, chmod, stat
-from os.path import exists, join
-
-if os.name != "nt":
- from os import chown
- from pwd import getpwnam
- from grp import getgrnam
-
-from itertools import islice
-
-from module.utils import save_join, save_path, fs_encode, fs_decode
-
-def chunks(iterable, size):
- it = iter(iterable)
- item = list(islice(it, size))
- while item:
- yield item
- item = list(islice(it, size))
-
-
-class Abort(Exception):
- """ raised when aborted """
-
-
-class Fail(Exception):
- """ raised when failed """
-
-
-class Reconnect(Exception):
- """ raised when reconnected """
-
-
-class Retry(Exception):
- """ raised when start again from beginning """
-
-
-class SkipDownload(Exception):
- """ raised when download should be skipped """
-
-
-class Base(object):
- """
- A Base class with log/config/db methods *all* plugin types can use
- """
-
- def __init__(self, core):
- #: Core instance
- self.core = core
- #: logging instance
- self.log = core.log
- #: core config
- self.config = core.config
-
- #log functions
- def logInfo(self, *args):
- self.log.info("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logWarning(self, *args):
- self.log.warning("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logError(self, *args):
- self.log.error("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logDebug(self, *args):
- self.log.debug("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
-
- def setConf(self, option, value):
- """ see `setConfig` """
- self.core.config.setPlugin(self.__name__, option, value)
-
- def setConfig(self, option, value):
- """ Set config value for current plugin
-
- :param option:
- :param value:
- :return:
- """
- self.setConf(option, value)
-
- def getConf(self, option):
- """ see `getConfig` """
- return self.core.config.getPlugin(self.__name__, option)
-
- def getConfig(self, option):
- """ Returns config value for current plugin
-
- :param option:
- :return:
- """
- return self.getConf(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)
-
-
-class Plugin(Base):
- """
- Base plugin for hoster/crypter.
- Overwrite `process` / `decrypt` in your subclassed plugin.
- """
- __name__ = "Plugin"
- __version__ = "0.4"
- __pattern__ = None
- __type__ = "hoster"
- __config__ = [("name", "type", "desc", "default")]
- __description__ = """Base Plugin"""
- __author_name__ = ("RaNaN", "spoob", "mkaay")
- __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org", "mkaay@mkaay.de")
-
- def __init__(self, pyfile):
- Base.__init__(self, pyfile.m.core)
-
- self.wantReconnect = False
- #: enables simultaneous processing of multiple downloads
- self.multiDL = True
- self.limitDL = 0
- #: chunk limit
- self.chunkLimit = 1
- self.resumeDownload = False
-
- #: time() + wait in seconds
- self.waitUntil = 0
- self.waiting = False
-
- self.ocr = None #captcha reader instance
- #: account handler instance, see :py:class:`Account`
- self.account = pyfile.m.core.accountManager.getAccountPlugin(self.__name__)
-
- #: premium status
- self.premium = False
- #: username/login
- self.user = None
-
- if self.account and not self.account.canUse(): self.account = None
- if self.account:
- self.user, data = self.account.selectAccount()
- #: Browser instance, see `network.Browser`
- self.req = self.account.getAccountRequest(self.user)
- self.chunkLimit = -1 # chunk limit, -1 for unlimited
- #: enables resume (will be ignored if server dont accept chunks)
- self.resumeDownload = True
- self.multiDL = True #every hoster with account should provide multiple downloads
- #: premium status
- self.premium = self.account.isPremium(self.user)
- else:
- self.req = pyfile.m.core.requestFactory.getRequest(self.__name__)
-
- #: associated pyfile instance, see `PyFile`
- self.pyfile = pyfile
- self.thread = None # holds thread in future
-
- #: location where the last call to download was saved
- self.lastDownload = ""
- #: re match of the last call to `checkDownload`
- self.lastCheck = None
- #: js engine, see `JsEngine`
- self.js = self.core.js
- self.cTask = None #captcha task
-
- self.retries = 0 # amount of retries already made
- self.html = None # some plugins store html code here
-
- self.init()
-
- def getChunkCount(self):
- if self.chunkLimit <= 0:
- return self.config["download"]["chunks"]
- return min(self.config["download"]["chunks"], self.chunkLimit)
-
- def __call__(self):
- return self.__name__
-
- def init(self):
- """initialize the plugin (in addition to `__init__`)"""
- pass
-
- def setup(self):
- """ setup for enviroment and other things, called before downloading (possibly more than one time)"""
- pass
-
- def preprocessing(self, thread):
- """ handles important things to do before starting """
- self.thread = thread
-
- if self.account:
- self.account.checkLogin(self.user)
- else:
- self.req.clearCookies()
-
- self.setup()
-
- self.pyfile.setStatus("starting")
-
- return self.process(self.pyfile)
-
-
- def process(self, pyfile):
- """the 'main' method of every plugin, you **have to** overwrite it"""
- raise NotImplementedError
-
- def resetAccount(self):
- """ dont use account and retry download """
- self.account = None
- self.req = self.core.requestFactory.getRequest(self.__name__)
- self.retry()
-
- def checksum(self, local_file=None):
- """
- return codes:
- 0 - checksum ok
- 1 - checksum wrong
- 5 - can't get checksum
- 10 - not implemented
- 20 - unknown error
- """
- #@TODO checksum check hook
-
- return True, 10
-
-
- def setWait(self, seconds, reconnect=False):
- """Set a specific wait time later used with `wait`
-
- :param seconds: wait time in seconds
- :param reconnect: True if a reconnect would avoid wait time
- """
- if reconnect:
- self.wantReconnect = True
- self.pyfile.waitUntil = time() + int(seconds)
-
- def wait(self):
- """ waits the time previously set """
- self.waiting = True
- self.pyfile.setStatus("waiting")
-
- while self.pyfile.waitUntil > time():
- self.thread.m.reconnecting.wait(2)
-
- if self.pyfile.abort: raise Abort
- if self.thread.m.reconnecting.isSet():
- self.waiting = False
- self.wantReconnect = False
- raise Reconnect
-
- self.waiting = False
- self.pyfile.setStatus("starting")
-
- def fail(self, reason):
- """ fail and give reason """
- raise Fail(reason)
-
- def offline(self):
- """ fail and indicate file is offline """
- raise Fail("offline")
-
- def tempOffline(self):
- """ fail and indicates file ist temporary offline, the core may take consequences """
- raise Fail("temp. offline")
-
- def retry(self, max_tries=3, wait_time=1, reason=""):
- """Retries and begin again from the beginning
-
- :param max_tries: number of maximum retries
- :param wait_time: time to wait in seconds
- :param reason: reason for retrying, will be passed to fail if max_tries reached
- """
- if 0 < max_tries <= self.retries:
- if not reason: reason = "Max retries reached"
- raise Fail(reason)
-
- self.wantReconnect = False
- self.setWait(wait_time)
- self.wait()
-
- self.retries += 1
- raise Retry(reason)
-
- def invalidCaptcha(self):
- if self.cTask:
- self.cTask.invalid()
-
- def correctCaptcha(self):
- if self.cTask:
- self.cTask.correct()
-
- 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()
-
- has_plugin = self.__name__ in self.core.pluginManager.captchaPlugins
-
- if self.core.captcha:
- Ocr = self.core.pluginManager.loadClass("captcha", self.__name__)
- else:
- Ocr = None
-
- if Ocr and not forceUser:
- sleep(randint(3000, 5000) / 1000.0)
- if self.pyfile.abort: raise Abort
-
- ocr = Ocr()
- result = ocr.get_captcha(temp_file.name)
- else:
- captchaManager = self.core.captchaManager
- task = captchaManager.newTask(img, imgtype, temp_file.name, result_type)
- self.cTask = task
- captchaManager.handleCaptcha(task)
-
- while task.isWaiting():
- if self.pyfile.abort:
- captchaManager.removeTask(task)
- raise Abort
- sleep(1)
-
- captchaManager.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 appropiate time by any of the plugins."))
-
- 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 load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
- """Load content at url and returns it
-
- :param url:
- :param get:
- :param post:
- :param ref:
- :param cookies:
- :param just_header: if True only the header will be retrieved and returned as dict
- :param decode: Wether to decode the output according to http header, should be True in most cases
- :return: Loaded content
- """
- if self.pyfile.abort: raise Abort
- #utf8 vs decode -> please use decode attribute in all future plugins
- if type(url) == unicode: url = str(url)
-
- 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 download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False):
- """Downloads the content at url to download folder
-
- :param url:
- :param get:
- :param post:
- :param ref:
- :param cookies:
- :param disposition: if True and server provides content-disposition header\
- the filename will be changed if needed
- :return: The location where the file was saved
- """
-
- self.checkForSameFiles()
-
- self.pyfile.setStatus("downloading")
-
- download_folder = self.config['general']['download_folder']
-
- location = save_join(download_folder, self.pyfile.package().folder)
-
- if not exists(location):
- makedirs(location, int(self.core.config["permission"]["folder"], 8))
-
- if self.core.config["permission"]["change_dl"] and os.name != "nt":
- try:
- uid = getpwnam(self.config["permission"]["user"])[2]
- gid = getgrnam(self.config["permission"]["group"])[2]
-
- chown(location, uid, gid)
- except Exception, e:
- self.log.warning(_("Setting User and Group failed: %s") % str(e))
-
- # convert back to unicode
- location = fs_decode(location)
- name = save_path(self.pyfile.name)
-
- filename = join(location, name)
-
- self.core.hookManager.dispatchEvent("downloadStarts", self.pyfile, url, filename)
-
- try:
- newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies,
- chunks=self.getChunkCount(), resume=self.resumeDownload,
- progressNotify=self.pyfile.setProgress, disposition=disposition)
- finally:
- self.pyfile.size = self.req.size
-
- if disposition and newname and newname != name: #triple check, just to be sure
- self.log.info("%(name)s saved as %(newname)s" % {"name": name, "newname": newname})
- self.pyfile.name = newname
- filename = join(location, newname)
-
- fs_filename = fs_encode(filename)
-
- if self.core.config["permission"]["change_file"]:
- chmod(fs_filename, int(self.core.config["permission"]["file"], 8))
-
- if self.core.config["permission"]["change_dl"] and os.name != "nt":
- try:
- uid = getpwnam(self.config["permission"]["user"])[2]
- gid = getgrnam(self.config["permission"]["group"])[2]
-
- chown(fs_filename, uid, gid)
- except Exception, e:
- self.log.warning(_("Setting User and Group failed: %s") % str(e))
-
- self.lastDownload = filename
- return self.lastDownload
-
- def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0):
- """ checks the content of the last downloaded file, re match is saved to `lastCheck`
-
- :param rules: dict with names and rules to match (compiled regexp or strings)
- :param api_size: expected file size
- :param max_size: if the file is larger then it wont be checked
- :param delete: delete if matched
- :param read_size: amount of bytes to read from files larger then max_size
- :return: dictionary key of the first rule that matched
- """
- lastDownload = fs_encode(self.lastDownload)
- if not exists(lastDownload): return None
-
- size = stat(lastDownload)
- size = size.st_size
-
- if api_size and api_size <= size: return None
- elif size > max_size and not read_size: return None
- self.log.debug("Download Check triggered")
- f = open(lastDownload, "rb")
- content = f.read(read_size if read_size else -1)
- f.close()
- #produces encoding errors, better log to other file in the future?
- #self.log.debug("Content: %s" % content)
- for name, rule in rules.iteritems():
- if type(rule) in (str, unicode):
- if rule in content:
- if delete:
- remove(lastDownload)
- return name
- elif hasattr(rule, "search"):
- m = rule.search(content)
- if m:
- if delete:
- remove(lastDownload)
- self.lastCheck = m
- return name
-
-
- def getPassword(self):
- """ get the password the user provided in the package"""
- password = self.pyfile.package().password
- if not password: return ""
- return password
-
-
- def checkForSameFiles(self, starting=False):
- """ checks if same file was/is downloaded within same package
-
- :param starting: indicates that the current download is going to start
- :raises SkipDownload:
- """
-
- pack = self.pyfile.package()
-
- for pyfile in self.core.files.cache.values():
- if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder:
- if pyfile.status in (0, 12): #finished or downloading
- raise SkipDownload(pyfile.pluginname)
- elif pyfile.status in (
- 5, 7) and starting: #a download is waiting/starting and was appenrently started before
- raise SkipDownload(pyfile.pluginname)
-
- download_folder = self.config['general']['download_folder']
- location = save_join(download_folder, pack.folder, self.pyfile.name)
-
- if starting and self.core.config['download']['skip_existing'] and exists(location):
- size = os.stat(location).st_size
- if size >= self.pyfile.size:
- raise SkipDownload("File exists.")
-
- pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name)
- if pyfile:
- if exists(location):
- raise SkipDownload(pyfile[0])
-
- self.log.debug("File %s not skipped, because it does not exists." % self.pyfile.name)
-
- def clean(self):
- """ clean everything and remove references """
- if hasattr(self, "pyfile"):
- del self.pyfile
- if hasattr(self, "req"):
- self.req.close()
- del self.req
- if hasattr(self, "thread"):
- del self.thread
- if hasattr(self, "html"):
- del self.html
diff --git a/module/plugins/PluginManager.py b/module/plugins/PluginManager.py
deleted file mode 100644
index f3f5f47bc..000000000
--- a/module/plugins/PluginManager.py
+++ /dev/null
@@ -1,380 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-import re
-import sys
-
-from os import listdir, makedirs
-from os.path import isfile, join, exists, abspath
-from sys import version_info
-from itertools import chain
-from traceback import print_exc
-
-from module.lib.SafeEval import const_eval as literal_eval
-from module.ConfigParser import IGNORE
-
-class PluginManager:
- ROOT = "module.plugins."
- USERROOT = "userplugins."
- TYPES = ("crypter", "container", "hoster", "captcha", "accounts", "hooks", "internal")
-
- PATTERN = re.compile(r'__pattern__.*=.*r("|\')([^"\']+)')
- VERSION = re.compile(r'__version__.*=.*("|\')([0-9.]+)')
- CONFIG = re.compile(r'__config__.*=.*\[([^\]]+)', re.MULTILINE)
- DESC = re.compile(r'__description__.?=.?("|"""|\')([^"\']+)')
-
-
- def __init__(self, core):
- self.core = core
-
- #self.config = self.core.config
- self.log = core.log
-
- self.plugins = {}
- self.createIndex()
-
- #register for import hook
- sys.meta_path.append(self)
-
-
- def createIndex(self):
- """create information for all plugins available"""
-
- sys.path.append(abspath(""))
-
- if not exists("userplugins"):
- makedirs("userplugins")
- if not exists(join("userplugins", "__init__.py")):
- f = open(join("userplugins", "__init__.py"), "wb")
- f.close()
-
- self.plugins["crypter"] = self.crypterPlugins = self.parse("crypter", pattern=True)
- self.plugins["container"] = self.containerPlugins = self.parse("container", pattern=True)
- self.plugins["hoster"] = self.hosterPlugins = self.parse("hoster", pattern=True)
-
- self.plugins["captcha"] = self.captchaPlugins = self.parse("captcha")
- self.plugins["accounts"] = self.accountPlugins = self.parse("accounts")
- self.plugins["hooks"] = self.hookPlugins = self.parse("hooks")
- self.plugins["internal"] = self.internalPlugins = self.parse("internal")
-
- self.log.debug("created index of plugins")
-
- def parse(self, folder, pattern=False, home={}):
- """
- returns dict with information
- home contains parsed plugins from module.
-
- {
- name : {path, version, config, (pattern, re), (plugin, class)}
- }
-
- """
- plugins = {}
- if home:
- pfolder = join("userplugins", folder)
- if not exists(pfolder):
- makedirs(pfolder)
- if not exists(join(pfolder, "__init__.py")):
- f = open(join(pfolder, "__init__.py"), "wb")
- f.close()
-
- else:
- pfolder = join(pypath, "module", "plugins", folder)
-
- for f in listdir(pfolder):
- if (isfile(join(pfolder, f)) and f.endswith(".py") or f.endswith("_25.pyc") or f.endswith(
- "_26.pyc") or f.endswith("_27.pyc")) and not f.startswith("_"):
- data = open(join(pfolder, f))
- content = data.read()
- data.close()
-
- if f.endswith("_25.pyc") and version_info[0:2] != (2, 5):
- continue
- elif f.endswith("_26.pyc") and version_info[0:2] != (2, 6):
- continue
- elif f.endswith("_27.pyc") and version_info[0:2] != (2, 7):
- continue
-
- name = f[:-3]
- if name[-1] == ".": name = name[:-4]
-
- version = self.VERSION.findall(content)
- if version:
- version = float(version[0][1])
- else:
- version = 0
-
- # home contains plugins from pyload root
- if home and name in home:
- if home[name]["v"] >= version:
- continue
-
- if name in IGNORE or (folder, name) in IGNORE:
- continue
-
- plugins[name] = {}
- plugins[name]["v"] = version
-
- module = f.replace(".pyc", "").replace(".py", "")
-
- # the plugin is loaded from user directory
- plugins[name]["user"] = True if home else False
- plugins[name]["name"] = module
-
- if pattern:
- pattern = self.PATTERN.findall(content)
-
- if pattern:
- pattern = pattern[0][1]
- else:
- pattern = "^unmachtable$"
-
- plugins[name]["pattern"] = pattern
-
- try:
- plugins[name]["re"] = re.compile(pattern)
- except:
- self.log.error(_("%s has a invalid pattern.") % name)
-
-
- # internals have no config
- if folder == "internal":
- self.core.config.deleteConfig(name)
- continue
-
- config = self.CONFIG.findall(content)
- if config:
- config = literal_eval(config[0].strip().replace("\n", "").replace("\r", ""))
- desc = self.DESC.findall(content)
- desc = desc[0][1] if desc else ""
-
- if type(config[0]) == tuple:
- config = [list(x) for x in config]
- else:
- config = [list(config)]
-
- if folder == "hooks":
- append = True
- for item in config:
- if item[0] == "activated": append = False
-
- # activated flag missing
- if append: config.append(["activated", "bool", "Activated", False])
-
- try:
- self.core.config.addPluginConfig(name, config, desc)
- except:
- self.log.error("Invalid config in %s: %s" % (name, config))
-
- elif folder == "hooks": #force config creation
- desc = self.DESC.findall(content)
- desc = desc[0][1] if desc else ""
- config = (["activated", "bool", "Activated", False],)
-
- try:
- self.core.config.addPluginConfig(name, config, desc)
- except:
- self.log.error("Invalid config in %s: %s" % (name, config))
-
- if not home:
- temp = self.parse(folder, pattern, plugins)
- plugins.update(temp)
-
- return plugins
-
-
- def parseUrls(self, urls):
- """parse plugins for given list of urls"""
-
- last = None
- res = [] # tupels of (url, plugin)
-
- for url in urls:
- if type(url) not in (str, unicode, buffer): continue
- found = False
-
- if last and last[1]["re"].match(url):
- res.append((url, last[0]))
- continue
-
- for name, value in chain(self.crypterPlugins.iteritems(), self.hosterPlugins.iteritems(),
- self.containerPlugins.iteritems()):
- if value["re"].match(url):
- res.append((url, name))
- last = (name, value)
- found = True
- break
-
- if not found:
- res.append((url, "BasePlugin"))
-
- return res
-
- def findPlugin(self, name, pluginlist=("hoster", "crypter", "container")):
- for ptype in pluginlist:
- if name in self.plugins[ptype]:
- return self.plugins[ptype][name], ptype
- return None, None
-
- def getPlugin(self, name, original=False):
- """return plugin module from hoster|decrypter|container"""
- plugin, type = self.findPlugin(name)
-
- if not plugin:
- self.log.warning("Plugin %s not found." % name)
- plugin = self.hosterPlugins["BasePlugin"]
-
- if "new_module" in plugin and not original:
- return plugin["new_module"]
-
- return self.loadModule(type, name)
-
- def getPluginName(self, name):
- """ used to obtain new name if other plugin was injected"""
- plugin, type = self.findPlugin(name)
-
- if "new_name" in plugin:
- return plugin["new_name"]
-
- return name
-
- def loadModule(self, type, name):
- """ Returns loaded module for plugin
-
- :param type: plugin type, subfolder of module.plugins
- :param name:
- """
- plugins = self.plugins[type]
- if name in plugins:
- if "module" in plugins[name]: return plugins[name]["module"]
- try:
- module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]["name"]), globals(), locals(),
- plugins[name]["name"])
- plugins[name]["module"] = module #cache import, maybe unneeded
- return module
- except Exception, e:
- self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)})
- if self.core.debug:
- print_exc()
-
- def loadClass(self, type, name):
- """Returns the class of a plugin with the same name"""
- module = self.loadModule(type, name)
- if module: return getattr(module, name)
-
- def getAccountPlugins(self):
- """return list of account plugin names"""
- return self.accountPlugins.keys()
-
- def find_module(self, fullname, path=None):
- #redirecting imports if necesarry
- if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #seperate pyload plugins
- if fullname.startswith(self.USERROOT): user = 1
- else: user = 0 #used as bool and int
-
- split = fullname.split(".")
- if len(split) != 4 - user: return
- type, name = split[2 - user:4 - user]
-
- if type in self.plugins and name in self.plugins[type]:
- #userplugin is a newer version
- if not user and self.plugins[type][name]["user"]:
- return self
- #imported from userdir, but pyloads is newer
- if user and not self.plugins[type][name]["user"]:
- return self
-
-
- def load_module(self, name, replace=True):
- if name not in sys.modules: #could be already in modules
- if replace:
- if self.ROOT in name:
- newname = name.replace(self.ROOT, self.USERROOT)
- else:
- newname = name.replace(self.USERROOT, self.ROOT)
- else: newname = name
-
- base, plugin = newname.rsplit(".", 1)
-
- self.log.debug("Redirected import %s -> %s" % (name, newname))
-
- module = __import__(newname, globals(), locals(), [plugin])
- #inject under new an old name
- sys.modules[name] = module
- sys.modules[newname] = module
-
- return sys.modules[name]
-
-
- def reloadPlugins(self, type_plugins):
- """ reloads and reindexes plugins """
- if not type_plugins: return False
-
- self.log.debug("Request reload of plugins: %s" % type_plugins)
-
- as_dict = {}
- for t,n in type_plugins:
- if t in as_dict:
- as_dict[t].append(n)
- else:
- as_dict[t] = [n]
-
- # we do not reload hooks or internals, would cause to much side effects
- if "hooks" in as_dict or "internal" in as_dict:
- return False
-
- for type in as_dict.iterkeys():
- for plugin in as_dict[type]:
- if plugin in self.plugins[type]:
- if "module" in self.plugins[type][plugin]:
- self.log.debug("Reloading %s" % plugin)
- reload(self.plugins[type][plugin]["module"])
-
- #index creation
- self.plugins["crypter"] = self.crypterPlugins = self.parse("crypter", pattern=True)
- self.plugins["container"] = self.containerPlugins = self.parse("container", pattern=True)
- self.plugins["hoster"] = self.hosterPlugins = self.parse("hoster", pattern=True)
- self.plugins["captcha"] = self.captchaPlugins = self.parse("captcha")
- self.plugins["accounts"] = self.accountPlugins = self.parse("accounts")
-
- if "accounts" in as_dict: #accounts needs to be reloaded
- self.core.accountManager.initPlugins()
- self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
-
- return True
-
-
-
-if __name__ == "__main__":
- _ = lambda x: x
- pypath = "/home/christian/Projekte/pyload-0.4/module/plugins"
-
- from time import time
-
- p = PluginManager(None)
-
- a = time()
-
- test = ["http://www.youtube.com/watch?v=%s" % x for x in range(0, 100)]
- print p.parseUrls(test)
-
- b = time()
-
- print b - a, "s"
-
diff --git a/module/plugins/__init__.py b/module/plugins/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/__init__.py
+++ /dev/null
diff --git a/module/plugins/accounts/AlldebridCom.py b/module/plugins/accounts/AlldebridCom.py
deleted file mode 100644
index f9c1f2ca6..000000000
--- a/module/plugins/accounts/AlldebridCom.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import xml.dom.minidom as dom
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.Account import Account
-
-
-class AlldebridCom(Account):
- __name__ = "AlldebridCom"
- __type__ = "account"
- __version__ = "0.23"
-
- __description__ = """AllDebrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- html = req.load("http://www.alldebrid.com/account/")
- soup = BeautifulSoup(html)
-
- #Try to parse expiration date directly from the control panel page (better accuracy)
- try:
- time_text = soup.find('div', attrs={'class': 'remaining_time_text'}).strong.string
-
- self.logDebug("Account expires in: %s" % time_text)
-
- p = re.compile('\d+')
- exp_data = p.findall(time_text)
- exp_time = time.time() + int(exp_data[0]) * 24 * 60 * 60 + int(
- exp_data[1]) * 60 * 60 + (int(exp_data[2]) - 1) * 60
-
- #Get expiration date from API
- except Exception:
- data = self.getAccountData(user)
- html = req.load("http://www.alldebrid.com/api.php",
- get={'action': "info_user", 'login': user, 'pw': data['password']})
-
- self.logDebug(html)
-
- xml = dom.parseString(html)
- exp_time = time.time() + int(xml.getElementsByTagName("date")[0].childNodes[0].nodeValue) * 24 * 60 * 60
-
- account_info = {"validuntil": exp_time, "trafficleft": -1}
- return account_info
-
-
- def login(self, user, data, req):
- html = req.load("http://www.alldebrid.com/register/",
- get={'action' : "login",
- 'login_login' : user,
- 'login_password': data['password']},
- decode=True)
-
- if "This login doesn't exist" in html \
- or "The password is not valid" in html \
- or "Invalid captcha" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/BackinNet.py b/module/plugins/accounts/BackinNet.py
deleted file mode 100644
index 46c8d7ac5..000000000
--- a/module/plugins/accounts/BackinNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class BackinNet(XFSAccount):
- __name__ = "BackinNet"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Backin.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "backin.net"
diff --git a/module/plugins/accounts/BillionuploadsCom.py b/module/plugins/accounts/BillionuploadsCom.py
deleted file mode 100644
index 11af36591..000000000
--- a/module/plugins/accounts/BillionuploadsCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class BillionuploadsCom(XFSAccount):
- __name__ = "BillionuploadsCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Billionuploads.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "billionuploads.com"
diff --git a/module/plugins/accounts/BitshareCom.py b/module/plugins/accounts/BitshareCom.py
deleted file mode 100644
index 00e546f6d..000000000
--- a/module/plugins/accounts/BitshareCom.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class BitshareCom(Account):
- __name__ = "BitshareCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Bitshare account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Paul King", None)]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://bitshare.com/mysettings.html")
-
- if "\"http://bitshare.com/myupgrade.html\">Free" in html:
- return {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- if not '<input type="checkbox" name="directdownload" checked="checked" />' in html:
- self.logWarning(_("Activate direct Download in your Bitshare Account"))
-
- return {"validuntil": -1, "trafficleft": -1, "premium": True}
-
-
- def login(self, user, data, req):
- html = req.load("http://bitshare.com/login.html",
- post={"user": user, "password": data['password'], "submit": "Login"},
- decode=True)
-
- if "login" in req.lastEffectiveURL:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CatShareNet.py b/module/plugins/accounts/CatShareNet.py
deleted file mode 100644
index bb42f443f..000000000
--- a/module/plugins/accounts/CatShareNet.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class CatShareNet(Account):
- __name__ = "CatShareNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """CatShareNet account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None)]
-
-
- PREMIUM_PATTERN = r'<a href="/premium">Konto:[\s\n]*Premium'
- VALID_UNTIL_PATTERN = r'>Konto premium.*?<strong>(.*?)</strong></span>'
- TRAFFIC_LEFT_PATTERN = r'<a href="/premium">([0-9.]+ [kMG]B)'
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = -1
- trafficleft = -1
-
- html = req.load("http://catshare.net/", decode=True)
-
- if re.search(self.PREMIUM_PATTERN, html):
- premium = True
-
- try:
- expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug("Expire date: " + expiredate)
-
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
-
- except Exception:
- pass
-
- try:
- trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
-
- except Exception:
- pass
-
- return {'premium': premium, 'trafficleft': trafficleft, 'validuntil': validuntil}
-
-
- def login(self, user, data, req):
- html = req.load("http://catshare.net/login",
- post={'user_email': user,
- 'user_password': data['password'],
- 'remindPassword': 0,
- 'user[submit]': "Login"},
- decode=True)
-
- if not '<a href="/logout">Wyloguj</a>' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CloudzillaTo.py b/module/plugins/accounts/CloudzillaTo.py
deleted file mode 100644
index d22d5e4b3..000000000
--- a/module/plugins/accounts/CloudzillaTo.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class CloudzillaTo(Account):
- __name__ = "CloudzillaTo"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Cloudzilla.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'<h2>account type</h2>\s*Premium Account'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.cloudzilla.to/")
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- return {'validuntil': -1, 'trafficleft': -1, 'premium': premium}
-
-
- def login(self, user, data, req):
- html = req.load("http://www.cloudzilla.to/",
- post={'lusername': user,
- 'lpassword': data['password'],
- 'w' : "dologin"},
- decode=True)
-
- if "ERROR" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CramitIn.py b/module/plugins/accounts/CramitIn.py
deleted file mode 100644
index a9e2274a2..000000000
--- a/module/plugins/accounts/CramitIn.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class CramitIn(XFSAccount):
- __name__ = "CramitIn"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Cramit.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "cramit.in"
diff --git a/module/plugins/accounts/CzshareCom.py b/module/plugins/accounts/CzshareCom.py
deleted file mode 100644
index 300943828..000000000
--- a/module/plugins/accounts/CzshareCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class CzshareCom(Account):
- __name__ = "CzshareCom"
- __type__ = "account"
- __version__ = "0.18"
-
- __description__ = """Czshare.com account plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([\d ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = None
-
- html = req.load("http://sdilej.cz/prehled_kreditu/")
-
- try:
- m = re.search(self.CREDIT_LEFT_PATTERN, html)
- trafficleft = self.parseTraffic(m.group(1).replace(' ', '').replace(',', '.')) + m.group(2)
- validuntil = time.mktime(time.strptime(m.group(3), '%d.%m.%y %H:%M'))
-
- except Exception, e:
- self.logError(e)
-
- else:
- premium = True
-
- return {'premium' : premium,
- 'validuntil' : validuntil,
- 'trafficleft': trafficleft}
-
-
- def login(self, user, data, req):
- html = req.load('https://sdilej.cz/index.php',
- post={"Prihlasit": "Prihlasit",
- "login-password": data['password'],
- "login-name": user},
- decode=True)
-
- if '<div class="login' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/DebridItaliaCom.py b/module/plugins/accounts/DebridItaliaCom.py
deleted file mode 100644
index 3df99101a..000000000
--- a/module/plugins/accounts/DebridItaliaCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class DebridItaliaCom(Account):
- __name__ = "DebridItaliaCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Debriditalia.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- WALID_UNTIL_PATTERN = r'Premium valid till: (.+?) \|'
-
-
- def loadAccountInfo(self, user, req):
- info = {"premium": False, "validuntil": None, "trafficleft": None}
- html = req.load("http://debriditalia.com/")
-
- if 'Account premium not activated' not in html:
- m = re.search(self.WALID_UNTIL_PATTERN, html)
- if m:
- validuntil = time.mktime(time.strptime(m.group(1), "%d/%m/%Y %H:%M"))
- info = {"premium": True, "validuntil": validuntil, "trafficleft": -1}
- else:
- self.logError(_("Unable to retrieve account information"))
-
- return info
-
-
- def login(self, user, data, req):
- html = req.load("http://debriditalia.com/login.php",
- get={'u': user, 'p': data['password']},
- decode=True)
-
- if 'NO' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/DepositfilesCom.py b/module/plugins/accounts/DepositfilesCom.py
deleted file mode 100644
index a0bd6a37c..000000000
--- a/module/plugins/accounts/DepositfilesCom.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class DepositfilesCom(Account):
- __name__ = "DepositfilesCom"
- __type__ = "account"
- __version__ = "0.32"
-
- __description__ = """Depositfiles.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://dfiles.eu/de/gold/")
- validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", html).group(1)
-
- validuntil = time.mktime(time.strptime(validuntil, "%Y-%m-%d %H:%M:%S"))
-
- return {"validuntil": validuntil, "trafficleft": -1}
-
-
- def login(self, user, data, req):
- html = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
- post={"login": user, "password": data['password']},
- decode=True)
-
- if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/EasybytezCom.py b/module/plugins/accounts/EasybytezCom.py
deleted file mode 100644
index 93d3e2c19..000000000
--- a/module/plugins/accounts/EasybytezCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class EasybytezCom(XFSAccount):
- __name__ = "EasybytezCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """EasyBytez.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "easybytez.com"
diff --git a/module/plugins/accounts/EuroshareEu.py b/module/plugins/accounts/EuroshareEu.py
deleted file mode 100644
index db4539e2e..000000000
--- a/module/plugins/accounts/EuroshareEu.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class EuroshareEu(Account):
- __name__ = "EuroshareEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Euroshare.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def loadAccountInfo(self, user, req):
- self.relogin(user)
- html = req.load("http://euroshare.eu/customer-zone/settings/")
-
- m = re.search('id="input_expire_date" value="(\d+\.\d+\.\d+ \d+:\d+)"', html)
- if m is None:
- premium, validuntil = False, -1
- else:
- premium = True
- validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y %H:%M"))
-
- return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load('http://euroshare.eu/customer-zone/login/',
- post={"trvale": "1",
- "login": user,
- "password": data['password']},
- decode=True)
-
- if u">Nesprávne prihlasovacie meno alebo heslo" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/ExashareCom.py b/module/plugins/accounts/ExashareCom.py
deleted file mode 100644
index 431798522..000000000
--- a/module/plugins/accounts/ExashareCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class ExashareCom(XFSAccount):
- __name__ = "ExashareCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Exashare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "exashare.com"
diff --git a/module/plugins/accounts/FastixRu.py b/module/plugins/accounts/FastixRu.py
deleted file mode 100644
index 51be3880f..000000000
--- a/module/plugins/accounts/FastixRu.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FastixRu(Account):
- __name__ = "FastixRu"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Fastix account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- html = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"}))
-
- points = html['points']
- kb = float(points) * 1024 ** 2 / 1000
-
- if points > 0:
- account_info = {"validuntil": -1, "trafficleft": kb}
- else:
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
- return account_info
-
-
- def login(self, user, data, req):
- html = req.load("http://fastix.ru/api_v2/",
- get={'sub': "get_apikey", 'email': user, 'password': data['password']})
-
- api = json_loads(html)
- api = api['apikey']
-
- data['api'] = api
-
- if "error_code" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FastshareCz.py b/module/plugins/accounts/FastshareCz.py
deleted file mode 100644
index b946e29ba..000000000
--- a/module/plugins/accounts/FastshareCz.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class FastshareCz(Account):
- __name__ = "FastshareCz"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """Fastshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CREDIT_PATTERN = r'Credit\s*:\s*</td>\s*<td>(.+?)\s*<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = -1
- trafficleft = None
- premium = False
-
- html = req.load("http://www.fastshare.cz/user", decode=True)
-
- m = re.search(self.CREDIT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
-
- premium = bool(trafficleft)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("fastshare.cz", "lang", "en")
-
- req.load('http://www.fastshare.cz/login') # Do not remove or it will not login
-
- html = req.load("http://www.fastshare.cz/sql.php",
- post={'login': user, 'heslo': data['password']},
- decode=True)
-
- if ">Wrong username or password" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/File4SafeCom.py b/module/plugins/accounts/File4SafeCom.py
deleted file mode 100644
index 50fe1aac8..000000000
--- a/module/plugins/accounts/File4SafeCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class File4SafeCom(XFSAccount):
- __name__ = "File4SafeCom"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """File4Safe.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- HOSTER_DOMAIN = "file4safe.com"
-
- LOGIN_FAIL_PATTERN = r'input_login'
diff --git a/module/plugins/accounts/FileParadoxIn.py b/module/plugins/accounts/FileParadoxIn.py
deleted file mode 100644
index c12d99d6a..000000000
--- a/module/plugins/accounts/FileParadoxIn.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FileParadoxIn(XFSAccount):
- __name__ = "FileParadoxIn"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """FileParadox.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "fileparadox.in"
diff --git a/module/plugins/accounts/FilecloudIo.py b/module/plugins/accounts/FilecloudIo.py
deleted file mode 100644
index 8ca55b1bc..000000000
--- a/module/plugins/accounts/FilecloudIo.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FilecloudIo(Account):
- __name__ = "FilecloudIo"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """FilecloudIo account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- # It looks like the first API request always fails, so we retry 5 times, it should work on the second try
- for _i in xrange(5):
- rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
- post={"username": user, "password": self.getAccountData(user)['password']})
- rep = json_loads(rep)
- if rep['status'] == 'ok':
- break
- elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password':
- self.logError(_("Wrong username or password"))
- return {"valid": False, "premium": False}
- else:
- return {"premium": False}
-
- akey = rep['akey']
- self.accounts[user]['akey'] = akey # Saved for hoster plugin
- rep = req.load("http://api.filecloud.io/api-fetch_account_details.api",
- post={"akey": akey})
- rep = json_loads(rep)
-
- if rep['is_premium'] == 1:
- return {"validuntil": float(rep['premium_until']), "trafficleft": -1}
- else:
- return {"premium": False}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("secure.filecloud.io", "lang", "en")
- html = req.load('https://secure.filecloud.io/user-login.html')
-
- if not hasattr(self, "form_data"):
- self.form_data = {}
-
- self.form_data['username'] = user
- self.form_data['password'] = data['password']
-
- html = req.load('https://secure.filecloud.io/user-login_p.html',
- post=self.form_data,
- multipart=True)
-
- if "you have successfully logged in" not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilefactoryCom.py b/module/plugins/accounts/FilefactoryCom.py
deleted file mode 100644
index 37b6f97a8..000000000
--- a/module/plugins/accounts/FilefactoryCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilefactoryCom(Account):
- __name__ = "FilefactoryCom"
- __type__ = "account"
- __version__ = "0.15"
-
- __description__ = """Filefactory.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- VALID_UNTIL_PATTERN = r'Premium valid until: <strong>(?P<D>\d{1,2})\w{1,2} (?P<M>\w{3}), (?P<Y>\d{4})</strong>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.filefactory.com/account/")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- premium = True
- validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<D> \g<M> \g<Y>', m.group(0))
- validuntil = time.mktime(time.strptime(validuntil, "%d %b %Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
-
- def login(self, user, data, req):
- req.http.c.setopt(pycurl.REFERER, "http://www.filefactory.com/member/login.php")
-
- html = req.load("http://www.filefactory.com/member/signin.php",
- post={"loginEmail" : user,
- "loginPassword": data['password'],
- "Submit" : "Sign In"})
-
- if req.lastEffectiveURL != "http://www.filefactory.com/account/":
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilejungleCom.py b/module/plugins/accounts/FilejungleCom.py
deleted file mode 100644
index b92a371a5..000000000
--- a/module/plugins/accounts/FilejungleCom.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilejungleCom(Account):
- __name__ = "FilejungleCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """Filejungle.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- login_timeout = 60
-
- URL = "http://filejungle.com/"
- TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ \w+ \d+)<br'
- LOGIN_FAILED_PATTERN = r'<span htmlfor="loginUser(Name|Password)" generated="true" class="fail_info">'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load(self.URL + "dashboard.php")
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- premium = True
- validuntil = time.mktime(time.strptime(m.group(1), "%d %b %Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
-
- def login(self, user, data, req):
- html = req.load(self.URL + "login.php",
- post={"loginUserName": user,
- "loginUserPassword": data['password'],
- "loginFormSubmit": "Login",
- "recaptcha_challenge_field": "",
- "recaptcha_response_field": "",
- "recaptcha_shortencode_field": ""},
- decode=True)
-
- if re.search(self.LOGIN_FAILED_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/FileomCom.py b/module/plugins/accounts/FileomCom.py
deleted file mode 100644
index 7c743f56a..000000000
--- a/module/plugins/accounts/FileomCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FileomCom(XFSAccount):
- __name__ = "FileomCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Fileom.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "fileom.com"
diff --git a/module/plugins/accounts/FilerNet.py b/module/plugins/accounts/FilerNet.py
deleted file mode 100644
index ac5fd11da..000000000
--- a/module/plugins/accounts/FilerNet.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilerNet(Account):
- __name__ = "FilerNet"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Filer.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- TOKEN_PATTERN = r'_csrf_token" value="(.+?)" />'
- WALID_UNTIL_PATTERN = r'Der Premium-Zugang ist gÃŒltig bis (.+)\.\s*</td>'
- TRAFFIC_PATTERN = r'Traffic</th>\s*<td>([^<]+)</td>'
- FREE_PATTERN = r'Account Status</th>\s*<td>\s*Free'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://filer.net/profile")
-
- # Free user
- if re.search(self.FREE_PATTERN, html):
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
- until = re.search(self.WALID_UNTIL_PATTERN, html)
- traffic = re.search(self.TRAFFIC_PATTERN, html)
-
- if until and traffic:
- validuntil = time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S"))
- trafficleft = self.parseTraffic(traffic.group(1))
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
- else:
- self.logError(_("Unable to retrieve account information"))
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
-
- def login(self, user, data, req):
- html = req.load("https://filer.net/login")
-
- token = re.search(self.TOKEN_PATTERN, html).group(1)
-
- html = req.load("https://filer.net/login_check",
- post={"_username": user,
- "_password": data['password'],
- "_remember_me": "on",
- "_csrf_token": token,
- "_target_path": "https://filer.net/"},
- decode=True)
-
- if 'Logout' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilerioCom.py b/module/plugins/accounts/FilerioCom.py
deleted file mode 100644
index 4c6755293..000000000
--- a/module/plugins/accounts/FilerioCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FilerioCom(XFSAccount):
- __name__ = "FilerioCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """FileRio.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "filerio.in"
diff --git a/module/plugins/accounts/FilesMailRu.py b/module/plugins/accounts/FilesMailRu.py
deleted file mode 100644
index ee309c425..000000000
--- a/module/plugins/accounts/FilesMailRu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class FilesMailRu(Account):
- __name__ = "FilesMailRu"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Filesmail.ru account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- return {"validuntil": None, "trafficleft": None}
-
-
- def login(self, user, data, req):
- user, domain = user.split("@")
-
- html = req.load("http://swa.mail.ru/cgi-bin/auth",
- post={"Domain": domain,
- "Login": user,
- "Password": data['password'],
- "Page": "http://files.mail.ru/"},
- decode=True)
-
- if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FileserveCom.py b/module/plugins/accounts/FileserveCom.py
deleted file mode 100644
index 5eb6b844c..000000000
--- a/module/plugins/accounts/FileserveCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FileserveCom(Account):
- __name__ = "FileserveCom"
- __type__ = "account"
- __version__ = "0.20"
-
- __description__ = """Fileserve.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
-
- html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(html)
-
- if res['type'] == "premium":
- validuntil = time.mktime(time.strptime(res['expireTime'], "%Y-%m-%d %H:%M:%S"))
- return {"trafficleft": res['traffic'], "validuntil": validuntil}
- else:
- return {"premium": False, "trafficleft": None, "validuntil": None}
-
-
- def login(self, user, data, req):
- html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(html)
-
- if not res['type']:
- self.wrongPassword()
-
- #login at fileserv html
- req.load("http://www.fileserve.com/login.php",
- post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
- "loginFormSubmit": "Login"})
diff --git a/module/plugins/accounts/FourSharedCom.py b/module/plugins/accounts/FourSharedCom.py
deleted file mode 100644
index 2777a142a..000000000
--- a/module/plugins/accounts/FourSharedCom.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class FourSharedCom(Account):
- __name__ = "FourSharedCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """FourShared.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- # Free mode only for now
- return {"premium": False}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("4shared.com", "4langcookie", "en")
-
- res = req.load("http://www.4shared.com/web/login",
- post={'login' : user,
- 'password' : data['password'],
- 'remember' : "on",
- '_remember': "on",
- 'returnTo' : "http://www.4shared.com/account/home.jsp"},
- decode=True)
-
- if 'Please log in to access your 4shared account' in res:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreakshareCom.py b/module/plugins/accounts/FreakshareCom.py
deleted file mode 100644
index ca3602a2c..000000000
--- a/module/plugins/accounts/FreakshareCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FreakshareCom(Account):
- __name__ = "FreakshareCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Freakshare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = None
-
- html = req.load("http://freakshare.com/")
-
- try:
- m = re.search(r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>', html, re.M)
- validuntil = time.mktime(time.strptime(m.group(1).strip(), "%d.%m.%Y - %H:%M"))
-
- except Exception:
- pass
-
- try:
- m = re.search(r'Traffic verbleibend:</td>\s*<td>([^<]+)', html, re.M)
- trafficleft = self.parseTraffic(m.group(1))
-
- except Exception:
- pass
-
- return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- req.load("http://freakshare.com/index.php?language=EN")
-
- html = req.load("http://freakshare.com/login.html",
- post={"submit": "Login", "user": user, "pass": data['password']},
- decode=True)
-
- if ">Wrong Username or Password" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreeWayMe.py b/module/plugins/accounts/FreeWayMe.py
deleted file mode 100644
index 14b9f1e9a..000000000
--- a/module/plugins/accounts/FreeWayMe.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FreeWayMe(Account):
- __name__ = "FreeWayMe"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """FreeWayMe account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def loadAccountInfo(self, user, req):
- status = self.getAccountStatus(user, req)
-
- self.logDebug(status)
-
- account_info = {"validuntil": -1, "premium": False}
- if status['premium'] == "Free":
- account_info['trafficleft'] = self.parseTraffic(status['guthaben'] + "MB")
- elif status['premium'] == "Spender":
- account_info['trafficleft'] = -1
- elif status['premium'] == "Flatrate":
- account_info = {"validuntil": float(status['Flatrate']),
- "trafficleft": -1,
- "premium": True}
-
- return account_info
-
-
- def login(self, user, data, req):
- status = self.getAccountStatus(user, req)
-
- # Check if user and password are valid
- if not status:
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- answer = req.load("https://www.free-way.me/ajax/jd.php",
- get={"id": 4, "user": user, "pass": self.getAccountData(user)['password']})
-
- self.logDebug("Login: %s" % answer)
-
- if answer == "Invalid login":
- self.wrongPassword()
-
- return json_loads(answer)
diff --git a/module/plugins/accounts/FshareVn.py b/module/plugins/accounts/FshareVn.py
deleted file mode 100644
index 7fcf88f20..000000000
--- a/module/plugins/accounts/FshareVn.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FshareVn(Account):
- __name__ = "FshareVn"
- __type__ = "account"
- __version__ = "0.09"
-
- __description__ = """Fshare.vn account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- VALID_UNTIL_PATTERN = ur'<dt>Thời hạn dùng:</dt>\s*<dd>([^<]+)</dd>'
- LIFETIME_PATTERN = ur'<dt>Lần đăng nhập trước:</dt>\s*<dd>.+?</dd>'
- TRAFFIC_LEFT_PATTERN = ur'<dt>Tổng Dung Lượng Tài Khoản</dt>\s*<dd.*?>([\d.]+) ([kKMG])B</dd>'
- DIRECT_DOWNLOAD_PATTERN = ur'<input type="checkbox"\s*([^=>]*)[^>]*/>Kích hoạt download trực tiếp</dt>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.fshare.vn/account_info.php", decode=True)
-
- if re.search(self.LIFETIME_PATTERN, html):
- self.logDebug("Lifetime membership detected")
- trafficleft = self.getTrafficLeft()
- return {"validuntil": -1, "trafficleft": trafficleft, "premium": True}
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- premium = True
- validuntil = time.mktime(time.strptime(m.group(1), '%I:%M:%S %p %d-%m-%Y'))
- trafficleft = self.getTrafficLeft()
- else:
- premium = False
- validuntil = None
- trafficleft = None
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load("https://www.fshare.vn/login.php",
- post={'LoginForm[email]' : user,
- 'LoginForm[password]' : data['password'],
- 'LoginForm[rememberMe]': 1,
- 'yt0' : "Login"},
- referer=True,
- decode=True)
-
- if not re.search(r'<img\s+alt="VIP"', html):
- self.wrongPassword()
-
-
- def getTrafficLeft(self):
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- return self.parseTraffic(m.group(1) + m.group(2)) if m else 0
diff --git a/module/plugins/accounts/Ftp.py b/module/plugins/accounts/Ftp.py
deleted file mode 100644
index f978d2fa0..000000000
--- a/module/plugins/accounts/Ftp.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class Ftp(Account):
- __name__ = "Ftp"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Ftp dummy account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- info_threshold = 1000000
- login_timeout = 1000000
diff --git a/module/plugins/accounts/HellshareCz.py b/module/plugins/accounts/HellshareCz.py
deleted file mode 100644
index e559b28e1..000000000
--- a/module/plugins/accounts/HellshareCz.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class HellshareCz(Account):
- __name__ = "HellshareCz"
- __type__ = "account"
- __version__ = "0.16"
-
- __description__ = """Hellshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- CREDIT_LEFT_PATTERN = r'<div class="credit-link">\s*<table>\s*<tr>\s*<th>(\d+|\d\d\.\d\d\.)</th>'
-
-
- def loadAccountInfo(self, user, req):
- self.relogin(user)
- html = req.load("http://www.hellshare.com/")
-
- m = re.search(self.CREDIT_LEFT_PATTERN, html)
- if m is None:
- trafficleft = None
- validuntil = None
- premium = False
- else:
- credit = m.group(1)
- premium = True
- try:
- if "." in credit:
- #Time-based account
- vt = [int(x) for x in credit.split('.')[:2]]
- lt = time.localtime()
- year = lt.tm_year + int(vt[1] < lt.tm_mon or (vt[1] == lt.tm_mon and vt[0] < lt.tm_mday))
- validuntil = time.mktime(time.strptime("%s%d 23:59:59" % (credit, year), "%d.%m.%Y %H:%M:%S"))
- trafficleft = -1
- else:
- #Traffic-based account
- trafficleft = self.parseTraffic(credit + "MB")
- validuntil = -1
- except Exception, e:
- self.logError(_("Unable to parse credit info"), e)
- validuntil = -1
- trafficleft = -1
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load('http://www.hellshare.com/', decode=True)
- if req.lastEffectiveURL != 'http://www.hellshare.com/':
- #Switch to English
- self.logDebug("Switch lang - URL: %s" % req.lastEffectiveURL)
-
- json = req.load("%s?do=locRouter-show" % req.lastEffectiveURL)
- hash = re.search(r"(\-\-[0-9a-f]+\-)", json).group(1)
-
- self.logDebug("Switch lang - HASH: %s" % hash)
-
- html = req.load('http://www.hellshare.com/%s/' % hash, decode=True)
-
- if re.search(self.CREDIT_LEFT_PATTERN, html):
- self.logDebug("Already logged in")
- return
-
- html = req.load('http://www.hellshare.com/login?do=loginForm-submit',
- post={"login": "Log in",
- "password": data['password'],
- "username": user,
- "perm_login": "on"},
- decode=True)
-
- if "<p>You input a wrong user name or wrong password</p>" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/Http.py b/module/plugins/accounts/Http.py
deleted file mode 100644
index 07e46eb07..000000000
--- a/module/plugins/accounts/Http.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class Http(Account):
- __name__ = "Http"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Http dummy account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- info_threshold = 1000000
- login_timeout = 1000000
diff --git a/module/plugins/accounts/HugefilesNet.py b/module/plugins/accounts/HugefilesNet.py
deleted file mode 100644
index 5da3bbc37..000000000
--- a/module/plugins/accounts/HugefilesNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class HugefilesNet(XFSAccount):
- __name__ = "HugefilesNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Hugefiles.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "hugefiles.net"
diff --git a/module/plugins/accounts/HundredEightyUploadCom.py b/module/plugins/accounts/HundredEightyUploadCom.py
deleted file mode 100644
index 319a3feee..000000000
--- a/module/plugins/accounts/HundredEightyUploadCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class HundredEightyUploadCom(XFSAccount):
- __name__ = "HundredEightyUploadCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """180upload.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "180upload.com"
diff --git a/module/plugins/accounts/JunkyvideoCom.py b/module/plugins/accounts/JunkyvideoCom.py
deleted file mode 100644
index 8275ff176..000000000
--- a/module/plugins/accounts/JunkyvideoCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class JunkyvideoCom(XFSAccount):
- __name__ = "JunkyvideoCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Junkyvideo.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "junkyvideo.com"
diff --git a/module/plugins/accounts/JunocloudMe.py b/module/plugins/accounts/JunocloudMe.py
deleted file mode 100644
index b0fc160f3..000000000
--- a/module/plugins/accounts/JunocloudMe.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class JunocloudMe(XFSAccount):
- __name__ = "JunocloudMe"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Junocloud.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "junocloud.me"
diff --git a/module/plugins/accounts/Keep2ShareCc.py b/module/plugins/accounts/Keep2ShareCc.py
deleted file mode 100644
index d2ba1d237..000000000
--- a/module/plugins/accounts/Keep2ShareCc.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class Keep2ShareCc(Account):
- __name__ = "Keep2ShareCc"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Keep2Share.cc account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("aeronaut", "aeronaut@pianoguy.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'Premium expires:\s*<b>(.+?)<'
- TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):\s*<b><a href="/user/statistic.html">(.+?)<'
-
- LOGIN_FAIL_PATTERN = r'Please fix the following input errors'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = req.load("http://keep2share.cc/site/profile.html", decode=True)
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- if expiredate == "LifeTime":
- premium = True
- validuntil = -1
- else:
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%Y.%m.%d"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- premium = True if validuntil > time.mktime(time.gmtime()) else False
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- try:
- trafficleft = self.parseTraffic(m.group(1))
-
- except Exception, e:
- self.logError(e)
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("keep2share.cc", "lang", "en")
-
- html = req.load("http://keep2share.cc/login.html",
- post={'LoginForm[username]' : user,
- 'LoginForm[password]' : data['password'],
- 'LoginForm[rememberMe]': 1,
- 'yt0' : ""},
- decode=True)
-
- if re.search(self.LOGIN_FAIL_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/LetitbitNet.py b/module/plugins/accounts/LetitbitNet.py
deleted file mode 100644
index 7f973d2d3..000000000
--- a/module/plugins/accounts/LetitbitNet.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-# from module.common.json_layer import json_loads, json_dumps
-
-
-class LetitbitNet(Account):
- __name__ = "LetitbitNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Letitbit.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
- # api_key = self.getAccountData(user)['password']
- # json_data = [api_key, ['key/info']]
- # api_rep = req.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
- # self.logDebug("API Key Info: " + api_rep)
- # api_rep = json_loads(api_rep)
- #
- # if api_rep['status'] == 'FAIL':
- # self.logWarning(api_rep['data'])
- # return {'valid': False, 'premium': False}
-
- return {"premium": True}
-
-
- def login(self, user, data, req):
- # API_KEY is the username and the PREMIUM_KEY is the password
- self.logInfo(_("You must use your API KEY as username and the PREMIUM KEY as password"))
diff --git a/module/plugins/accounts/LinestorageCom.py b/module/plugins/accounts/LinestorageCom.py
deleted file mode 100644
index a48d5beb9..000000000
--- a/module/plugins/accounts/LinestorageCom.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class LinestorageCom(XFSAccount):
- __name__ = "LinestorageCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Linestorage.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "linestorage.com"
- HOSTER_URL = "http://linestorage.com/"
diff --git a/module/plugins/accounts/LinksnappyCom.py b/module/plugins/accounts/LinksnappyCom.py
deleted file mode 100644
index 34571d374..000000000
--- a/module/plugins/accounts/LinksnappyCom.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class LinksnappyCom(Account):
- __name__ = "LinksnappyCom"
- __type__ = "account"
- __version__ = "0.05"
- __description__ = """Linksnappy.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- r = req.load('http://gen.linksnappy.com/lseAPI.php',
- get={'act': 'USERDETAILS', 'username': user, 'password': hashlib.md5(data['password']).hexdigest()})
-
- self.logDebug("JSON data: " + r)
-
- j = json_loads(r)
-
- if j['error']:
- return {"premium": False}
-
- validuntil = j['return']['expire']
-
- if validuntil == 'lifetime':
- validuntil = -1
-
- elif validuntil == 'expired':
- return {"premium": False}
-
- else:
- validuntil = float(validuntil)
-
- if 'trafficleft' not in j['return'] or isinstance(j['return']['trafficleft'], str):
- trafficleft = -1
- else:
- trafficleft = self.parseTraffic("%d MB" % j['return']['trafficleft'])
-
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- r = req.load("http://gen.linksnappy.com/lseAPI.php",
- get={'act' : 'USERDETAILS',
- 'username': user,
- 'password': hashlib.md5(data['password']).hexdigest()},
- decode=True)
-
- if 'Invalid Account Details' in r:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MegaDebridEu.py b/module/plugins/accounts/MegaDebridEu.py
deleted file mode 100644
index a082b97af..000000000
--- a/module/plugins/accounts/MegaDebridEu.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class MegaDebridEu(Account):
- __name__ = "MegaDebridEu"
- __type__ = "account"
- __version__ = "0.20"
-
- __description__ = """mega-debrid.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
-
-
- # Define the base URL of MegaDebrid api
- API_URL = "https://www.mega-debrid.eu/api.php"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- jsonResponse = req.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- res = json_loads(jsonResponse)
-
- if res['response_code'] == "ok":
- return {"premium": True, "validuntil": float(res['vip_end']), "status": True}
- else:
- self.logError(res)
- return {"status": False, "premium": False}
-
-
- def login(self, user, data, req):
- jsonResponse = req.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- res = json_loads(jsonResponse)
- if res['response_code'] != "ok":
- self.wrongPassword()
diff --git a/module/plugins/accounts/MegaRapidCz.py b/module/plugins/accounts/MegaRapidCz.py
deleted file mode 100644
index 262d5a818..000000000
--- a/module/plugins/accounts/MegaRapidCz.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegaRapidCz(Account):
- __name__ = "MegaRapidCz"
- __type__ = "account"
- __version__ = "0.35"
-
- __description__ = """MegaRapid.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("MikyWoW", "mikywow@seznam.cz"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- login_timeout = 60
-
- LIMITDL_PATTERN = ur'<td>Max. počet paralelních stahování: </td><td>(\d+)'
- VALID_UNTIL_PATTERN = ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>'
- TRAFFIC_LEFT_PATTERN = r'<tr><td>Kredit</td><td>(.*?) GiB'
-
-
- def loadAccountInfo(self, user, req):
- htmll = req.load("http://megarapid.cz/mujucet/", decode=True)
-
- m = re.search(self.LIMITDL_PATTERN, htmll)
- if m:
- data = self.getAccountData(user)
- data['options']['limitDL'] = [int(m.group(1))]
-
- m = re.search(self.VALID_UNTIL_PATTERN, htmll)
- if m:
- validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y - %H:%M"))
- return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, htmll)
- if m:
- trafficleft = float(m.group(1)) * (1 << 20)
- return {"premium": True, "trafficleft": trafficleft, "validuntil": -1}
-
- return {"premium": False, "trafficleft": None, "validuntil": None}
-
-
- def login(self, user, data, req):
- html = req.load("http://megarapid.cz/prihlaseni/", decode=True)
-
- if "Heslo:" in html:
- start = html.index('id="inp_hash" name="hash" value="')
- html = html[start + 33:]
- hashes = html[0:32]
- html = req.load("http://megarapid.cz/prihlaseni/",
- post={"hash": hashes,
- "login": user,
- "pass1": data['password'],
- "remember": 0,
- "sbmt": u"Přihlásit"})
diff --git a/module/plugins/accounts/MegaRapidoNet.py b/module/plugins/accounts/MegaRapidoNet.py
deleted file mode 100644
index d061d02bc..000000000
--- a/module/plugins/accounts/MegaRapidoNet.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegaRapidoNet(Account):
- __name__ = "MegaRapidoNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """MegaRapido.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- VALID_UNTIL_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?[\'"]premium_index[\'"].*?>[^<]*?<[^>]*?b.*?>\s*?TEMPO\s*?PREMIUM.*?<[^>]*?/b.*?>\s*?(\d*)[^\d]*?DIAS[^\d]*?(\d*)[^\d]*?HORAS[^\d]*?(\d*)[^\d]*?MINUTOS[^\d]*?(\d*)[^\d]*?SEGUNDOS'
- USER_ID_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']checkbox_compartilhar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']usar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']user["\'][^>]*?value\s*?=\s*?["\'](.*?)\s*?["\']'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
-
- html = req.load("http://megarapido.net/gerador", decode=True)
-
- validuntil = re.search(self.VALID_UNTIL_PATTERN, html)
- if validuntil:
- #hier weitermachen!!! (mÌssen umbedingt die zeit richtig machen damit! (sollte aber möglich))
- validuntil = time.time() + int(validuntil.group(1)) * 24 * 3600 + int(validuntil.group(2)) * 3600 + int(validuntil.group(3)) * 60 + int(validuntil.group(4))
- trafficleft = -1
- premium = True
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- req.load("http://megarapido.net/login")
- req.load("http://megarapido.net/painel_user/ajax/logar.php",
- post={'login': user, 'senha': data['password']},
- decode=True)
-
- html = req.load("http://megarapido.net/gerador")
-
- if "sair" not in html.lower():
- self.wrongPassword()
- else:
- m = re.search(self.USER_ID_PATTERN, html)
- if m:
- data['uid'] = m.group(1)
- else:
- self.fail("Couldn't find the user ID")
diff --git a/module/plugins/accounts/MegasharesCom.py b/module/plugins/accounts/MegasharesCom.py
deleted file mode 100644
index 3d7ddbe46..000000000
--- a/module/plugins/accounts/MegasharesCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegasharesCom(Account):
- __name__ = "MegasharesCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Megashares.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- VALID_UNTIL_PATTERN = r'<p class="premium_info_box">Period Ends: (\w{3} \d{1,2}, \d{4})</p>'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://d01.megashares.com/myms.php", decode=True)
-
- premium = False if '>Premium Upgrade<' in html else True
-
- validuntil = trafficleft = -1
- try:
- timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug(timestr)
- validuntil = time.mktime(time.strptime(timestr, "%b %d, %Y"))
- except Exception, e:
- self.logError(e)
-
- return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load('http://d01.megashares.com/myms_login.php',
- post={"httpref" : "",
- "myms_login" : "Login",
- "mymslogin_name": user,
- "mymspassword" : data['password']},
- decode=True)
-
- if not '<span class="b ml">%s</span>' % user in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MovReelCom.py b/module/plugins/accounts/MovReelCom.py
deleted file mode 100644
index 6128cddc8..000000000
--- a/module/plugins/accounts/MovReelCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class MovReelCom(XFSAccount):
- __name__ = "MovReelCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Movreel.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
- login_timeout = 60
- info_threshold = 30
-
- HOSTER_DOMAIN = "movreel.com"
diff --git a/module/plugins/accounts/MultihostersCom.py b/module/plugins/accounts/MultihostersCom.py
deleted file mode 100644
index e98f17b2d..000000000
--- a/module/plugins/accounts/MultihostersCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.accounts.ZeveraCom import ZeveraCom
-
-
-class MultihostersCom(ZeveraCom):
- __name__ = "MultihostersCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Multihosters.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
-
-
- HOSTER_DOMAIN = "multihosters.com"
diff --git a/module/plugins/accounts/MultishareCz.py b/module/plugins/accounts/MultishareCz.py
deleted file mode 100644
index 3488e3288..000000000
--- a/module/plugins/accounts/MultishareCz.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class MultishareCz(Account):
- __name__ = "MultishareCz"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Multishare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
- ACCOUNT_INFO_PATTERN = r'<input type="hidden" id="(u_ID|u_hash)" name=".+?" value="(.+?)">'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://www.multishare.cz/profil/", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- trafficleft = self.parseTraffic(m.group('S') + m.group('U')) if m else 0
- self.premium = True if trafficleft else False
-
- html = req.load("http://www.multishare.cz/", decode=True)
- mms_info = dict(re.findall(self.ACCOUNT_INFO_PATTERN, html))
-
- return dict(mms_info, **{"validuntil": -1, "trafficleft": trafficleft})
-
-
- def login(self, user, data, req):
- html = req.load('http://www.multishare.cz/html/prihlaseni_process.php',
- post={"akce" : "Přihlásit",
- "heslo": data['password'],
- "jmeno": user},
- decode=True)
-
- if '<div class="akce-chyba akce">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MyfastfileCom.py b/module/plugins/accounts/MyfastfileCom.py
deleted file mode 100644
index 9a13e2e42..000000000
--- a/module/plugins/accounts/MyfastfileCom.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class MyfastfileCom(Account):
- __name__ = "MyfastfileCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Myfastfile.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- if 'days_left' in self.json_data:
- validuntil = time.time() + self.json_data['days_left'] * 24 * 60 * 60
- return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
- else:
- self.logError(_("Unable to get account information"))
-
-
- def login(self, user, data, req):
- # Password to use is the API-Password written in http://myfastfile.com/myaccount
- html = req.load("http://myfastfile.com/api.php",
- get={"user": user, "pass": data['password']})
-
- self.logDebug("JSON data: " + html)
-
- self.json_data = json_loads(html)
- if self.json_data['status'] != 'ok':
- self.logError(_('Invalid login. The password to use is the API-Password you find in your "My Account" page'))
- self.wrongPassword()
diff --git a/module/plugins/accounts/NetloadIn.py b/module/plugins/accounts/NetloadIn.py
deleted file mode 100644
index 066174a28..000000000
--- a/module/plugins/accounts/NetloadIn.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class NetloadIn(Account):
- __name__ = "NetloadIn"
- __type__ = "account"
- __version__ = "0.24"
-
- __description__ = """Netload.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, id, password, req):
- return req.load("http://api.netload.in/user_info.php",
- get={'auth' : "BVm96BWDSoB4WkfbEhn42HgnjIe1ilMt",
- 'user_id' : id,
- 'user_password': password}).strip()
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = self.api_response(user, self.getAccountData(user)['password'], req)
-
- if html == "-1":
- premium = True
-
- elif html == "0":
- validuntil = -1
-
- else:
- try:
- validuntil = time.mktime(time.strptime(html, "%Y-%m-%d %H:%M"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- self.logDebug("Valid until: %s" % validuntil)
-
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- validuntil = -1
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- html = self.api_response(user, data['password'], req)
-
- if not html or re.search(r'disallowed_agent|unknown_auth|login_failed', html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/NoPremiumPl.py b/module/plugins/accounts/NoPremiumPl.py
deleted file mode 100644
index 7e3f757d3..000000000
--- a/module/plugins/accounts/NoPremiumPl.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import hashlib
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads as loads
-
-
-class NoPremiumPl(Account):
- __name__ = "NoPremiumPl"
- __version__ = "0.01"
- __type__ = "account"
- __description__ = "NoPremium.pl account plugin"
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
- _api_url = "http://crypt.nopremium.pl"
-
- _api_query = {
- "site": "nopremium",
- "username": "",
- "password": "",
- "output": "json",
- "loc": "1",
- "info": "1"
- }
-
- _req = None
- _usr = None
- _pwd = None
-
- def loadAccountInfo(self, name, req):
- self._req = req
- try:
- result = loads(self.runAuthQuery())
- except Exception:
- # todo: return or let it be thrown?
- return
-
- premium = False
- valid_untill = -1
-
- if "expire" in result.keys() and result["expire"]:
- premium = True
- valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result["expire"])).timetuple())
- traffic_left = result["balance"] * 1024
-
- return ({
- "validuntil": valid_untill,
- "trafficleft": traffic_left,
- "premium": premium
- })
-
- def login(self, user, data, req):
- self._usr = user
- self._pwd = hashlib.sha1(hashlib.md5(data["password"]).hexdigest()).hexdigest()
- self._req = req
-
- try:
- response = loads(self.runAuthQuery())
- except Exception:
- self.wrongPassword()
-
- if "errno" in response.keys():
- self.wrongPassword()
- data['usr'] = self._usr
- data['pwd'] = self._pwd
-
- def createAuthQuery(self):
- query = self._api_query
- query["username"] = self._usr
- query["password"] = self._pwd
-
- return query
-
- def runAuthQuery(self):
- data = self._req.load(self._api_url, post=self.createAuthQuery())
-
- return data \ No newline at end of file
diff --git a/module/plugins/accounts/NosuploadCom.py b/module/plugins/accounts/NosuploadCom.py
deleted file mode 100644
index e523ee2f4..000000000
--- a/module/plugins/accounts/NosuploadCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class NosuploadCom(XFSAccount):
- __name__ = "NosuploadCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Nosupload.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "nosupload.com"
diff --git a/module/plugins/accounts/NovafileCom.py b/module/plugins/accounts/NovafileCom.py
deleted file mode 100644
index ab61bf0fc..000000000
--- a/module/plugins/accounts/NovafileCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class NovafileCom(XFSAccount):
- __name__ = "NovafileCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Novafile.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "novafile.com"
diff --git a/module/plugins/accounts/NowVideoSx.py b/module/plugins/accounts/NowVideoSx.py
deleted file mode 100644
index 2f7b033bd..000000000
--- a/module/plugins/accounts/NowVideoSx.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class NowVideoSx(Account):
- __name__ = "NowVideoSx"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """NowVideo.at account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'>Your premium membership expires on: (.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = None
-
- html = req.load("http://www.nowvideo.sx/premium.php")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%b-%d"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- premium = False
- validuntil = -1
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load("http://www.nowvideo.sx/login.php",
- post={'user': user, 'pass': data['password']},
- decode=True)
-
- if re.search(r'>Log In<', html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/OboomCom.py b/module/plugins/accounts/OboomCom.py
deleted file mode 100644
index 020a45ba7..000000000
--- a/module/plugins/accounts/OboomCom.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-try:
- from beaker.crypto.pbkdf2 import PBKDF2
-
-except ImportError:
- from beaker.crypto.pbkdf2 import pbkdf2
- from binascii import b2a_hex
-
- class PBKDF2(object):
- def __init__(self, passphrase, salt, iterations=1000):
- self.passphrase = passphrase
- self.salt = salt
- self.iterations = iterations
-
- def hexread(self, octets):
- return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class OboomCom(Account):
- __name__ = "OboomCom"
- __type__ = "account"
- __version__ = "0.24"
-
- __description__ = """Oboom.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stanley", "stanley.foerster@gmail.com")]
-
-
- def loadAccountData(self, user, req):
- passwd = self.getAccountData(user)['password']
- salt = passwd[::-1]
- pbkdf2 = PBKDF2(passwd, salt, 1000).hexread(16)
-
- result = json_loads(req.load("https://www.oboom.com/1/login", get={"auth": user, "pass": pbkdf2}))
-
- if not result[0] == 200:
- self.logWarning(_("Failed to log in: %s") % result[1])
- self.wrongPassword()
-
- return result[1]
-
-
- def loadAccountInfo(self, name, req):
- accountData = self.loadAccountData(name, req)
-
- userData = accountData['user']
-
- if userData['premium'] == "null":
- premium = False
- else:
- premium = True
-
- if userData['premium_unix'] == "null":
- validUntil = -1
- else:
- validUntil = float(userData['premium_unix'])
-
- traffic = userData['traffic']
-
- trafficLeft = traffic['current'] / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- maxTraffic = traffic['max'] / 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- session = accountData['session']
-
- return {'premium' : premium,
- 'validuntil' : validUntil,
- 'trafficleft': trafficLeft,
- 'maxtraffic' : maxTraffic,
- 'session' : session}
-
-
- def login(self, user, data, req):
- self.loadAccountData(user, req)
diff --git a/module/plugins/accounts/OneFichierCom.py b/module/plugins/accounts/OneFichierCom.py
deleted file mode 100644
index b19e2bc69..000000000
--- a/module/plugins/accounts/OneFichierCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class OneFichierCom(Account):
- __name__ = "OneFichierCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """1fichier.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'Your Premium Status will end the (\d+/\d+/\d+)'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = None
-
- html = req.load("https://1fichier.com/console/abo.pl")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1)
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%d/%m/%Y"))
- except Exception, e:
- self.logError(e)
- else:
- premium = True
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium or False}
-
-
- def login(self, user, data, req):
- req.http.c.setopt(pycurl.REFERER, "https://1fichier.com/login.pl?lg=en")
-
- html = req.load("https://1fichier.com/login.pl?lg=en",
- post={'mail' : user,
- 'pass' : data['password'],
- 'It' : "on",
- 'purge' : "off",
- 'valider': "Send"},
- decode=True)
-
- if '>Invalid email address' in html or '>Invalid password' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/OverLoadMe.py b/module/plugins/accounts/OverLoadMe.py
deleted file mode 100644
index 64d04aded..000000000
--- a/module/plugins/accounts/OverLoadMe.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class OverLoadMe(Account):
- __name__ = "OverLoadMe"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Over-Load.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def loadAccountInfo(self, user, req):
- https = "https" if self.getConfig('ssl') else "http"
- data = self.getAccountData(user)
- html = req.load(https + "://api.over-load.me/account.php",
- get={'user': user,
- 'auth': data['password']}).strip()
-
- data = json_loads(html)
- self.logDebug(data)
-
- # Check for premium
- if data['membership'] == "Free":
- return {'premium': False, 'validuntil': None, 'trafficleft': None}
- else:
- return {'premium': True, 'validuntil': data['expirationunix'], 'trafficleft': -1}
-
-
- def login(self, user, data, req):
- https = "https" if self.getConfig('ssl') else "http"
- jsondata = req.load(https + "://api.over-load.me/account.php",
- get={'user': user,
- 'auth': data['password']}).strip()
-
- data = json_loads(jsondata)
-
- if data['err'] == 1:
- self.wrongPassword()
diff --git a/module/plugins/accounts/PremiumTo.py b/module/plugins/accounts/PremiumTo.py
deleted file mode 100644
index c8ea2fa26..000000000
--- a/module/plugins/accounts/PremiumTo.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class PremiumTo(Account):
- __name__ = "PremiumTo"
- __type__ = "account"
- __version__ = "0.08"
-
- __description__ = """Premium.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- traffic = req.load("http://premium.to/api/straffic.php",
- get={'username': self.username, 'password': self.password})
-
- if "wrong username" not in traffic:
- trafficleft = sum(map(float, traffic.split(';'))) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- return {'premium': True, 'trafficleft': trafficleft, 'validuntil': -1}
- else:
- return {'premium': False, 'trafficleft': None, 'validuntil': None}
-
-
- def login(self, user, data, req):
- self.username = user
- self.password = data['password']
- authcode = req.load("http://premium.to/api/getauthcode.php",
- get={'username': user, 'password': self.password},
- decode=True)
-
- if "wrong username" in authcode:
- self.wrongPassword()
diff --git a/module/plugins/accounts/PremiumizeMe.py b/module/plugins/accounts/PremiumizeMe.py
deleted file mode 100644
index 7d061ec2d..000000000
--- a/module/plugins/accounts/PremiumizeMe.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-from module.common.json_layer import json_loads
-
-
-class PremiumizeMe(Account):
- __name__ = "PremiumizeMe"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Premiumize.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
- self.logDebug(status)
-
- # Parse account info
- account_info = {"validuntil": float(status['result']['expires']),
- "trafficleft": max(0, status['result']['trafficleft_bytes'] / 1024)} #@TODO: Remove `/ 1024` in 0.4.10
-
- if status['result']['type'] == 'free':
- account_info['premium'] = False
-
- return account_info
-
-
- def login(self, user, data, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
-
- # Check if user and password are valid
- if status['status'] != 200:
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- # Use premiumize.me API v1 (see https://secure.premiumize.me/?show=api)
- # to retrieve account info and return the parsed json answer
- answer = req.load("https://api.premiumize.me/pm-api/v1.php",
- get={'method' : "accountstatus",
- 'params[login]': user,
- 'params[pass]' : self.getAccountData(user)['password']})
- return json_loads(answer)
diff --git a/module/plugins/accounts/PutdriveCom.py b/module/plugins/accounts/PutdriveCom.py
deleted file mode 100644
index 4f2fadbcc..000000000
--- a/module/plugins/accounts/PutdriveCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.accounts.ZeveraCom import ZeveraCom
-
-
-class PutdriveCom(ZeveraCom):
- __name__ = "PutdriveCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Putdrive.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "putdrive.com"
diff --git a/module/plugins/accounts/QuickshareCz.py b/module/plugins/accounts/QuickshareCz.py
deleted file mode 100644
index 16141d63e..000000000
--- a/module/plugins/accounts/QuickshareCz.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class QuickshareCz(Account):
- __name__ = "QuickshareCz"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Quickshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- TRAFFIC_LEFT_PATTERN = r'Stav kreditu: <strong>(.+?)</strong>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.quickshare.cz/premium", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
- premium = True if trafficleft else False
- else:
- trafficleft = None
- premium = False
-
- return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load('http://www.quickshare.cz/html/prihlaseni_process.php',
- post={"akce": u'Přihlásit',
- "heslo": data['password'],
- "jmeno": user},
- decode=True)
-
- if u'>TakovÜ uşivatel neexistuje.<' in html or u'>Špatné heslo.<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/RPNetBiz.py b/module/plugins/accounts/RPNetBiz.py
deleted file mode 100644
index 829e54a46..000000000
--- a/module/plugins/accounts/RPNetBiz.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RPNetBiz(Account):
- __name__ = "RPNetBiz"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """RPNet.biz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- # Get account information from rpnet.biz
- res = self.getAccountStatus(user, req)
- try:
- if res['accountInfo']['isPremium']:
- # Parse account info. Change the trafficleft later to support per host info.
- account_info = {"validuntil": float(res['accountInfo']['premiumExpiry']),
- "trafficleft": -1, "premium": True}
- else:
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
-
- except KeyError:
- #handle wrong password exception
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
-
- return account_info
-
-
- def login(self, user, data, req):
- # Get account information from rpnet.biz
- res = self.getAccountStatus(user, req)
-
- # If we have an error in the res, we have wrong login information
- if 'error' in res:
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- # Using the rpnet API, check if valid premium account
- res = req.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": self.getAccountData(user)['password'],
- "action": "showAccountInformation"})
- self.logDebug("JSON data: %s" % res)
-
- return json_loads(res)
diff --git a/module/plugins/accounts/RapideoPl.py b/module/plugins/accounts/RapideoPl.py
deleted file mode 100644
index 3e9d52fe8..000000000
--- a/module/plugins/accounts/RapideoPl.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import hashlib
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads as loads
-
-
-class RapideoPl(Account):
- __name__ = "RapideoPl"
- __version__ = "0.01"
- __type__ = "account"
- __description__ = "Rapideo.pl account plugin"
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
- _api_url = "http://enc.rapideo.pl"
-
- _api_query = {
- "site": "newrd",
- "username": "",
- "password": "",
- "output": "json",
- "loc": "1",
- "info": "1"
- }
-
- _req = None
- _usr = None
- _pwd = None
-
- def loadAccountInfo(self, name, req):
- self._req = req
- try:
- result = loads(self.runAuthQuery())
- except Exception:
- # todo: return or let it be thrown?
- return
-
- premium = False
- valid_untill = -1
- if "expire" in result.keys() and result["expire"]:
- premium = True
- valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result["expire"])).timetuple())
-
- traffic_left = result["balance"]
-
- return ({
- "validuntil": valid_untill,
- "trafficleft": traffic_left,
- "premium": premium
- })
-
- def login(self, user, data, req):
- self._usr = user
- self._pwd = hashlib.md5(data["password"]).hexdigest()
- self._req = req
- try:
- response = loads(self.runAuthQuery())
- except Exception:
- self.wrongPassword()
-
- if "errno" in response.keys():
- self.wrongPassword()
- data['usr'] = self._usr
- data['pwd'] = self._pwd
-
- def createAuthQuery(self):
- query = self._api_query
- query["username"] = self._usr
- query["password"] = self._pwd
-
- return query
-
- def runAuthQuery(self):
- data = self._req.load(self._api_url, post=self.createAuthQuery())
-
- return data \ No newline at end of file
diff --git a/module/plugins/accounts/RapidfileshareNet.py b/module/plugins/accounts/RapidfileshareNet.py
deleted file mode 100644
index c0dd7eaee..000000000
--- a/module/plugins/accounts/RapidfileshareNet.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RapidfileshareNet(XFSAccount):
- __name__ = "RapidfileshareNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Rapidfileshare.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "rapidfileshare.net"
-
- TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><label for="name">\s*(?P<S>[\d.,]+)\s*(?:(?P<U>[\w^_]+))?'
diff --git a/module/plugins/accounts/RapidgatorNet.py b/module/plugins/accounts/RapidgatorNet.py
deleted file mode 100644
index 23d7aff53..000000000
--- a/module/plugins/accounts/RapidgatorNet.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RapidgatorNet(Account):
- __name__ = "RapidgatorNet"
- __type__ = "account"
- __version__ = "0.09"
-
- __description__ = """Rapidgator.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- API_URL = "http://rapidgator.net/api/user"
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
- sid = None
-
- try:
- sid = self.getAccountData(user).get('sid')
- assert sid
-
- html = req.load("%s/info" % self.API_URL, get={'sid': sid})
-
- self.logDebug("API:USERINFO", html)
-
- json = json_loads(html)
-
- if json['response_status'] == 200:
- if "reset_in" in json['response']:
- self.scheduleRefresh(user, json['response']['reset_in'])
-
- validuntil = json['response']['expire_date']
- trafficleft = float(json['response']['traffic_left']) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- premium = True
- else:
- self.logError(json['response_details'])
-
- except Exception, e:
- self.logError(e)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium,
- 'sid' : sid}
-
-
- def login(self, user, data, req):
- try:
- html = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
-
- self.logDebug("API:LOGIN", html)
-
- json = json_loads(html)
-
- if json['response_status'] == 200:
- data['sid'] = str(json['response']['session_id'])
- return
- else:
- self.logError(json['response_details'])
-
- except Exception, e:
- self.logError(e)
-
- self.wrongPassword()
diff --git a/module/plugins/accounts/RapiduNet.py b/module/plugins/accounts/RapiduNet.py
deleted file mode 100644
index 70f47b673..000000000
--- a/module/plugins/accounts/RapiduNet.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RapiduNet(Account):
- __name__ = "RapiduNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Rapidu.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'>Account: <b>Premium'
-
- VALID_UNTIL_PATTERN = r'>Account: <b>\w+ \((\d+)'
-
- TRAFFIC_LEFT_PATTERN = r'class="tipsyS"><b>(.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = req.load("https://rapidu.net/", decode=True)
-
- if re.search(self.PREMIUM_PATTERN, html):
- premium = True
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- validuntil = time.time() + (86400 * int(m.group(1)))
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- req.load("https://rapidu.net/ajax.php",
- get={'a': "getChangeLang"},
- post={'_go' : "",
- 'lang': "en"})
-
- json = json_loads(req.load("https://rapidu.net/ajax.php",
- get={'a': "getUserLogin"},
- post={'_go' : "",
- 'login' : user,
- 'pass' : data['password'],
- 'remember': "1"}))
-
- self.logDebug(json)
-
- if not json['message'] == "success":
- self.wrongPassword()
diff --git a/module/plugins/accounts/RarefileNet.py b/module/plugins/accounts/RarefileNet.py
deleted file mode 100644
index 577a6c8f6..000000000
--- a/module/plugins/accounts/RarefileNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RarefileNet(XFSAccount):
- __name__ = "RarefileNet"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """RareFile.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "rarefile.net"
diff --git a/module/plugins/accounts/RealdebridCom.py b/module/plugins/accounts/RealdebridCom.py
deleted file mode 100644
index 41d8a0975..000000000
--- a/module/plugins/accounts/RealdebridCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import xml.dom.minidom as dom
-
-from module.plugins.Account import Account
-
-
-class RealdebridCom(Account):
- __name__ = "RealdebridCom"
- __type__ = "account"
- __version__ = "0.45"
-
- __description__ = """Real-Debrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def loadAccountInfo(self, user, req):
- if self.pin_code:
- return {"premium": False}
- html = req.load("https://real-debrid.com/api/account.php")
- xml = dom.parseString(html)
- account_info = {"validuntil": float(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
- "trafficleft": -1}
-
- return account_info
-
-
- def login(self, user, data, req):
- self.pin_code = False
- html = req.load("https://real-debrid.com/ajax/login.php",
- get={"user": user, "pass": data['password']},
- decode=True)
-
- if "Your login informations are incorrect" in html:
- self.wrongPassword()
-
- elif "PIN Code required" in html:
- self.logWarning(_("PIN code required. Please login to https://real-debrid.com using the PIN or disable the double authentication in your control panel on https://real-debrid.com"))
- self.pin_code = True
diff --git a/module/plugins/accounts/RehostTo.py b/module/plugins/accounts/RehostTo.py
deleted file mode 100644
index 04e71c9ad..000000000
--- a/module/plugins/accounts/RehostTo.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class RehostTo(Account):
- __name__ = "RehostTo"
- __type__ = "account"
- __version__ = "0.16"
-
- __description__ = """Rehost.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- trafficleft = None
- validuntil = -1
- session = ""
-
- html = req.load("http://rehost.to/api.php",
- get={'cmd' : "login", 'user': user,
- 'pass': self.getAccountData(user)['password']})
- try:
- session = html.split(",")[1].split("=")[1]
-
- html = req.load("http://rehost.to/api.php",
- get={'cmd': "get_premium_credits", 'long_ses': session})
-
- if html.strip() == "0,0" or "ERROR" in html:
- self.logDebug(html)
- else:
- traffic, valid = html.split(",")
-
- premium = True
- trafficleft = self.parseTraffic(traffic + "MB")
- validuntil = float(valid)
-
- finally:
- return {'premium' : premium,
- 'trafficleft': trafficleft,
- 'validuntil' : validuntil,
- 'session' : session}
-
-
- def login(self, user, data, req):
- html = req.load("http://rehost.to/api.php",
- get={'cmd': "login", 'user': user, 'pass': data['password']},
- decode=True)
-
- if "ERROR" in html:
- self.logDebug(html)
- self.wrongPassword()
diff --git a/module/plugins/accounts/RyushareCom.py b/module/plugins/accounts/RyushareCom.py
deleted file mode 100644
index 8c56ff20f..000000000
--- a/module/plugins/accounts/RyushareCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RyushareCom(XFSAccount):
- __name__ = "RyushareCom"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """Ryushare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "ryushare.com"
diff --git a/module/plugins/accounts/SafesharingEu.py b/module/plugins/accounts/SafesharingEu.py
deleted file mode 100644
index 2e58d33b3..000000000
--- a/module/plugins/accounts/SafesharingEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SafesharingEu(XFSAccount):
- __name__ = "SafesharingEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Safesharing.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "safesharing.eu"
diff --git a/module/plugins/accounts/SecureUploadEu.py b/module/plugins/accounts/SecureUploadEu.py
deleted file mode 100644
index b335c94da..000000000
--- a/module/plugins/accounts/SecureUploadEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SecureUploadEu(XFSAccount):
- __name__ = "SecureUploadEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """SecureUpload.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "secureupload.eu"
diff --git a/module/plugins/accounts/SendmywayCom.py b/module/plugins/accounts/SendmywayCom.py
deleted file mode 100644
index 4fcbe0b7a..000000000
--- a/module/plugins/accounts/SendmywayCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SendmywayCom(XFSAccount):
- __name__ = "SendmywayCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Sendmyway.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "sendmyway.com"
diff --git a/module/plugins/accounts/ShareonlineBiz.py b/module/plugins/accounts/ShareonlineBiz.py
deleted file mode 100644
index 7e05e2e76..000000000
--- a/module/plugins/accounts/ShareonlineBiz.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class ShareonlineBiz(Account):
- __name__ = "ShareonlineBiz"
- __type__ = "account"
- __version__ = "0.31"
-
- __description__ = """Share-online.biz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, user, req):
- return req.load("http://api.share-online.biz/cgi-bin",
- get={'q' : "userdetails",
- 'aux' : "traffic",
- 'username': user,
- 'password': self.getAccountData(user)['password']})
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = -1
- maxtraffic = 100 * 1024 * 1024 * 1024 #: 100 GB
-
- api = {}
- for line in self.api_response(user, req).splitlines():
- if "=" in line:
- key, value = line.split("=")
- api[key] = value
-
- self.logDebug(api)
-
- if api['a'].lower() != "not_available":
- req.cj.setCookie("share-online.biz", 'a', api['a'])
-
- premium = api['group'] in ("Premium", "PrePaid")
-
- validuntil = float(api['expire_date'])
-
- traffic = float(api['traffic_1d'].split(";")[0])
- maxtraffic = max(maxtraffic, traffic)
- trafficleft = maxtraffic - traffic
-
- maxtraffic /= 1024 #@TODO: Remove `/ 1024` in 0.4.10
- trafficleft /= 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- return {'premium' : premium,
- 'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'maxtraffic' : maxtraffic}
-
-
- def login(self, user, data, req):
- html = self.api_response(user, req)
- err = re.search(r'\*\*(.+?)\*\*', html)
- if err:
- self.logError(err.group(1))
- self.wrongPassword()
diff --git a/module/plugins/accounts/SimplyPremiumCom.py b/module/plugins/accounts/SimplyPremiumCom.py
deleted file mode 100644
index 8caf600f9..000000000
--- a/module/plugins/accounts/SimplyPremiumCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class SimplyPremiumCom(Account):
- __name__ = "SimplyPremiumCom"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Simply-Premium.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = -1
- trafficleft = None
-
- json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
-
- self.logDebug("JSON data: %s" % json_data)
-
- json_data = json_loads(json_data)
-
- if 'vip' in json_data['result'] and json_data['result']['vip']:
- premium = True
-
- if 'timeend' in json_data['result'] and json_data['result']['timeend']:
- validuntil = float(json_data['result']['timeend'])
-
- if 'remain_traffic' in json_data['result'] and json_data['result']['remain_traffic']:
- trafficleft = float(json_data['result']['remain_traffic']) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("simply-premium.com", "lang", "EN")
-
- html = req.load("http://www.simply-premium.com/login.php",
- post={'key': user} if not data['password'] else {'login_name': user, 'login_pass': data['password']},
- decode=True)
-
- if 'logout' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/SimplydebridCom.py b/module/plugins/accounts/SimplydebridCom.py
deleted file mode 100644
index 24108eb0b..000000000
--- a/module/plugins/accounts/SimplydebridCom.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-
-
-class SimplydebridCom(Account):
- __name__ = "SimplydebridCom"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Simply-Debrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def loadAccountInfo(self, user, req):
- get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
- res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- data = [x.strip() for x in res.split(";")]
- if str(data[0]) != "1":
- return {"premium": False}
- else:
- return {"trafficleft": -1, "validuntil": time.mktime(time.strptime(str(data[2]), "%d/%m/%Y"))}
-
-
- def login(self, user, data, req):
- self.loginname = user
- self.password = data['password']
- get_data = {'login': 1, 'u': self.loginname, 'p': self.password}
-
- res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- if res != "02: loggin success":
- self.wrongPassword()
diff --git a/module/plugins/accounts/SmoozedCom.py b/module/plugins/accounts/SmoozedCom.py
deleted file mode 100644
index 56cd1864a..000000000
--- a/module/plugins/accounts/SmoozedCom.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-import time
-
-try:
- from beaker.crypto.pbkdf2 import PBKDF2
-
-except ImportError:
- from beaker.crypto.pbkdf2 import pbkdf2
- from binascii import b2a_hex
-
- class PBKDF2(object):
- def __init__(self, passphrase, salt, iterations=1000):
- self.passphrase = passphrase
- self.salt = salt
- self.iterations = iterations
-
- def hexread(self, octets):
- return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class SmoozedCom(Account):
- __name__ = "SmoozedCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Smoozed.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def loadAccountInfo(self, user, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
-
- self.logDebug(status)
-
- if status['state'] != 'ok':
- info = {'validuntil' : None,
- 'trafficleft': None,
- 'premium' : False}
- else:
- # Parse account info
- info = {'validuntil' : float(status["data"]["user"]["user_premium"]),
- 'trafficleft': max(0, status["data"]["traffic"][1] - status["data"]["traffic"][0]),
- 'session' : status["data"]["session_key"],
- 'hosters' : [hoster["name"] for hoster in status["data"]["hoster"]]}
-
- if info['validuntil'] < time.time():
- info['premium'] = False
- else:
- info['premium'] = True
-
- return info
-
-
- def login(self, user, data, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
-
- # Check if user and password are valid
- if status['state'] != 'ok':
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- password = self.getAccountData(user)['password']
- salt = hashlib.sha256(password).hexdigest()
- encrypted = PBKDF2(password, salt, iterations=1000).hexread(32)
-
- return json_loads(req.load("http://www2.smoozed.com/api/login",
- get={'auth': user, 'password': encrypted}))
diff --git a/module/plugins/accounts/StahnuTo.py b/module/plugins/accounts/StahnuTo.py
deleted file mode 100644
index 882dbd2c3..000000000
--- a/module/plugins/accounts/StahnuTo.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class StahnuTo(Account):
- __name__ = "StahnuTo"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """StahnuTo account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.stahnu.to/")
-
- m = re.search(r'>VIP: (\d+.*)<', html)
- trafficleft = self.parseTraffic(m.group(1)) if m else 0
-
- return {"premium": trafficleft > 512, "trafficleft": trafficleft, "validuntil": -1}
-
-
- def login(self, user, data, req):
- html = req.load("http://www.stahnu.to/login.php",
- post={"username": user,
- "password": data['password'],
- "submit": "Login"},
- decode=True)
-
- if not '<a href="logout.php">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/StreamcloudEu.py b/module/plugins/accounts/StreamcloudEu.py
deleted file mode 100644
index aa1eafcbd..000000000
--- a/module/plugins/accounts/StreamcloudEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class StreamcloudEu(XFSAccount):
- __name__ = "StreamcloudEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Streamcloud.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "streamcloud.eu"
diff --git a/module/plugins/accounts/TurbobitNet.py b/module/plugins/accounts/TurbobitNet.py
deleted file mode 100644
index 010fbc270..000000000
--- a/module/plugins/accounts/TurbobitNet.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class TurbobitNet(Account):
- __name__ = "TurbobitNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """TurbobitNet account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://turbobit.net")
-
- m = re.search(r'<u>Turbo Access</u> to ([\d.]+)', html)
- if m:
- premium = True
- validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("turbobit.net", "user_lang", "en")
-
- html = req.load("http://turbobit.net/user/login",
- post={"user[login]": user,
- "user[pass]": data['password'],
- "user[submit]": "Login"},
- decode=True)
-
- if not '<div class="menu-item user-name">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/TusfilesNet.py b/module/plugins/accounts/TusfilesNet.py
deleted file mode 100644
index d06ba0583..000000000
--- a/module/plugins/accounts/TusfilesNet.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class TusfilesNet(XFSAccount):
- __name__ = "TusfilesNet"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """ Tusfile.net account plugin """
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "tusfiles.net"
-
- VALID_UNTIL_PATTERN = r'<span class="label label-default">([^<]+)</span>'
- TRAFFIC_LEFT_PATTERN = r'<td><img src="//www\.tusfiles\.net/i/icon/meter\.png" alt=""/></td>\n<td>&nbsp;(?P<S>[\d.,]+)'
diff --git a/module/plugins/accounts/UlozTo.py b/module/plugins/accounts/UlozTo.py
deleted file mode 100644
index f95c8834b..000000000
--- a/module/plugins/accounts/UlozTo.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.Account import Account
-
-
-class UlozTo(Account):
- __name__ = "UlozTo"
- __type__ = "account"
- __version__ = "0.10"
-
- __description__ = """Uloz.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("pulpe", None)]
-
-
- TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a .*?title=".+?GB = ([\d.]+) MB"'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.ulozto.net/", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
-
- trafficleft = float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048 if m else 0
- premium = True if trafficleft else False
-
- return {'validuntil': -1, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- login_page = req.load('http://www.ulozto.net/?do=web-login', decode=True)
- action = re.findall('<form action="(.+?)"', login_page)[1].replace('&amp;', '&')
- token = re.search('_token_" value="(.+?)"', login_page).group(1)
-
- html = req.load(urlparse.urljoin("http://www.ulozto.net/", action),
- post={'_token_' : token,
- 'do' : "loginForm-submit",
- 'login' : u"Přihlásit",
- 'password': data['password'],
- 'username': user,
- 'remember': "on"},
- decode=True)
-
- if '<div class="flash error">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UnrestrictLi.py b/module/plugins/accounts/UnrestrictLi.py
deleted file mode 100644
index 6a8187234..000000000
--- a/module/plugins/accounts/UnrestrictLi.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class UnrestrictLi(Account):
- __name__ = "UnrestrictLi"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Unrestrict.li account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json')
- self.logDebug("JSON data: " + json_data)
- json_data = json_loads(json_data)
-
- if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
- return {"premium": False}
-
- validuntil = json_data['result']['expires']
- trafficleft = float(json_data['result']['traffic'] / 1024) #@TODO: Remove `/ 1024` in 0.4.10
-
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("unrestrict.li", "lang", "EN")
- html = req.load("https://unrestrict.li/sign_in", decode=True)
-
- if 'solvemedia' in html:
- self.logError(_("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry"))
- return
-
- post_data = {"username": user, "password": data['password'],
- "remember_me": "remember", "signin": "Sign in"}
- html = req.load("https://unrestrict.li/sign_in", post=post_data, decode=True)
-
- if 'sign_out' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadableCh.py b/module/plugins/accounts/UploadableCh.py
deleted file mode 100644
index 86ae5dd17..000000000
--- a/module/plugins/accounts/UploadableCh.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class UploadableCh(Account):
- __name__ = "UploadableCh"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Uploadable.ch account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Sasch", "gsasch@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.uploadable.ch/login.php")
-
- premium = '<a href="/logout.php"' in html
- trafficleft = -1 if premium else None
-
- return {'validuntil': None, 'trafficleft': trafficleft, 'premium': premium} #@TODO: validuntil
-
-
- def login(self, user, data, req):
- html = req.load("http://www.uploadable.ch/login.php",
- post={'userName' : user,
- 'userPassword' : data["password"],
- 'autoLogin' : "1",
- 'action__login': "normalLogin"},
- decode=True)
-
- if "Login failed" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadcCom.py b/module/plugins/accounts/UploadcCom.py
deleted file mode 100644
index d1e1a2ead..000000000
--- a/module/plugins/accounts/UploadcCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class UploadcCom(XFSAccount):
- __name__ = "UploadcCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Uploadc.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "uploadc.com"
diff --git a/module/plugins/accounts/UploadedTo.py b/module/plugins/accounts/UploadedTo.py
deleted file mode 100644
index d1556b6db..000000000
--- a/module/plugins/accounts/UploadedTo.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class UploadedTo(Account):
- __name__ = "UploadedTo"
- __type__ = "account"
- __version__ = "0.30"
-
- __description__ = """Uploaded.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'<em>Premium</em>'
- VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>(.+?)<'
- TRAFFIC_LEFT_PATTERN = r'<b class="cB">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = None
-
- html = req.load("http://uploaded.net/me")
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html, re.M)
- if m:
- expiredate = m.group(1).lower().strip()
-
- if expiredate == "unlimited":
- validuntil = -1
- else:
- m = re.findall(r'(\d+) (week|day|hour)', expiredate)
- if m:
- validuntil = time.time()
- for n, u in m:
- validuntil += float(n) * 60 * 60 * {'week': 168, 'day': 24, 'hour': 1}[u]
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- traffic = m.groupdict()
- size = traffic['S'].replace('.', '')
- unit = traffic['U'].lower()
-
- if unit.startswith('t'): #@NOTE: Remove in 0.4.10
- trafficleft = float(size.replace(',', '.')) / 1024
- trafficleft *= 1 << 40
- else:
- trafficleft = self.parseTraffic(size + unit)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- # req.cj.setCookie("uploaded.net", "lang", "en")
-
- html = req.load("http://uploaded.net/io/login",
- post={'id': user, 'pw': data['password'], '_': ""},
- decode=True)
-
- if '"err"' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadheroCom.py b/module/plugins/accounts/UploadheroCom.py
deleted file mode 100644
index 714f5b0a6..000000000
--- a/module/plugins/accounts/UploadheroCom.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import datetime
-import time
-
-from module.plugins.Account import Account
-
-
-class UploadheroCom(Account):
- __name__ = "UploadheroCom"
- __type__ = "account"
- __version__ = "0.21"
-
- __description__ = """Uploadhero.co account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mcmyst", "mcmyst@hotmail.fr")]
-
-
- def loadAccountInfo(self, user, req):
- premium_pattern = re.compile('Il vous reste <span class="bleu">(\d+)</span> jours premium')
-
- data = self.getAccountData(user)
- html = req.load("http://uploadhero.co/my-account")
-
- if premium_pattern.search(html):
- end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(html).group(1)))
- end_date = time.mktime(future.timetuple())
- account_info = {"validuntil": end_date, "trafficleft": -1, "premium": True}
- else:
- account_info = {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- return account_info
-
-
- def login(self, user, data, req):
- html = req.load("http://uploadhero.co/lib/connexion.php",
- post={"pseudo_login": user, "password_login": data['password']},
- decode=True)
-
- if "mot de passe invalide" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadingCom.py b/module/plugins/accounts/UploadingCom.py
deleted file mode 100644
index a20c44535..000000000
--- a/module/plugins/accounts/UploadingCom.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import set_cookies
-
-
-class UploadingCom(Account):
- __name__ = "UploadingCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """Uploading.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- PREMIUM_PATTERN = r'UPGRADE TO PREMIUM'
- VALID_UNTIL_PATTERN = r'Valid Until:(.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = None
-
- html = req.load("http://uploading.com/")
-
- premium = False if re.search(self.PREMIUM_PATTERN, html) else True
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%b %d, %Y"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- premium = False
- validuntil = None
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- set_cookies(req.cj,
- [("uploading.com", "lang" , "1" ),
- ("uploading.com", "language", "1" ),
- ("uploading.com", "setlang" , "en"),
- ("uploading.com", "_lang" , "en")])
-
- req.load("http://uploading.com/")
- req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time.time() * 1000),
- post={'email': user, 'password': data['password'], 'remember': "on"})
diff --git a/module/plugins/accounts/UptoboxCom.py b/module/plugins/accounts/UptoboxCom.py
deleted file mode 100644
index c40dbd6e6..000000000
--- a/module/plugins/accounts/UptoboxCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class UptoboxCom(XFSAccount):
- __name__ = "UptoboxCom"
- __type__ = "account"
- __version__ = "0.08"
-
- __description__ = """DDLStorage.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "uptobox.com"
- HOSTER_URL = "https://uptobox.com/"
- LOGIN_URL = "https://login.uptobox.com/"
diff --git a/module/plugins/accounts/VidPlayNet.py b/module/plugins/accounts/VidPlayNet.py
deleted file mode 100644
index 5bfc24963..000000000
--- a/module/plugins/accounts/VidPlayNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class VidPlayNet(XFSAccount):
- __name__ = "VidPlayNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """VidPlay.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "vidplay.net"
diff --git a/module/plugins/accounts/WebshareCz.py b/module/plugins/accounts/WebshareCz.py
deleted file mode 100644
index f032e2317..000000000
--- a/module/plugins/accounts/WebshareCz.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-import re
-import time
-
-from passlib.hash import md5_crypt
-
-from module.plugins.Account import Account
-
-
-class WebshareCz(Account):
- __name__ = "WebshareCz"
- __type__ = "account"
- __version__ = "0.07"
-
- __description__ = """Webshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("rush", "radek.senfeld@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'<vip_until>(.+)</vip_until>'
-
- TRAFFIC_LEFT_PATTERN = r'<bytes>(.+)</bytes>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://webshare.cz/api/user_data/",
- post={'wst': self.infos['wst']},
- decode=True)
-
- self.logDebug("Response: " + html)
-
- expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug("Expire date: " + expiredate)
-
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
- trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
- premium = validuntil > time.time()
-
- return {'validuntil': validuntil, 'trafficleft': -1, 'premium': premium}
-
-
- def login(self, user, data, req):
- salt = req.load("https://webshare.cz/api/salt/",
- post={'username_or_email': user,
- 'wst' : ""},
- decode=True)
-
- if "<status>OK</status>" not in salt:
- self.wrongPassword()
-
- salt = re.search('<salt>(.+)</salt>', salt).group(1)
- password = hashlib.sha1(md5_crypt.encrypt(data["password"], salt=salt)).hexdigest()
- digest = hashlib.md5(user + ":Webshare:" + password).hexdigest()
-
- login = req.load("https://webshare.cz/api/login/",
- post={'digest' : digest,
- 'keep_logged_in' : 1,
- 'password' : password,
- 'username_or_email': user,
- 'wst' : ""},
- decode=True)
-
- if "<status>OK</status>" not in login:
- self.wrongPassword()
-
- self.infos['wst'] = re.search('<token>(.+)</token>', login).group(1)
diff --git a/module/plugins/accounts/XFileSharingPro.py b/module/plugins/accounts/XFileSharingPro.py
deleted file mode 100644
index 8dc7f3a30..000000000
--- a/module/plugins/accounts/XFileSharingPro.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class XFileSharingPro(XFSAccount):
- __name__ = "XFileSharingPro"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """XFileSharingPro multi-purpose account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = None
-
-
- def init(self):
- if self.HOSTER_DOMAIN:
- return super(XFileSharingPro, self).init()
-
-
- def loadAccountInfo(self, user, req):
- return super(XFileSharingPro if self.HOSTER_DOMAIN else XFSAccount, self).loadAccountInfo(user, req)
-
-
- def login(self, user, data, req):
- if self.HOSTER_DOMAIN:
- try:
- return super(XFileSharingPro, self).login(user, data, req)
- except Exception:
- self.HOSTER_URL = self.HOSTER_URL.replace("www.", "")
- return super(XFileSharingPro, self).login(user, data, req)
diff --git a/module/plugins/accounts/YibaishiwuCom.py b/module/plugins/accounts/YibaishiwuCom.py
deleted file mode 100644
index 33602a2fe..000000000
--- a/module/plugins/accounts/YibaishiwuCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class YibaishiwuCom(Account):
- __name__ = "YibaishiwuCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """115.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- ACCOUNT_INFO_PATTERN = r'var USER_PERMISSION = {(.*?)}'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://115.com/", decode=True)
-
- m = re.search(self.ACCOUNT_INFO_PATTERN, html, re.S)
- premium = True if m and 'is_vip: 1' in m.group(1) else False
- validuntil = trafficleft = (-1 if m else 0)
- return dict({"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium})
-
-
- def login(self, user, data, req):
- html = req.load("http://passport.115.com/?ac=login",
- post={"back": "http://www.115.com/",
- "goto": "http://115.com/",
- "login[account]": user,
- "login[passwd]": data['password']},
- decode=True)
-
- if not 'var USER_PERMISSION = {' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/ZeveraCom.py b/module/plugins/accounts/ZeveraCom.py
deleted file mode 100644
index 9bc6d0487..000000000
--- a/module/plugins/accounts/ZeveraCom.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-
-
-class ZeveraCom(Account):
- __name__ = "ZeveraCom"
- __type__ = "account"
- __version__ = "0.26"
-
- __description__ = """Zevera.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "zevera.com"
-
-
- def __init__(self, manager, accounts): #@TODO: remove in 0.4.10
- self.init()
- return super(ZeveraCom, self).__init__(manager, accounts)
-
-
- def init(self):
- if not self.HOSTER_DOMAIN:
- self.logError(_("Missing HOSTER_DOMAIN"))
-
- if not hasattr(self, "API_URL"):
- self.API_URL = "http://api.%s/jDownloader.ashx" % (self.HOSTER_DOMAIN or "")
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
-
- api = self.api_response(req)
-
- if "No trafic" not in api and api['endsubscriptiondate'] != "Expired!":
- validuntil = time.mktime(time.strptime(api['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S"))
- trafficleft = float(api['availabletodaytraffic']) * 1024 if api['orondaytrafficlimit'] != '0' else -1
- premium = True
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- self.user = user
- self.password = data['password']
-
- if self.api_response(req) == "No trafic":
- self.wrongPassword()
-
-
- def api_response(self, req, just_header=False, **kwargs):
- get_data = {'cmd' : "accountinfo",
- 'login': self.user,
- 'pass' : self.password}
-
- get_data.update(kwargs)
-
- res = req.load(self.API_URL,
- get=get_data,
- just_header=just_header,
- decode=True)
-
- self.logDebug(res)
-
- if ':' in res:
- if not just_header:
- res = res.replace(',', '\n')
- return dict((y.strip().lower(), z.strip()) for (y, z) in
- [x.split(':', 1) for x in res.splitlines() if ':' in x])
- else:
- return res
diff --git a/module/plugins/accounts/__init__.py b/module/plugins/accounts/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/accounts/__init__.py
+++ /dev/null
diff --git a/module/plugins/captcha/GigasizeCom.py b/module/plugins/captcha/GigasizeCom.py
deleted file mode 100644
index 52c41729b..000000000
--- a/module/plugins/captcha/GigasizeCom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class GigasizeCom(OCR):
- __name__ = "GigasizeCom"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Gigasize.com ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- def __init__(self):
- OCR.__init__(self)
-
-
- def get_captcha(self, image):
- self.load_image(image)
- self.threshold(2.8)
- self.run_tesser(True, False, False, True)
- return self.result_captcha
diff --git a/module/plugins/captcha/LinksaveIn.py b/module/plugins/captcha/LinksaveIn.py
deleted file mode 100644
index 95b107977..000000000
--- a/module/plugins/captcha/LinksaveIn.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-
-try:
- from PIL import Image
-except ImportError:
- import Image
-
-import glob
-import os
-
-from module.plugins.captcha.OCR import OCR
-
-
-class LinksaveIn(OCR):
- __name__ = "LinksaveIn"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Linksave.in ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- def __init__(self):
- OCR.__init__(self)
- self.data_dir = os.path.dirname(os.path.abspath(__file__)) + os.sep + "LinksaveIn" + os.sep
-
-
- def load_image(self, image):
- im = Image.open(image)
- frame_nr = 0
-
- lut = im.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- new = Image.new("RGB", im.size)
- npix = new.load()
- while True:
- try:
- im.seek(frame_nr)
- except EOFError:
- break
- frame = im.copy()
- pix = frame.load()
- for x in xrange(frame.size[0]):
- for y in xrange(frame.size[1]):
- if lut[pix[x, y]] != (0, 0, 0):
- npix[x, y] = lut[pix[x, y]]
- frame_nr += 1
- new.save(self.data_dir+"unblacked.png")
- self.image = new.copy()
- self.pixels = self.image.load()
- self.result_captcha = ''
-
-
- def get_bg(self):
- stat = {}
- cstat = {}
- img = self.image.convert("P")
- for bgpath in glob.glob(self.data_dir+"bg/*.gif"):
- stat[bgpath] = 0
- bg = Image.open(bgpath)
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- for x in xrange(bg.size[0]):
- for y in xrange(bg.size[1]):
- rgb_bg = bglut[bgpix[x, y]]
- rgb_c = lut[pix[x, y]]
- try:
- cstat[rgb_c] += 1
- except Exception:
- cstat[rgb_c] = 1
- if rgb_bg == rgb_c:
- stat[bgpath] += 1
- max_p = 0
- bg = ""
- for bgpath, value in stat.iteritems():
- if max_p < value:
- bg = bgpath
- max_p = value
- return bg
-
-
- def substract_bg(self, bgpath):
- bg = Image.open(bgpath)
- img = self.image.convert("P")
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- orgpix = self.image.load()
- for x in xrange(bg.size[0]):
- for y in xrange(bg.size[1]):
- rgb_bg = bglut[bgpix[x, y]]
- rgb_c = lut[pix[x, y]]
- if rgb_c == rgb_bg:
- orgpix[x, y] = (255, 255, 255)
-
-
- def eval_black_white(self):
- new = Image.new("RGB", (140, 75))
- pix = new.load()
- orgpix = self.image.load()
- thresh = 4
- for x in xrange(new.size[0]):
- for y in xrange(new.size[1]):
- rgb = orgpix[x, y]
- r, g, b = rgb
- pix[x, y] = (255, 255, 255)
- if r > max(b, g)+thresh:
- pix[x, y] = (0, 0, 0)
- if g < min(r, b):
- pix[x, y] = (0, 0, 0)
- if g > max(r, b)+thresh:
- pix[x, y] = (0, 0, 0)
- if b > max(r, g)+thresh:
- pix[x, y] = (0, 0, 0)
- self.image = new
- self.pixels = self.image.load()
-
-
- def get_captcha(self, image):
- self.load_image(image)
- bg = self.get_bg()
- self.substract_bg(bg)
- self.eval_black_white()
- self.to_greyscale()
- self.image.save(self.data_dir+"cleaned_pass1.png")
- self.clean(4)
- self.clean(4)
- self.image.save(self.data_dir+"cleaned_pass2.png")
- letters = self.split_captcha_letters()
- final = ""
- for n, letter in enumerate(letters):
- self.image = letter
- self.image.save(ocr.data_dir+"letter%d.png" % n)
- self.run_tesser(True, True, False, False)
- final += self.result_captcha
-
- return final
diff --git a/module/plugins/captcha/NetloadIn.py b/module/plugins/captcha/NetloadIn.py
deleted file mode 100644
index 1fb258c47..000000000
--- a/module/plugins/captcha/NetloadIn.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class NetloadIn(OCR):
- __name__ = "NetloadIn"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Netload.in ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- def __init__(self):
- OCR.__init__(self)
-
-
- def get_captcha(self, image):
- self.load_image(image)
- self.to_greyscale()
- self.clean(3)
- self.clean(3)
- self.run_tesser(True, True, False, False)
-
- self.result_captcha = self.result_captcha.replace(" ", "")[:4] # cut to 4 numbers
-
- return self.result_captcha
diff --git a/module/plugins/captcha/OCR.py b/module/plugins/captcha/OCR.py
deleted file mode 100644
index 1874ba07d..000000000
--- a/module/plugins/captcha/OCR.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-try:
- from PIL import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
-
-except ImportError:
- import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
-
-import logging
-import os
-import subprocess
-#import tempfile
-
-from module.utils import save_join
-
-
-class OCR(object):
- __name__ = "OCR"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """OCR base plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- def __init__(self):
- self.logger = logging.getLogger("log")
-
-
- def load_image(self, image):
- self.image = Image.open(image)
- self.pixels = self.image.load()
- self.result_captcha = ''
-
-
- def unload(self):
- """delete all tmp images"""
- pass
-
-
- def threshold(self, value):
- self.image = self.image.point(lambda a: a * value + 10)
-
-
- def run(self, command):
- """Run a command"""
-
- popen = subprocess.Popen(command, bufsize = -1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- popen.wait()
- output = popen.stdout.read() +" | "+ popen.stderr.read()
- popen.stdout.close()
- popen.stderr.close()
- self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output))
-
-
- def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True):
- #tmpTif = tempfile.NamedTemporaryFile(suffix=".tif")
- try:
- tmpTif = open(save_join("tmp", "tmpTif_%s.tif" % self.__name__), "wb")
- tmpTif.close()
-
- #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
- tmpTxt = open(save_join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb")
- tmpTxt.close()
-
- except IOError, e:
- self.logError(e)
- return
-
- self.logger.debug("save tiff")
- self.image.save(tmpTif.name, 'TIFF')
-
- if os.name == "nt":
- tessparams = [os.path.join(pypath, "tesseract", "tesseract.exe")]
- else:
- tessparams = ["tesseract"]
-
- tessparams.extend( [os.path.abspath(tmpTif.name), os.path.abspath(tmpTxt.name).replace(".txt", "")] )
-
- if subset and (digits or lowercase or uppercase):
- #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
- with open(save_join("tmp", "tmpSub_%s.subset" % self.__name__), "wb") as tmpSub:
- tmpSub.write("tessedit_char_whitelist ")
-
- if digits:
- tmpSub.write("0123456789")
- if lowercase:
- tmpSub.write("abcdefghijklmnopqrstuvwxyz")
- if uppercase:
- tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
-
- tmpSub.write("\n")
- tessparams.append("nobatch")
- tessparams.append(os.path.abspath(tmpSub.name))
-
- self.logger.debug("run tesseract")
- self.run(tessparams)
- self.logger.debug("read txt")
-
- try:
- with open(tmpTxt.name, 'r') as f:
- self.result_captcha = f.read().replace("\n", "")
- except Exception:
- self.result_captcha = ""
-
- self.logger.debug(self.result_captcha)
- try:
- os.remove(tmpTif.name)
- os.remove(tmpTxt.name)
- if subset and (digits or lowercase or uppercase):
- os.remove(tmpSub.name)
- except Exception:
- pass
-
-
- def get_captcha(self, name):
- raise NotImplementedError
-
-
- def to_greyscale(self):
- if self.image.mode != 'L':
- self.image = self.image.convert('L')
-
- self.pixels = self.image.load()
-
-
- def eval_black_white(self, limit):
- self.pixels = self.image.load()
- w, h = self.image.size
- for x in xrange(w):
- for y in xrange(h):
- if self.pixels[x, y] > limit:
- self.pixels[x, y] = 255
- else:
- self.pixels[x, y] = 0
-
-
- def clean(self, allowed):
- pixels = self.pixels
-
- w, h = self.image.size
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 255:
- continue
- # No point in processing white pixels since we only want to remove black pixel
- count = 0
-
- try:
- if pixels[x-1, y-1] != 255:
- count += 1
- if pixels[x-1, y] != 255:
- count += 1
- if pixels[x-1, y + 1] != 255:
- count += 1
- if pixels[x, y + 1] != 255:
- count += 1
- if pixels[x + 1, y + 1] != 255:
- count += 1
- if pixels[x + 1, y] != 255:
- count += 1
- if pixels[x + 1, y-1] != 255:
- count += 1
- if pixels[x, y-1] != 255:
- count += 1
- except Exception:
- pass
-
- # not enough neighbors are dark pixels so mark this pixel
- # to be changed to white
- if count < allowed:
- pixels[x, y] = 1
-
- # second pass: this time set all 1's to 255 (white)
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 1:
- pixels[x, y] = 255
-
- self.pixels = pixels
-
-
- def derotate_by_average(self):
- """rotate by checking each angle and guess most suitable"""
-
- w, h = self.image.size
- pixels = self.pixels
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 155
-
- highest = {}
- counts = {}
-
- for angle in xrange(-45, 45):
-
- tmpimage = self.image.rotate(angle)
-
- pixels = tmpimage.load()
-
- w, h = self.image.size
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 255
-
-
- count = {}
-
- for x in xrange(w):
- count[x] = 0
- for y in xrange(h):
- if pixels[x, y] == 155:
- count[x] += 1
-
- sum = 0
- cnt = 0
-
- for x in count.values():
- if x != 0:
- sum += x
- cnt += 1
-
- avg = sum / cnt
- counts[angle] = cnt
- highest[angle] = 0
- for x in count.values():
- if x > highest[angle]:
- highest[angle] = x
-
- highest[angle] = highest[angle] - avg
-
- hkey = 0
- hvalue = 0
-
- for key, value in highest.iteritems():
- if value > hvalue:
- hkey = key
- hvalue = value
-
- self.image = self.image.rotate(hkey)
- pixels = self.image.load()
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 255
-
- if pixels[x, y] == 155:
- pixels[x, y] = 0
-
- self.pixels = pixels
-
-
- def split_captcha_letters(self):
- captcha = self.image
- started = False
- letters = []
- width, height = captcha.size
- bottomY, topY = 0, height
- pixels = captcha.load()
-
- for x in xrange(width):
- black_pixel_in_col = False
- for y in xrange(height):
- if pixels[x, y] != 255:
- if not started:
- started = True
- firstX = x
- lastX = x
-
- if y > bottomY:
- bottomY = y
- if y < topY:
- topY = y
- if x > lastX:
- lastX = x
-
- black_pixel_in_col = True
-
- if black_pixel_in_col is False and started is True:
- rect = (firstX, topY, lastX, bottomY)
- new_captcha = captcha.crop(rect)
-
- w, h = new_captcha.size
- if w > 5 and h > 5:
- letters.append(new_captcha)
-
- started = False
- bottomY, topY = 0, height
-
- return letters
-
-
- def correct(self, values, var=None):
- if var:
- result = var
- else:
- result = self.result_captcha
-
- for key, item in values.iteritems():
-
- if key.__class__ == str:
- result = result.replace(key, item)
- else:
- for expr in key:
- result = result.replace(expr, item)
-
- if var:
- return result
- else:
- self.result_captcha = result
diff --git a/module/plugins/captcha/ShareonlineBiz.py b/module/plugins/captcha/ShareonlineBiz.py
deleted file mode 100644
index 6fad66600..000000000
--- a/module/plugins/captcha/ShareonlineBiz.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class ShareonlineBiz(OCR):
- __name__ = "ShareonlineBiz"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Shareonline.biz ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def __init__(self):
- OCR.__init__(self)
-
-
- def get_captcha(self, image):
- self.load_image(image)
- self.to_greyscale()
- self.image = self.image.resize((160, 50))
- self.pixels = self.image.load()
- self.threshold(1.85)
- #self.eval_black_white(240)
- #self.derotate_by_average()
-
- letters = self.split_captcha_letters()
-
- final = ""
- for letter in letters:
- self.image = letter
- self.run_tesser(True, True, False, False)
- final += self.result_captcha
-
- return final
-
- #tesseract at 60%
diff --git a/module/plugins/captcha/__init__.py b/module/plugins/captcha/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/captcha/__init__.py
+++ /dev/null
diff --git a/module/plugins/container/CCF.py b/module/plugins/container/CCF.py
deleted file mode 100644
index 235d5dc1d..000000000
--- a/module/plugins/container/CCF.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import MultipartPostHandler
-import re
-import urllib2
-
-from module.plugins.Container import Container
-from module.utils import fs_encode, save_join
-
-
-class CCF(Container):
- __name__ = "CCF"
- __type__ = "container"
- __version__ = "0.23"
-
- __pattern__ = r'.+\.ccf$'
-
- __description__ = """CCF container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Willnix", "Willnix@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def decrypt(self, pyfile):
- fs_filename = fs_encode(pyfile.url.strip())
- opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
-
- dlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php',
- {'src' : "ccf",
- 'filename': "test.ccf",
- 'upload' : open(fs_filename, "rb")}).read()
-
- download_folder = self.config['general']['download_folder']
- dlc_file = save_join(download_folder, "tmp_%s.dlc" % pyfile.name)
-
- try:
- dlc = re.search(r'<dlc>(.+)</dlc>', dlc_content, re.S).group(1).decode('base64')
-
- except AttributeError:
- self.fail(_("Container is corrupted"))
-
- with open(dlc_file, "w") as tempdlc:
- tempdlc.write(dlc)
-
- self.urls = [dlc_file]
diff --git a/module/plugins/container/DLC.py b/module/plugins/container/DLC.py
deleted file mode 100644
index ff2f0104a..000000000
--- a/module/plugins/container/DLC.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-import xml.dom.minidom
-
-from Crypto.Cipher import AES
-
-from module.plugins.Container import Container
-from module.utils import decode, fs_encode
-
-
-class DLC(Container):
- __name__ = "DLC"
- __type__ = "container"
- __version__ = "0.24"
-
- __pattern__ = r'.+\.dlc$'
-
- __description__ = """DLC container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Schnusch", "Schnusch@users.noreply.github.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- KEY = "cb99b5cbc24db398"
- IV = "9bc24cb995cb8db3"
- API_URL = "http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType=pylo&data=%s"
-
-
- def decrypt(self, pyfile):
- fs_filename = fs_encode(pyfile.url.strip())
- with open(fs_filename) as dlc:
- data = dlc.read().strip()
-
- data += '=' * (-len(data) % 4)
-
- dlc_key = data[-88:]
- dlc_data = data[:-88].decode('base64')
- dlc_content = self.load(self.API_URL % dlc_key)
-
- try:
- rc = re.search(r'<rc>(.+)</rc>', dlc_content, re.S).group(1).decode('base64')
-
- except AttributeError:
- self.fail(_("Container is corrupted"))
-
- key = iv = AES.new(self.KEY, AES.MODE_CBC, self.IV).decrypt(rc)
-
- self.data = AES.new(key, AES.MODE_CBC, iv).decrypt(dlc_data).decode('base64')
- self.packages = [(name or pyfile.name, links, name or pyfile.name) \
- for name, links in self.getPackages()]
-
-
- def getPackages(self):
- root = xml.dom.minidom.parseString(self.data).documentElement
- content = root.getElementsByTagName("content")[0]
- return self.parsePackages(content)
-
-
- def parsePackages(self, startNode):
- return [(decode(node.getAttribute("name")).decode('base64'), self.parseLinks(node)) \
- for node in startNode.getElementsByTagName("package")]
-
-
- def parseLinks(self, startNode):
- return [node.getElementsByTagName("url")[0].firstChild.data.decode('base64') \
- for node in startNode.getElementsByTagName("file")]
diff --git a/module/plugins/container/RSDF.py b/module/plugins/container/RSDF.py
deleted file mode 100644
index dd2d14cf7..000000000
--- a/module/plugins/container/RSDF.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import binascii
-import re
-
-from Crypto.Cipher import AES
-
-from module.plugins.Container import Container
-from module.utils import fs_encode
-
-
-class RSDF(Container):
- __name__ = "RSDF"
- __type__ = "container"
- __version__ = "0.29"
-
- __pattern__ = r'.+\.rsdf$'
-
- __description__ = """RSDF container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("spoob", "spoob@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- KEY = "8C35192D964DC3182C6F84F3252239EB4A320D2500000000"
- IV = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
-
-
- def decrypt(self, pyfile):
- KEY = binascii.unhexlify(self.KEY)
- IV = binascii.unhexlify(self.IV)
-
- iv = AES.new(KEY, AES.MODE_ECB).encrypt(IV)
- cipher = AES.new(KEY, AES.MODE_CFB, iv)
-
- try:
- fs_filename = fs_encode(pyfile.url.strip())
- with open(fs_filename, 'r') as rsdf:
- data = rsdf.read()
-
- except IOError, e:
- self.fail(e)
-
- if re.search(r"<title>404 - Not Found</title>", data):
- pyfile.setStatus("offline")
-
- else:
- try:
- raw_links = binascii.unhexlify(''.join(data.split())).splitlines()
-
- except TypeError:
- self.fail(_("Container is corrupted"))
-
- for link in raw_links:
- if not link:
- continue
- link = cipher.decrypt(link.decode('base64')).replace('CCF: ', '')
- self.urls.append(link)
diff --git a/module/plugins/container/TXT.py b/module/plugins/container/TXT.py
deleted file mode 100644
index d419ee060..000000000
--- a/module/plugins/container/TXT.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import codecs
-
-from module.plugins.Container import Container
-from module.utils import fs_encode
-
-
-class TXT(Container):
- __name__ = "TXT"
- __type__ = "container"
- __version__ = "0.15"
-
- __pattern__ = r'.+\.(txt|text)$'
- __config__ = [("flush" , "bool" , "Flush list after adding", False ),
- ("encoding", "string", "File encoding" , "utf-8")]
-
- __description__ = """Read link lists in plain text formats"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("jeix", "jeix@hasnomail.com")]
-
-
- def decrypt(self, pyfile):
- try:
- encoding = codecs.lookup(self.getConfig('encoding')).name
-
- except Exception:
- encoding = "utf-8"
-
- fs_filename = fs_encode(pyfile.url.strip())
- txt = codecs.open(fs_filename, 'r', encoding)
- curPack = "Parsed links from %s" % pyfile.name
- packages = {curPack:[],}
-
- for link in txt.readlines():
- link = link.strip()
-
- if not link:
- continue
-
- if link.startswith(";"):
- continue
-
- if link.startswith("[") and link.endswith("]"):
- # new package
- curPack = link[1:-1]
- packages[curPack] = []
- continue
-
- packages[curPack].append(link)
-
- txt.close()
-
- # empty packages fix
- for key, value in packages.iteritems():
- if not value:
- packages.pop(key, None)
-
- if self.getConfig('flush'):
- try:
- txt = open(fs_filename, 'wb')
- txt.close()
-
- except IOError:
- self.logWarning(_("Failed to flush list"))
-
- for name, links in packages.iteritems():
- self.packages.append((name, links, name))
diff --git a/module/plugins/container/__init__.py b/module/plugins/container/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/container/__init__.py
+++ /dev/null
diff --git a/module/plugins/crypter/BitshareComFolder.py b/module/plugins/crypter/BitshareComFolder.py
deleted file mode 100644
index 256c5b5aa..000000000
--- a/module/plugins/crypter/BitshareComFolder.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class BitshareComFolder(SimpleCrypter):
- __name__ = "BitshareComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Bitshare.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href="(http://bitshare\.com/files/.+)">.+</a></td>'
- NAME_PATTERN = r'View public folder "(?P<N>.+)"</h1>'
-
-
-getInfo = create_getInfo(BitshareComFolder)
diff --git a/module/plugins/crypter/C1NeonCom.py b/module/plugins/crypter/C1NeonCom.py
deleted file mode 100644
index 926633ff7..000000000
--- a/module/plugins/crypter/C1NeonCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class C1NeonCom(DeadCrypter):
- __name__ = "C1NeonCom"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?c1neon\.com/.+'
- __config__ = []
-
- __description__ = """C1neon.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
-getInfo = create_getInfo(C1NeonCom)
diff --git a/module/plugins/crypter/ChipDe.py b/module/plugins/crypter/ChipDe.py
deleted file mode 100644
index f535da48d..000000000
--- a/module/plugins/crypter/ChipDe.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class ChipDe(Crypter):
- __name__ = "ChipDe"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?chip\.de/video/.+\.html'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Chip.de decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("4Christopher", "4Christopher@gmx.de")]
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- try:
- f = re.search(r'"(http://video\.chip\.de/.+)"', self.html)
- except Exception:
- self.fail(_("Failed to find the URL"))
- else:
- self.urls = [f.group(1)]
- self.logDebug("The file URL is %s" % self.urls[0])
diff --git a/module/plugins/crypter/CloudzillaToFolder.py b/module/plugins/crypter/CloudzillaToFolder.py
deleted file mode 100644
index 96d7245f1..000000000
--- a/module/plugins/crypter/CloudzillaToFolder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class CloudzillaToFolder(SimpleHoster):
- __name__ = "CloudzillaToFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?cloudzilla\.to/share/folder/(?P<ID>[\w^_]+)'
-
- __description__ = """Cloudzilla.to folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'<span class="name" title="(?P<N>.+?)"'
- OFFLINE_PATTERN = r'>File not found...<'
-
- LINK_PATTERN = r'<a href="(.+?)" class="item_href">'
-
- PASSWORD_PATTERN = r'<div id="pwd_protected">'
-
-
- def checkErrors(self):
- m = re.search(self.PASSWORD_PATTERN, self.html)
- if m:
- self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
-
- if re.search(self.PASSWORD_PATTERN, self.html):
- self.retry(reason="Wrong password")
-
-
-getInfo = create_getInfo(CloudzillaToFolder)
diff --git a/module/plugins/crypter/CrockoComFolder.py b/module/plugins/crypter/CrockoComFolder.py
deleted file mode 100644
index f56cc449a..000000000
--- a/module/plugins/crypter/CrockoComFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class CrockoComFolder(SimpleCrypter):
- __name__ = "CrockoComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?crocko\.com/f/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Crocko.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<td class="last"><a href="(.+?)">download</a>'
-
-
-getInfo = create_getInfo(CrockoComFolder)
diff --git a/module/plugins/crypter/CryptItCom.py b/module/plugins/crypter/CryptItCom.py
deleted file mode 100644
index 2cf4e9f62..000000000
--- a/module/plugins/crypter/CryptItCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class CryptItCom(DeadCrypter):
- __name__ = "CryptItCom"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/\w+'
- __config__ = []
-
- __description__ = """Crypt-it.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de")]
-
-
-getInfo = create_getInfo(CryptItCom)
diff --git a/module/plugins/crypter/CzshareComFolder.py b/module/plugins/crypter/CzshareComFolder.py
deleted file mode 100644
index c317b1b49..000000000
--- a/module/plugins/crypter/CzshareComFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class CzshareComFolder(Crypter):
- __name__ = "CzshareComFolder"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_PATTERN = r'<tr class="subdirectory">\s*<td>\s*<table>(.*?)</table>'
- LINK_PATTERN = r'<td class="col2"><a href="(.+?)">info</a></td>'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- m = re.search(self.FOLDER_PATTERN, html, re.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/module/plugins/crypter/DailymotionComFolder.py b/module/plugins/crypter/DailymotionComFolder.py
deleted file mode 100644
index 01caa0bb8..000000000
--- a/module/plugins/crypter/DailymotionComFolder.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class DailymotionComFolder(Crypter):
- __name__ = "DailymotionComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Dailymotion.com channel & playlist decrypter"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, ref, req=None):
- url = urlparse.urljoin("https://api.dailymotion.com/", ref)
- html = self.load(url, get=req)
- return json_loads(html)
-
-
- def getPlaylistInfo(self, id):
- ref = "playlist/" + id
- req = {"fields": "name,owner.screenname"}
- playlist = self.api_response(ref, req)
-
- if "error" in playlist:
- return
-
- name = playlist['name']
- owner = playlist['owner.screenname']
- return name, owner
-
-
- def _getPlaylists(self, user_id, page=1):
- ref = "user/%s/playlists" % user_id
- req = {"fields": "id", "page": page, "limit": 100}
- user = self.api_response(ref, req)
-
- if "error" in user:
- return
-
- for playlist in user['list']:
- yield playlist['id']
-
- if user['has_more']:
- for item in self._getPlaylists(user_id, page + 1):
- yield item
-
-
- def getPlaylists(self, user_id):
- return [(id,) + self.getPlaylistInfo(id) for id in self._getPlaylists(user_id)]
-
-
- def _getVideos(self, id, page=1):
- ref = "playlist/%s/videos" % id
- req = {"fields": "url", "page": page, "limit": 100}
- playlist = self.api_response(ref, req)
-
- if "error" in playlist:
- return
-
- for video in playlist['list']:
- yield video['url']
-
- if playlist['has_more']:
- for item in self._getVideos(id, page + 1):
- yield item
-
-
- def getVideos(self, playlist_id):
- return list(self._getVideos(playlist_id))[::-1]
-
-
- def decrypt(self, pyfile):
- m = re.match(self.__pattern__, pyfile.url)
- m_id = m.group('ID')
- m_type = m.group('TYPE')
-
- if m_type == "playlist":
- self.logDebug("Url recognized as Playlist")
- p_info = self.getPlaylistInfo(m_id)
- playlists = [(m_id,) + p_info] if p_info else None
- else:
- self.logDebug("Url recognized as Channel")
- playlists = self.getPlaylists(m_id)
- self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), m_id))
-
- if not playlists:
- self.fail(_("No playlist available"))
-
- for p_id, p_name, p_owner in playlists:
- p_videos = self.getVideos(p_id)
- p_folder = save_join(self.config['general']['download_folder'], p_owner, p_name)
- self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
- self.packages.append((p_name, p_videos, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
diff --git a/module/plugins/crypter/DataHuFolder.py b/module/plugins/crypter/DataHuFolder.py
deleted file mode 100644
index 67f5e788f..000000000
--- a/module/plugins/crypter/DataHuFolder.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DataHuFolder(SimpleCrypter):
- __name__ = "DataHuFolder"
- __type__ = "crypter"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?data\.hu/dir/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Data.hu folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("crash", None),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href=\'(http://data\.hu/get/.+)\' target=\'_blank\'>\1</a>'
- NAME_PATTERN = ur'<title>(?P<N>.+) Let\xf6lt\xe9se</title>'
-
-
- def prepare(self):
- super(DataHuFolder, self).prepare()
-
- if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: # Password protected
- password = self.getPassword()
- if not password:
- self.fail(_("Password required"))
-
- self.logDebug("The folder is password protected', 'Using password: " + password)
-
- self.html = self.load(self.pyfile.url, post={'mappa_pass': password}, decode=True)
-
- if u'Hib\xe1s jelsz\xf3' in self.html: # Wrong password
- self.fail(_("Wrong password"))
-
-
-getInfo = create_getInfo(DataHuFolder)
diff --git a/module/plugins/crypter/DdlstorageComFolder.py b/module/plugins/crypter/DdlstorageComFolder.py
deleted file mode 100644
index e02e77fda..000000000
--- a/module/plugins/crypter/DdlstorageComFolder.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class DdlstorageComFolder(DeadCrypter):
- __name__ = "DdlstorageComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+'
- __config__ = []
-
- __description__ = """DDLStorage.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DdlstorageComFolder)
diff --git a/module/plugins/crypter/DepositfilesComFolder.py b/module/plugins/crypter/DepositfilesComFolder.py
deleted file mode 100644
index 46ec265c3..000000000
--- a/module/plugins/crypter/DepositfilesComFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DepositfilesComFolder(SimpleCrypter):
- __name__ = "DepositfilesComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?depositfiles\.com/folders/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Depositfiles.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<div class="progressName".*?>\s*<a href="(.+?)" title=".+?" target="_blank">'
-
-
-getInfo = create_getInfo(DepositfilesComFolder)
diff --git a/module/plugins/crypter/Dereferer.py b/module/plugins/crypter/Dereferer.py
deleted file mode 100644
index 8427c1540..000000000
--- a/module/plugins/crypter/Dereferer.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class Dereferer(SimpleDereferer):
- __name__ = "Dereferer"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'https?://([^/]+)/.*?(?P<LINK>(ht|f)tps?(://|%3A%2F%2F).+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Crypter for dereferers"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/module/plugins/crypter/DevhostStFolder.py b/module/plugins/crypter/DevhostStFolder.py
deleted file mode 100644
index 4d15e2058..000000000
--- a/module/plugins/crypter/DevhostStFolder.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://d-h.st/users/shine/?fld_id=37263#files
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DevhostStFolder(SimpleCrypter):
- __name__ = "DevhostStFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """d-h.st folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'(?:/> |;">)<a href="(.+?)"(?!>Back to \w+<)'
- OFFLINE_PATTERN = r'"/cHP">test\.png<'
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("File info (BEFORE): %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("File info (AFTER): %s" % self.info)
-
- try:
- if self.info['pattern']['ID'] == "0":
- raise
-
- p = r'href="(.+?)">Back to \w+<'
- m = re.search(p, self.html)
- html = self.load(urlparse.urljoin("http://d-h.st", m.group(1)),
- cookies=False)
-
- p = '\?fld_id=%s.*?">(.+?)<' % self.info['pattern']['ID']
- m = re.search(p, html)
- self.pyfile.name = m.group(1)
-
- except Exception, e:
- self.logDebug(e)
- self.pyfile.name = self.info['pattern']['USER']
-
- try:
- folder = self.info['folder'] = self.pyfile.name
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File folder: %s" % self.pyfile.name)
-
-
-getInfo = create_getInfo(DevhostStFolder)
diff --git a/module/plugins/crypter/DlProtectCom.py b/module/plugins/crypter/DlProtectCom.py
deleted file mode 100644
index a9f39c6f3..000000000
--- a/module/plugins/crypter/DlProtectCom.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from base64 import urlsafe_b64encode
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DlProtectCom(SimpleCrypter):
- __name__ = "DlProtectCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?dl-protect\.com/((en|fr)/)?\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Dl-protect.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- COOKIES = [("dl-protect.com", "l", "en")]
-
- OFFLINE_PATTERN = r'Unfortunately, the link you are looking for is not found'
-
-
- def getLinks(self):
- # Direct link with redirect
- if not re.match(r"https?://(?:www\.)?dl-protect\.com/.+", self.req.http.lastEffectiveURL):
- return [self.req.http.lastEffectiveURL]
-
- post_req = {'key' : re.search(r'name="key" value="(.+?)"', self.html).group(1),
- 'submitform': ""}
-
- if "Please click on continue to see the content" in self.html:
- post_req['submitform'] = "Continue"
- self.wait(2)
-
- else:
- mstime = int(round(time.time() * 1000))
- b64time = "_" + urlsafe_b64encode(str(mstime)).replace("=", "%3D")
-
- post_req.update({'i' : b64time,
- 'submitform': "Decrypt+link"})
-
- if "Password :" in self.html:
- post_req['pwd'] = self.getPassword()
-
- if "Security Code" in self.html:
- captcha_id = re.search(r'/captcha\.php\?uid=(.+?)"', self.html).group(1)
- captcha_url = "http://www.dl-protect.com/captcha.php?uid=" + captcha_id
- captcha_code = self.decryptCaptcha(captcha_url, imgtype="gif")
-
- post_req['secure'] = captcha_code
-
- self.html = self.load(self.pyfile.url, post=post_req)
-
- for errmsg in ("The password is incorrect", "The security code is incorrect"):
- if errmsg in self.html:
- self.fail(_(errmsg[1:]))
-
- return re.findall(r'<a href="([^/].+?)" target="_blank">', self.html)
-
-
-getInfo = create_getInfo(DlProtectCom)
diff --git a/module/plugins/crypter/DontKnowMe.py b/module/plugins/crypter/DontKnowMe.py
deleted file mode 100644
index e56751972..000000000
--- a/module/plugins/crypter/DontKnowMe.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class DontKnowMe(SimpleDereferer):
- __name__ = "DontKnowMe"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?dontknow\.me/at/\?(?P<LINK>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """DontKnow.me decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("selaux", "")]
diff --git a/module/plugins/crypter/DuckCryptInfo.py b/module/plugins/crypter/DuckCryptInfo.py
deleted file mode 100644
index cc108d101..000000000
--- a/module/plugins/crypter/DuckCryptInfo.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.Crypter import Crypter
-
-
-class DuckCryptInfo(Crypter):
- __name__ = "DuckCryptInfo"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?duckcrypt\.info/(folder|wait|link)/(\w+)/?(\w*)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """DuckCrypt.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
- TIMER_PATTERN = r'<span id="timer">(.*)</span>'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- m = re.match(self.__pattern__, url)
- if m is None:
- self.fail(_("Weird error in link"))
- if str(m.group(1)) == "link":
- self.handleLink(url)
- else:
- self.handleFolder(m)
-
-
- def handleFolder(self, m):
- html = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
- m = re.match(self.__pattern__, html)
- self.logDebug("Redirectet to " + str(m.group(0)))
- html = self.load(str(m.group(0)))
- soup = BeautifulSoup(html)
- cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
- self.logDebug("Redirectet to " + str(cryptlinks))
- if not cryptlinks:
- self.error(_("No link found"))
- for clink in cryptlinks:
- if clink.find("a"):
- self.handleLink(clink.find("a")['href'])
-
-
- def handleLink(self, url):
- html = self.load(url)
- soup = BeautifulSoup(html)
- self.urls = [soup.find("iframe")['src']]
- if not self.urls:
- self.logInfo(_("No link found"))
diff --git a/module/plugins/crypter/DuploadOrgFolder.py b/module/plugins/crypter/DuploadOrgFolder.py
deleted file mode 100644
index 066fbe3d7..000000000
--- a/module/plugins/crypter/DuploadOrgFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class DuploadOrgFolder(DeadCrypter):
- __name__ = "DuploadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/folder/\d+'
- __config__ = []
-
- __description__ = """Dupload.org folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DuploadOrgFolder)
diff --git a/module/plugins/crypter/EasybytezComFolder.py b/module/plugins/crypter/EasybytezComFolder.py
deleted file mode 100644
index fdd3d4ff4..000000000
--- a/module/plugins/crypter/EasybytezComFolder.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class EasybytezComFolder(XFSCrypter):
- __name__ = "EasybytezComFolder"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?easybytez\.com/users/\d+/\d+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Easybytez.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LOGIN_ACCOUNT = True
-
-
-getInfo = create_getInfo(EasybytezComFolder)
diff --git a/module/plugins/crypter/EmbeduploadCom.py b/module/plugins/crypter/EmbeduploadCom.py
deleted file mode 100644
index 28633f634..000000000
--- a/module/plugins/crypter/EmbeduploadCom.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-from module.network.HTTPRequest import BadHeader
-
-
-class EmbeduploadCom(Crypter):
- __name__ = "EmbeduploadCom"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?embedupload\.com/\?d=.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True ),
- ("preferedHoster" , "str" , "Prefered hoster list (bar-separated)", "embedupload"),
- ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
-
- __description__ = """EmbedUpload.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<div id="(.+?)".*?>\s*<a href="(.+?)" target="_blank" (?:class="DownloadNow"|style="color:red")>'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- tmp_links = []
-
- m = re.findall(self.LINK_PATTERN, self.html)
- if m:
- prefered_set = set(self.getConfig('preferedHoster').split('|'))
- prefered_set = map(lambda s: s.lower().split('.')[0], prefered_set)
-
- self.logDebug("PF: %s" % prefered_set)
-
- tmp_links.extend(x[1] for x in m if x[0] in prefered_set)
- self.urls = self.getLocation(tmp_links)
-
- if not self.urls:
- ignored_set = set(self.getConfig('ignoredHoster').split('|'))
- ignored_set = map(lambda s: s.lower().split('.')[0], ignored_set)
-
- self.logDebug("IG: %s" % ignored_set)
-
- tmp_links.extend(x[1] for x in m if x[0] not in ignored_set)
- self.urls = self.getLocation(tmp_links)
-
-
- def getLocation(self, tmp_links):
- new_links = []
- for link in tmp_links:
- try:
- header = self.load(link, just_header=True)
- if 'location' in header:
- new_links.append(header['location'])
- except BadHeader:
- pass
- return new_links
diff --git a/module/plugins/crypter/FilebeerInfoFolder.py b/module/plugins/crypter/FilebeerInfoFolder.py
deleted file mode 100644
index a3c7ee74c..000000000
--- a/module/plugins/crypter/FilebeerInfoFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FilebeerInfoFolder(DeadCrypter):
- __name__ = "FilebeerInfoFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?filebeer\.info/\d*~f\w+'
- __config__ = []
-
- __description__ = """Filebeer.info folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FilebeerInfoFolder)
diff --git a/module/plugins/crypter/FilecloudIoFolder.py b/module/plugins/crypter/FilecloudIoFolder.py
deleted file mode 100644
index f900f4910..000000000
--- a/module/plugins/crypter/FilecloudIoFolder.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilecloudIoFolder(SimpleCrypter):
- __name__ = "FilecloudIoFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filecloud.io folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'href="(http://filecloud\.io/\w+)" title'
- NAME_PATTERN = r'>(?P<N>.+?) - filecloud\.io<'
-
-
-getInfo = create_getInfo(FilecloudIoFolder)
diff --git a/module/plugins/crypter/FilecryptCc.py b/module/plugins/crypter/FilecryptCc.py
deleted file mode 100644
index a1a94b6f6..000000000
--- a/module/plugins/crypter/FilecryptCc.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://filecrypt.cc/Container/64E039F859.html
-
-import binascii
-import re
-import urlparse
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class FilecryptCc(Crypter):
- __name__ = "FilecryptCc"
- __type__ = "crypter"
- __version__ = "0.14"
-
- __pattern__ = r'https?://(?:www\.)?filecrypt\.cc/Container/\w+'
-
- __description__ = """Filecrypt.cc decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "")]
-
-
- # URL_REPLACEMENTS = [(r'.html$', ""), (r'$', ".html")] #@TODO: Extend SimpleCrypter
-
- DLC_LINK_PATTERN = r'<button class="dlcdownload" type="button" title="Download \*.dlc" onclick="DownloadDLC\(\'(.+)\'\);"><i></i><span>dlc<'
- WEBLINK_PATTERN = r"openLink.?'([\w_-]*)',"
-
- CAPTCHA_PATTERN = r'<img id="nc" src="(.+?)"'
- CIRCLE_CAPTCHA_PATTERN = r'<input type="image" src="(.+?)"'
-
- MIRROR_PAGE_PATTERN = r'"[\w]*" href="(https?://(?:www\.)?filecrypt.cc/Container/\w+\.html\?mirror=\d+)">'
-
-
- def setup(self):
- self.links = []
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- self.base_url = self.pyfile.url.split("Container")[0]
-
- if "content notfound" in self.html: #@NOTE: "content notfound" is NOT a typo
- self.offline()
-
- self.handlePasswordProtection()
- self.handleCaptcha()
- self.handleMirrorPages()
-
- for handle in (self.handleCNL, self.handleWeblinks, self.handleDlcContainer):
- handle()
- if self.links:
- self.packages = [(pyfile.package().name, self.links, pyfile.package().name)]
- return
-
-
- def handleMirrorPages(self):
- if "mirror=" not in self.siteWithLinks:
- return
-
- mirror = re.findall(self.MIRROR_PAGE_PATTERN, self.siteWithLinks)
-
- self.logInfo(_("Found %d mirrors") % len(mirror))
-
- for i in mirror[1:]:
- self.siteWithLinks = self.siteWithLinks + self.load(i).decode("utf-8", "replace")
-
-
- def handlePasswordProtection(self):
- if '<input type="text" name="password"' not in self.html:
- return
-
- self.logInfo(_("Folder is password protected"))
-
- password = self.getPassword()
-
- if not password:
- self.fail(_("Please enter the password in package section and try again"))
-
- self.html = self.load(self.pyfile.url, post={"password": password})
-
-
- def handleCaptcha(self):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- m2 = re.search(self.CIRCLE_CAPTCHA_PATTERN, self.html)
-
- if m: #: normal captcha
- self.logDebug("Captcha-URL: %s" % m.group(1))
-
- captcha_code = self.decryptCaptcha(urlparse.urljoin(self.base_url, m.group(1)),
- forceUser=True,
- imgtype="gif")
-
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'recaptcha_response_field': captcha_code},
- decode=True)
- elif m2: #: circle captcha
- self.logDebug("Captcha-URL: %s" % m2.group(1))
-
- captcha_code = self.decryptCaptcha('%s%s?c=abc' %(self.base_url, m2.group(1)),
- result_type='positional')
-
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'button.x': captcha_code[0], 'button.y': captcha_code[1]},
- decode=True)
-
- else:
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
-
- if captcha_key:
- response, challenge = recaptcha.challenge(captcha_key)
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'g-recaptcha-response': response},
- decode=True)
- else:
- self.logInfo(_("No captcha found"))
- self.siteWithLinks = self.html
-
- if "recaptcha_image" in self.siteWithLinks or "data-sitekey" in self.siteWithLinks:
- self.invalidCaptcha()
- self.retry()
-
-
- def handleDlcContainer(self):
- dlc = re.findall(self.DLC_LINK_PATTERN, self.siteWithLinks)
-
- if not dlc:
- return
-
- for i in dlc:
- self.links.append("%s/DLC/%s.dlc" % (self.base_url, i))
-
-
- def handleWeblinks(self):
- try:
- weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks)
-
- for link in weblinks:
- res = self.load("%s/Link/%s.html" % (self.base_url, link))
- link2 = re.search('<iframe noresize src="(.*)"></iframe>', res)
- res2 = self.load(link2.group(1), just_header=True)
- self.links.append(res2['location'])
-
- except Exception, e:
- self.logDebug("Error decrypting weblinks: %s" % e)
-
-
- def handleCNL(self):
- try:
- vjk = re.findall('<input type="hidden" name="jk" value="function f\(\){ return \'(.*)\';}">', self.siteWithLinks)
- vcrypted = re.findall('<input type="hidden" name="crypted" value="(.*)">', self.siteWithLinks)
-
- for i in xrange(len(vcrypted)):
- self.links.extend(self._getLinks(vcrypted[i], vjk[i]))
-
- except Exception, e:
- self.logDebug("Error decrypting CNL: %s" % e)
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- key = binascii.unhexlify(str(jk))
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- return links
diff --git a/module/plugins/crypter/FilefactoryComFolder.py b/module/plugins/crypter/FilefactoryComFolder.py
deleted file mode 100644
index c0cd028cc..000000000
--- a/module/plugins/crypter/FilefactoryComFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilefactoryComFolder(SimpleCrypter):
- __name__ = "FilefactoryComFolder"
- __type__ = "crypter"
- __version__ = "0.32"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filefactory.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
-
- LINK_PATTERN = r'<td>\s*<a href="(.+?)"'
- NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+?)<'
- PAGES_PATTERN = r'data-paginator-totalPages="(\d+)'
-
-
- def loadPage(self, page_n):
- return self.load(self.pyfile.url, get={'page': page_n, 'show': 100})
-
-
-getInfo = create_getInfo(FilefactoryComFolder)
diff --git a/module/plugins/crypter/FilerNetFolder.py b/module/plugins/crypter/FilerNetFolder.py
deleted file mode 100644
index e2b44e868..000000000
--- a/module/plugins/crypter/FilerNetFolder.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilerNetFolder(SimpleCrypter):
- __name__ = "FilerNetFolder"
- __type__ = "crypter"
- __version__ = "0.42"
-
- __pattern__ = r'https?://filer\.net/folder/\w{16}'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filer.net decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
-
- NAME_PATTERN = r'<h3>(?P<N>.+?) - <small'
- OFFLINE_PATTERN = r'Nicht gefunden'
-
-
-getInfo = create_getInfo(FilerNetFolder)
diff --git a/module/plugins/crypter/FileserveComFolder.py b/module/plugins/crypter/FileserveComFolder.py
deleted file mode 100644
index 1363e2d45..000000000
--- a/module/plugins/crypter/FileserveComFolder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class FileserveComFolder(Crypter):
- __name__ = "FileserveComFolder"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?fileserve\.com/list/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """FileServe.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fionnc", "fionnc@gmail.com")]
-
-
- FOLDER_PATTERN = r'<table class="file_list">(.*?)</table>'
- LINK_PATTERN = r'<a href="(.+?)" class="sheet_icon wbold">'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- new_links = []
-
- folder = re.search(self.FOLDER_PATTERN, html, re.S)
- if folder is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1)))
-
- if new_links:
- self.urls = [map(lambda s: "http://fileserve.com%s" % s, new_links)]
diff --git a/module/plugins/crypter/FilesonicComFolder.py b/module/plugins/crypter/FilesonicComFolder.py
deleted file mode 100644
index 8fc28d592..000000000
--- a/module/plugins/crypter/FilesonicComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FilesonicComFolder(DeadCrypter):
- __name__ = "FilesonicComFolder"
- __type__ = "crypter"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?filesonic\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Filesonic.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FilesonicComFolder)
diff --git a/module/plugins/crypter/FilestubeCom.py b/module/plugins/crypter/FilestubeCom.py
deleted file mode 100644
index 133f4a53f..000000000
--- a/module/plugins/crypter/FilestubeCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilestubeCom(SimpleCrypter):
- __name__ = "FilestubeCom"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filestube.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
- NAME_PATTERN = r'<h1\s*> (?P<N>.+) download\s*</h1>'
-
-
-getInfo = create_getInfo(FilestubeCom)
diff --git a/module/plugins/crypter/FiletramCom.py b/module/plugins/crypter/FiletramCom.py
deleted file mode 100644
index b012d35bc..000000000
--- a/module/plugins/crypter/FiletramCom.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FiletramCom(SimpleCrypter):
- __name__ = "FiletramCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filetram\.com/[^/]+/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filetram.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'\s+(http://.+)'
- NAME_PATTERN = r'<title>(?P<N>.+?) - Free Download'
-
-
-getInfo = create_getInfo(FiletramCom)
diff --git a/module/plugins/crypter/FiredriveComFolder.py b/module/plugins/crypter/FiredriveComFolder.py
deleted file mode 100644
index 7d3a357fd..000000000
--- a/module/plugins/crypter/FiredriveComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FiredriveComFolder(DeadCrypter):
- __name__ = "FiredriveComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
- __config__ = []
-
- __description__ = """Firedrive.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(FiredriveComFolder)
diff --git a/module/plugins/crypter/FourChanOrg.py b/module/plugins/crypter/FourChanOrg.py
deleted file mode 100644
index c90c84b6f..000000000
--- a/module/plugins/crypter/FourChanOrg.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599)
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class FourChanOrg(Crypter):
- __name__ = "FourChanOrg"
- __type__ = "crypter"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """4chan.org folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
- def decrypt(self, pyfile):
- pagehtml = self.load(pyfile.url)
- images = set(re.findall(r'(images\.4chan\.org/[^/]*/src/[^"<]+)', pagehtml))
- self.urls = ["http://" + image for image in images]
diff --git a/module/plugins/crypter/FreakhareComFolder.py b/module/plugins/crypter/FreakhareComFolder.py
deleted file mode 100644
index 173660668..000000000
--- a/module/plugins/crypter/FreakhareComFolder.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FreakhareComFolder(SimpleCrypter):
- __name__ = "FreakhareComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.com/folder/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Freakhare.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href="(http://freakshare\.com/files/.+?)" target="_blank">'
- NAME_PATTERN = r'Folder:</b> (?P<N>.+)'
- PAGES_PATTERN = r'Pages: +(\d+)'
-
-
- def loadPage(self, page_n):
- if not hasattr(self, 'f_id') and not hasattr(self, 'f_md5'):
- m = re.search(r'http://freakshare.com/\?x=folder&f_id=(\d+)&f_md5=(\w+)', self.html)
- if m:
- self.f_id = m.group(1)
- self.f_md5 = m.group(2)
- return self.load('http://freakshare.com/', get={'x': 'folder',
- 'f_id': self.f_id,
- 'f_md5': self.f_md5,
- 'entrys': '20',
- 'page': page_n - 1,
- 'order': ''}, decode=True)
-
-
-getInfo = create_getInfo(FreakhareComFolder)
diff --git a/module/plugins/crypter/FreetexthostCom.py b/module/plugins/crypter/FreetexthostCom.py
deleted file mode 100644
index cf6fbb8a4..000000000
--- a/module/plugins/crypter/FreetexthostCom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FreetexthostCom(SimpleCrypter):
- __name__ = "FreetexthostCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?freetexthost\.com/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Freetexthost.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getLinks(self):
- m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.S)
- if m is None:
- self.error(_("Unable to extract links"))
- links = m.group(1)
- return links.strip().split("<br />\r\n")
-
-
-getInfo = create_getInfo(FreetexthostCom)
diff --git a/module/plugins/crypter/FshareVnFolder.py b/module/plugins/crypter/FshareVnFolder.py
deleted file mode 100644
index bbee53337..000000000
--- a/module/plugins/crypter/FshareVnFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FshareVnFolder(SimpleCrypter):
- __name__ = "FshareVnFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?fshare\.vn/folder/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Fshare.vn folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<li class="w_80pc"><a href="(.+?)" target="_blank">'
-
-
-getInfo = create_getInfo(FshareVnFolder)
diff --git a/module/plugins/crypter/Go4UpCom.py b/module/plugins/crypter/Go4UpCom.py
deleted file mode 100644
index 22f31f6f6..000000000
--- a/module/plugins/crypter/Go4UpCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class Go4UpCom(SimpleCrypter):
- __name__ = "Go4UpCom"
- __type__ = "crypter"
- __version__ = "0.12"
-
- __pattern__ = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Go4Up.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("rlindner81", "rlindner81@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'(http://go4up\.com/rd/.+?)<'
-
- NAME_PATTERN = r'<title>Download (.+?)<'
-
- OFFLINE_PATTERN = r'>\s*(404 Page Not Found|File not Found|Mirror does not exist)'
-
-
- def getLinks(self):
- links = []
-
- m = re.search(r'(/download/gethosts/.+?)"', self.html)
- if m:
- self.html = self.load(urlparse.urljoin("http://go4up.com/", m.group(1)))
- pages = [self.load(url) for url in re.findall(self.LINK_PATTERN, self.html)]
- else:
- pages = [self.html]
-
- for html in pages:
- try:
- links.append(re.search(r'<b><a href="(.+?)"', html).group(1))
- except Exception:
- continue
-
- return links
-
-
-getInfo = create_getInfo(Go4UpCom)
diff --git a/module/plugins/crypter/GooGl.py b/module/plugins/crypter/GooGl.py
deleted file mode 100644
index b5f0c0447..000000000
--- a/module/plugins/crypter/GooGl.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter
-from module.common.json_layer import json_loads
-
-
-class GooGl(Crypter):
- __name__ = "GooGl"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?goo\.gl/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Goo.gl decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- API_URL = "https://www.googleapis.com/urlshortener/v1/url"
-
-
- def decrypt(self, pyfile):
- rep = self.load(self.API_URL, get={'shortUrl': pyfile.url})
- self.logDebug("JSON data: " + rep)
- rep = json_loads(rep)
-
- if 'longUrl' in rep:
- self.urls = [rep['longUrl']]
- else:
- self.fail(_("Unable to expand shortened link"))
diff --git a/module/plugins/crypter/HoerbuchIn.py b/module/plugins/crypter/HoerbuchIn.py
deleted file mode 100644
index ccc3ab664..000000000
--- a/module/plugins/crypter/HoerbuchIn.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
-
-from module.plugins.Crypter import Crypter
-
-
-class HoerbuchIn(Crypter):
- __name__ = "HoerbuchIn"
- __type__ = "crypter"
- __version__ = "0.60"
-
- __pattern__ = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out\.php\?.+|protection/folder_\d+\.html)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Hoerbuch.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de")]
-
-
- article = re.compile("http://(?:www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/")
- protection = re.compile("http://(?:www\.)?hoerbuch\.in/protection/folder_\d+.html")
-
-
- def decrypt(self, pyfile):
- self.pyfile = pyfile
-
- if self.article.match(pyfile.url):
- html = self.load(pyfile.url)
- soup = BeautifulSoup(html, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
-
- abookname = soup.find("a", attrs={"rel": "bookmark"}).text
- for a in soup.findAll("a", attrs={"href": self.protection}):
- package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1])
- links = self.decryptFolder(a['href'])
-
- self.packages.append((package, links, package))
- else:
- self.urls = self.decryptFolder(pyfile.url)
-
-
- def decryptFolder(self, url):
- m = self.protection.search(url)
- if m is None:
- self.fail(_("Bad URL"))
- url = m.group(0)
-
- self.pyfile.url = url
- html = self.load(url, post={"viewed": "adpg"})
-
- links = []
- pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
- for hoster, lid in pattern.findall(html):
- self.req.lastURL = url
- self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid))
- links.append(self.req.lastEffectiveURL)
-
- return links
diff --git a/module/plugins/crypter/HotfileComFolder.py b/module/plugins/crypter/HotfileComFolder.py
deleted file mode 100644
index 4f40587ad..000000000
--- a/module/plugins/crypter/HotfileComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class HotfileComFolder(DeadCrypter):
- __name__ = "HotfileComFolder"
- __type__ = "crypter"
- __version__ = "0.30"
-
- __pattern__ = r'https?://(?:www\.)?hotfile\.com/list/\w+/\w+'
- __config__ = []
-
- __description__ = """Hotfile.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
-getInfo = create_getInfo(HotfileComFolder)
diff --git a/module/plugins/crypter/ILoadTo.py b/module/plugins/crypter/ILoadTo.py
deleted file mode 100644
index f3415706d..000000000
--- a/module/plugins/crypter/ILoadTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class ILoadTo(DeadCrypter):
- __name__ = "ILoadTo"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?iload\.to/go/\d+-[\w.-]+/'
- __config__ = []
-
- __description__ = """Iload.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(ILoadTo)
diff --git a/module/plugins/crypter/ImgurComAlbum.py b/module/plugins/crypter/ImgurComAlbum.py
deleted file mode 100644
index 9ef7797af..000000000
--- a/module/plugins/crypter/ImgurComAlbum.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-from module.utils import uniqify
-
-
-class ImgurComAlbum(SimpleCrypter):
- __name__ = "ImgurComAlbum"
- __type__ = "crypter"
- __version__ = "0.51"
-
- __pattern__ = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Imgur.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com")]
-
-
- NAME_PATTERN = r'(?P<N>.+?) - Imgur'
- LINK_PATTERN = r'i\.imgur\.com/\w{7}s?\.(?:jpeg|jpg|png|gif|apng)'
-
-
- def getLinks(self):
- f = lambda url: "http://" + re.sub(r'(\w{7})s\.', r'\1.', url)
- return uniqify(map(f, re.findall(self.LINK_PATTERN, self.html)))
-
-
-getInfo = create_getInfo(ImgurComAlbum)
diff --git a/module/plugins/crypter/LetitbitNetFolder.py b/module/plugins/crypter/LetitbitNetFolder.py
deleted file mode 100644
index b60d754a7..000000000
--- a/module/plugins/crypter/LetitbitNetFolder.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class LetitbitNetFolder(Crypter):
- __name__ = "LetitbitNetFolder"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?letitbit\.net/folder/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Letitbit.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("DHMH", "webmaster@pcProfil.de"),
- ("z00nx", "z00nx0@gmail.com")]
-
-
- FOLDER_PATTERN = r'<table>(.*)</table>'
- LINK_PATTERN = r'<a href="(.+?)" target="_blank">'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- folder = re.search(self.FOLDER_PATTERN, html, re.S)
- if folder is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
diff --git a/module/plugins/crypter/LinkCryptWs.py b/module/plugins/crypter/LinkCryptWs.py
deleted file mode 100644
index d3e75aad4..000000000
--- a/module/plugins/crypter/LinkCryptWs.py
+++ /dev/null
@@ -1,322 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import re
-
-import pycurl
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.utils import html_unescape
-
-
-class LinkCryptWs(Crypter):
- __name__ = "LinkCryptWs"
- __type__ = "crypter"
- __version__ = "0.08"
-
- __pattern__ = r'http://(?:www\.)?linkcrypt\.ws/(dir|container)/(?P<ID>\w+)'
-
- __description__ = """LinkCrypt.ws decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("kagenoshin", "kagenoshin[AT]gmx[DOT]ch"),
- ("glukgluk", None),
- ("Gummibaer", None)]
-
-
- CRYPTED_KEY = "crypted"
- JK_KEY = "jk"
-
-
- def setup(self):
- self.captcha = False
- self.links = []
- self.sources = ['cnl', 'web', 'dlc', 'rsdf', 'ccf']
-
-
- def prepare(self):
- # Init
- self.fileid = re.match(self.__pattern__, self.pyfile.url).group('ID')
-
- self.req.cj.setCookie("linkcrypt.ws", "language", "en")
-
- # Request package
- self.req.http.c.setopt(pycurl.USERAGENT, "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko") #: better chance to not get those key-captchas
- self.html = self.load(self.pyfile.url)
-
-
- def decrypt(self, pyfile):
- if not self.js:
- self.fail(_("Missing JS Engine"))
-
- self.prepare()
-
- if not self.isOnline():
- self.offline()
-
- if self.isKeyCaptchaProtected():
- self.retry(8, 15, _("Can't handle Key-Captcha"))
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleCaptchaErrors()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- # get unrar password
- self.getunrarpw()
-
- # Get package name and folder
- package_name, folder_name = self.getPackageInfo()
-
- #get the container definitions from script section
- self.get_container_html()
-
- # Extract package links
- for type in self.sources:
- links = self.handleLinkSource(type)
-
- if links:
- self.links.extend(links)
- break
-
- if self.links:
- self.packages = [(package_name, self.links, folder_name)]
-
-
- def isOnline(self):
- if "<title>Linkcrypt.ws // Error 404</title>" in self.html:
- self.logDebug("folder doesen't exist anymore")
- return False
- else:
- return True
-
-
- def isPasswordProtected(self):
- if "Authorizing" in self.html:
- self.logDebug("Links are password protected")
- return True
- else:
- return False
-
-
- def isCaptchaProtected(self):
- if 'id="captcha">' in self.html:
- self.logDebug("Links are captcha protected")
- return True
- else:
- return False
-
-
- def isKeyCaptchaProtected(self):
- if re.search(r'>If the folder does not open after klick on <', self.html, re.I):
- return True
- else:
- return False
-
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
-
- if password:
- self.logDebug("Submitting password [%s] for protected links" % password)
- self.html = self.load(self.pyfile.url, post={"password": password, 'x': "0", 'y': "0"})
- else:
- self.fail(_("Folder is password protected"))
-
-
- def unlockCaptchaProtection(self):
- captcha_url = re.search(r'<form.*?id\s*?=\s*?"captcha"[^>]*?>.*?<\s*?input.*?src="(.+?)"', self.html, re.I | re.S).group(1)
- captcha_code = self.decryptCaptcha(captcha_url, forceUser=True, imgtype="gif", result_type='positional')
-
- self.html = self.load(self.pyfile.url, post={"x": captcha_code[0], "y": captcha_code[1]})
-
-
- def getPackageInfo(self):
- name = self.pyfile.package().name
- folder = self.pyfile.package().folder
-
- self.logDebug("Defaulting to pyfile name [%s] and folder [%s] for package" % (name, folder))
-
- return name, folder
-
-
- def getunrarpw(self):
- sitein = self.html
- indexi = sitein.find("|source|") + 8
- indexe = sitein.find("|",indexi)
-
- unrarpw = sitein[indexi:indexe]
-
- if not (unrarpw == "Password" or "Dateipasswort") :
- self.logDebug("File password set to: [%s]"% unrarpw)
- self.pyfile.package().password = unrarpw
-
-
- def handleErrors(self):
- if self.isPasswordProtected():
- self.fail(_("Incorrect password"))
-
-
- def handleCaptchaErrors(self):
- if self.captcha:
- if "Your choice was wrong!" in self.html:
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
-
- def handleLinkSource(self, type):
- if type == 'cnl':
- return self.handleCNL2()
-
- elif type == 'web':
- return self.handleWebLinks()
-
- elif type in ('rsdf', 'ccf', 'dlc'):
- return self.handleContainer(type)
-
- else:
- self.fail(_("Unknown source type: %s") % type) #@TODO: Replace with self.error in 0.4.10
-
-
- def handleWebLinks(self):
- self.logDebug("Search for Web links ")
-
- package_links = []
- pattern = r'<form action="http://linkcrypt.ws/out.html"[^>]*?>.*?<input[^>]*?value="(.+?)"[^>]*?name="file"'
- ids = re.findall(pattern, self.html, re.I | re.S)
-
- self.logDebug("Decrypting %d Web links" % len(ids))
-
- for idx, weblink_id in enumerate(ids):
- try:
- self.logDebug("Decrypting Web link %d, %s" % (idx + 1, weblink_id))
-
- res = self.load("http://linkcrypt.ws/out.html", post = {'file':weblink_id})
-
- indexs = res.find("window.location =") + 19
- indexe = res.find('"', indexs)
-
- link2 = res[indexs:indexe]
-
- self.logDebug(link2)
-
- link2 = html_unescape(link2)
- package_links.append(link2)
-
- except Exception, detail:
- self.logDebug("Error decrypting Web link %s, %s" % (weblink_id, detail))
-
- return package_links
-
-
- def get_container_html(self):
- self.container_html = []
-
- script = re.search(r'<div.*?id="ad_cont".*?<script.*?javascrip[^>]*?>(.*?)</script', self.html, re.I | re.S)
-
- if script:
- container_html_text = script.group(1)
- container_html_text.strip()
- self.container_html = container_html_text.splitlines()
-
-
- def handle_javascript(self, line):
- return self.js.eval(line.replace('{}))',"{}).replace('document.open();document.write','').replace(';document.close();',''))"))
-
-
- def handleContainer(self, type):
- package_links = []
- type = type.lower()
-
- self.logDebug('Search for %s Container links' % type.upper())
-
- if not type.isalnum(): # check to prevent broken re-pattern (cnl2,rsdf,ccf,dlc,web are all alpha-numeric)
- self.fail(_("Unknown container type: %s") % type) #@TODO: Replace with self.error in 0.4.10
-
- for line in self.container_html:
- if type in line:
- jseval = self.handle_javascript(line)
- clink = re.search(r'href=["\'](["\']+)', jseval, re.I)
-
- if not clink:
- continue
-
- self.logDebug("clink avaible")
-
- package_name, folder_name = self.getPackageInfo()
- self.logDebug("Added package with name %s.%s and container link %s" %( package_name, type, clink.group(1)))
- self.core.api.uploadContainer( "%s.%s" %(package_name, type), self.load(clink.group(1)))
- return "Found it"
-
- return package_links
-
-
- def handleCNL2(self):
- self.logDebug("Search for CNL links")
-
- package_links = []
- cnl_line = None
-
- for line in self.container_html:
- if "cnl" in line:
- cnl_line = line
- break
-
- if cnl_line:
- self.logDebug("cnl_line gefunden")
-
- try:
- cnl_section = self.handle_javascript(cnl_line)
- (vcrypted, vjk) = self._getCipherParams(cnl_section)
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except Exception:
- self.logError(_("Unable to decrypt CNL links (JS Error) try to get over links"))
- return self.handleWebLinks()
-
- return package_links
-
-
- def _getCipherParams(self, cnl_section):
- # Get jk
- jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.JK_KEY
- vjk = re.findall(jk_re, cnl_section)
-
- # Get crypted
- crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.CRYPTED_KEY
- vcrypted = re.findall(crypted_re, cnl_section)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- key = binascii.unhexlify(jreturn)
-
- self.logDebug("JsEngine returns value [%s]" % jreturn)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Package has %d links" % len(links))
-
- return links
diff --git a/module/plugins/crypter/LinkSaveIn.py b/module/plugins/crypter/LinkSaveIn.py
deleted file mode 100644
index 040305bd9..000000000
--- a/module/plugins/crypter/LinkSaveIn.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class LinkSaveIn(SimpleDereferer):
- __name__ = "LinkSaveIn"
- __type__ = "crypter"
- __version__ = "2.03"
-
- __pattern__ = r'https?://(?:www\.)?linksave\.in/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """LinkSave.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- COOKIES = [("linksave.in", "Linksave_Language", "english")]
-
- OFFLINE_PATTERN = r'>(Error )?404 -'
diff --git a/module/plugins/crypter/LinkdecrypterCom.py b/module/plugins/crypter/LinkdecrypterCom.py
deleted file mode 100644
index 0704214d0..000000000
--- a/module/plugins/crypter/LinkdecrypterCom.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class LinkdecrypterCom(Crypter):
- __name__ = "LinkdecrypterCom"
- __type__ = "crypter"
- __version__ = "0.29"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Linkdecrypter.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("flowlee", None)]
-
-
- TEXTAREA_PATTERN = r'<textarea name="links" wrap="off" readonly="1" class="caja_des">(.+)</textarea>'
- PASSWORD_PATTERN = r'<input type="text" name="password"'
- CAPTCHA_PATTERN = r'<img class="captcha" src="(.+?)"(.*?)>'
- REDIR_PATTERN = r'<i>(Click <a href="./">here</a> if your browser does not redirect you).</i>'
-
-
- def setup(self):
- self.password = self.getPassword()
- self.req.setOption("timeout", 300)
-
-
- def decrypt(self, pyfile):
- retries = 5
-
- post_dict = {"link_cache": "on", "pro_links": pyfile.url, "modo_links": "text"}
- self.html = self.load('http://linkdecrypter.com/', post=post_dict, decode=True)
-
- while retries:
- m = re.search(self.TEXTAREA_PATTERN, self.html, re.S)
- if m:
- self.urls = [x for x in m.group(1).splitlines() if '[LINK-ERROR]' not in x]
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- captcha_url = 'http://linkdecrypter.com/' + m.group(1)
- result_type = "positional" if "getPos" in m.group(2) else "textual"
-
- m = re.search(r"<p><i><b>([^<]+)</b></i></p>", self.html)
- msg = m.group(1) if m else ""
- self.logInfo(_("Captcha protected link"), result_type, msg)
-
- captcha = self.decryptCaptcha(captcha_url, result_type=result_type)
- if result_type == "positional":
- captcha = "%d|%d" % captcha
- self.html = self.load('http://linkdecrypter.com/', post={"captcha": captcha}, decode=True)
- retries -= 1
-
- elif self.PASSWORD_PATTERN in self.html:
- if self.password:
- self.logInfo(_("Password protected link"))
- self.html = self.load('http://linkdecrypter.com/', post={'password': self.password}, decode=True)
- else:
- self.fail(_("Missing password"))
-
- else:
- retries -= 1
- self.html = self.load('http://linkdecrypter.com/', decode=True)
diff --git a/module/plugins/crypter/LixIn.py b/module/plugins/crypter/LixIn.py
deleted file mode 100644
index 66a0cf339..000000000
--- a/module/plugins/crypter/LixIn.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class LixIn(Crypter):
- __name__ = "LixIn"
- __type__ = "crypter"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?lix\.in/(?P<ID>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Lix.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
- CAPTCHA_PATTERN = r'<img src="(captcha_img\.php\?.*?)"'
- SUBMIT_PATTERN = r'value=\'continue.*?\''
- LINK_PATTERN = r'name="ifram" src="(.*?)"'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- m = re.match(self.__pattern__, url)
- if m is None:
- self.error(_("Unable to identify file ID"))
-
- id = m.group('ID')
- self.logDebug("File id is %s" % id)
-
- self.html = self.load(url, decode=True)
-
- m = re.search(self.SUBMIT_PATTERN, self.html)
- if m is None:
- self.error(_("Link doesn't seem valid"))
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- for _i in xrange(5):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- self.logDebug("Trying captcha")
- captcharesult = self.decryptCaptcha("http://lix.in/" + m.group(1))
- self.html = self.load(url, decode=True,
- post={"capt": captcharesult, "submit": "submit", "tiny": id})
- else:
- self.logDebug("No captcha/captcha solved")
- else:
- self.html = self.load(url, decode=True, post={"submit": "submit", "tiny": id})
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.error(_("Unable to find destination url"))
- else:
- self.urls = [m.group(1)]
- self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/module/plugins/crypter/LofCc.py b/module/plugins/crypter/LofCc.py
deleted file mode 100644
index 3cac0fbf2..000000000
--- a/module/plugins/crypter/LofCc.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class LofCc(DeadCrypter):
- __name__ = "LofCc"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?lof\.cc/(.+)'
- __config__ = []
-
- __description__ = """Lof.cc decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(LofCc)
diff --git a/module/plugins/crypter/MBLinkInfo.py b/module/plugins/crypter/MBLinkInfo.py
deleted file mode 100644
index 82c2d9719..000000000
--- a/module/plugins/crypter/MBLinkInfo.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MBLinkInfo(DeadCrypter):
- __name__ = "MBLinkInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
- __config__ = []
-
- __description__ = """MBLink.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Gummibaer", "Gummibaer@wiki-bierkiste.de"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(MBLinkInfo)
diff --git a/module/plugins/crypter/MediafireComFolder.py b/module/plugins/crypter/MediafireComFolder.py
deleted file mode 100644
index c1612bb6a..000000000
--- a/module/plugins/crypter/MediafireComFolder.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-from module.plugins.hoster.MediafireCom import checkHTMLHeader
-from module.common.json_layer import json_loads
-
-
-class MediafireComFolder(Crypter):
- __name__ = "MediafireComFolder"
- __type__ = "crypter"
- __version__ = "0.14"
-
- __pattern__ = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Mediafire.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_KEY_PATTERN = r'var afI= \'(\w+)'
- LINK_PATTERN = r'<meta property="og:url" content="http://www\.mediafire\.com/\?(\w+)"/>'
-
-
- def decrypt(self, pyfile):
- url, result = checkHTMLHeader(pyfile.url)
- self.logDebug("Location (%d): %s" % (result, url))
-
- if result == 0:
- # load and parse html
- html = self.load(pyfile.url)
- m = re.search(self.LINK_PATTERN, html)
- if m:
- # file page
- self.urls.append("http://www.mediafire.com/file/%s" % m.group(1))
- else:
- # folder page
- m = re.search(self.FOLDER_KEY_PATTERN, html)
- if m:
- folder_key = m.group(1)
- self.logDebug("FOLDER KEY: %s" % folder_key)
-
- json_resp = json_loads(self.load("http://www.mediafire.com/api/folder/get_info.php",
- get={'folder_key' : folder_key,
- 'response_format': "json",
- 'version' : 1}))
- #self.logInfo(json_resp)
- if json_resp['response']['result'] == "Success":
- for link in json_resp['response']['folder_info']['files']:
- self.urls.append("http://www.mediafire.com/file/%s" % link['quickkey'])
- else:
- self.fail(json_resp['response']['message'])
- elif result == 1:
- self.offline()
- else:
- self.urls.append(url)
diff --git a/module/plugins/crypter/MegaCoNzFolder.py b/module/plugins/crypter/MegaCoNzFolder.py
deleted file mode 100644
index bd135ac5f..000000000
--- a/module/plugins/crypter/MegaCoNzFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class MegaCoNzFolder(Crypter):
- __name__ = "MegaCoNzFolder"
- __type__ = "crypter"
- __version__ = "0.04"
-
- __pattern__ = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#F!(?P<ID>[\w^_]+)!(?P<KEY>[\w,\\-]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Mega.co.nz folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def setup(self):
- self.req.setOption("timeout", 300)
-
-
- def decrypt(self, pyfile):
- url = "https://mega.co.nz/#F!%s!%s" % re.match(self.__pattern__, pyfile.url).groups()
- self.html = self.load("http://rapidgen.org/linkfinder", post={'linklisturl': url})
- self.urls = re.findall(r'(https://mega.co.nz/#N!.+?)<', self.html)
-
- if not self.urls: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
diff --git a/module/plugins/crypter/MegaRapidCzFolder.py b/module/plugins/crypter/MegaRapidCzFolder.py
deleted file mode 100644
index fadd6dbed..000000000
--- a/module/plugins/crypter/MegaRapidCzFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class MegaRapidCzFolder(SimpleCrypter):
- __name__ = "MegaRapidCzFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/slozka/\d+/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Share-Rapid.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<td class="soubor".*?><a href="(.+?)">'
-
-
-getInfo = create_getInfo(MegaRapidCzFolder)
diff --git a/module/plugins/crypter/MegauploadComFolder.py b/module/plugins/crypter/MegauploadComFolder.py
deleted file mode 100644
index 1af9d556d..000000000
--- a/module/plugins/crypter/MegauploadComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MegauploadComFolder(DeadCrypter):
- __name__ = "MegauploadComFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+'
- __config__ = []
-
- __description__ = """Megaupload.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(MegauploadComFolder)
diff --git a/module/plugins/crypter/Movie2KTo.py b/module/plugins/crypter/Movie2KTo.py
deleted file mode 100644
index 76bf702ac..000000000
--- a/module/plugins/crypter/Movie2KTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class Movie2KTo(DeadCrypter):
- __name__ = "Movie2KTo"
- __type__ = "crypter"
- __version__ = "0.51"
-
- __pattern__ = r'http://(?:www\.)?movie2k\.to/(.+)\.html'
- __config__ = []
-
- __description__ = """Movie2k.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("4Christopher", "4Christopher@gmx.de")]
-
-
-getInfo = create_getInfo(Movie2KTo)
diff --git a/module/plugins/crypter/MultiUpOrg.py b/module/plugins/crypter/MultiUpOrg.py
deleted file mode 100644
index b676c3029..000000000
--- a/module/plugins/crypter/MultiUpOrg.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class MultiUpOrg(SimpleCrypter):
- __name__ = "MultiUpOrg"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """MultiUp.org crypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<title>.*(?:Project|Projet|ownload|élécharger) (?P<N>.+?) (\(|- )'
-
-
- def getLinks(self):
- m_type = re.match(self.__pattern__, self.pyfile.url).group('TYPE')
-
- if m_type == "project":
- pattern = r'\n(http://www\.multiup\.org/(?:en|fr)/download/.*)'
- else:
- pattern = r'style="width:97%;text-align:left".*\n.*href="(.*)"'
- if m_type == "download":
- dl_pattern = r'href="(.*)">.*\n.*<h5>DOWNLOAD</h5>'
- miror_page = urlparse.urljoin("http://www.multiup.org", re.search(dl_pattern, self.html).group(1))
- self.html = self.load(miror_page)
-
- return re.findall(pattern, self.html)
-
-
-getInfo = create_getInfo(MultiUpOrg)
diff --git a/module/plugins/crypter/MultiloadCz.py b/module/plugins/crypter/MultiloadCz.py
deleted file mode 100644
index eea689a09..000000000
--- a/module/plugins/crypter/MultiloadCz.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class MultiloadCz(Crypter):
- __name__ = "MultiloadCz"
- __type__ = "crypter"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:[^/]*\.)?multiload\.cz/(stahnout|slozka)/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True),
- ("usedHoster" , "str" , "Prefered hoster list (bar-separated)", "" ),
- ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
-
- __description__ = """Multiload.cz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_PATTERN = r'<form action="" method="get"><textarea.*?>([^>]*)</textarea></form>'
- LINK_PATTERN = r'<p class="manager-server"><strong>([^<]+)</strong></p><p class="manager-linky"><a href="(.+?)">'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- if re.match(self.__pattern__, pyfile.url).group(1) == "slozka":
- m = re.search(self.FOLDER_PATTERN, self.html)
- if m:
- self.urls.extend(m.group(1).split())
- else:
- m = re.findall(self.LINK_PATTERN, self.html)
- if m:
- prefered_set = set(self.getConfig('usedHoster').split('|'))
- self.urls.extend(x[1] for x in m if x[0] in prefered_set)
-
- if not self.urls:
- ignored_set = set(self.getConfig('ignoredHoster').split('|'))
- self.urls.extend(x[1] for x in m if x[0] not in ignored_set)
diff --git a/module/plugins/crypter/MultiuploadCom.py b/module/plugins/crypter/MultiuploadCom.py
deleted file mode 100644
index 347b7e5af..000000000
--- a/module/plugins/crypter/MultiuploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MultiuploadCom(DeadCrypter):
- __name__ = "MultiuploadCom"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?multiupload\.(com|nl)/\w+'
- __config__ = []
-
- __description__ = """ MultiUpload.com decrypter plugin """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(MultiuploadCom)
diff --git a/module/plugins/crypter/NCryptIn.py b/module/plugins/crypter/NCryptIn.py
deleted file mode 100644
index 8ceb9d3c8..000000000
--- a/module/plugins/crypter/NCryptIn.py
+++ /dev/null
@@ -1,310 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import re
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class NCryptIn(Crypter):
- __name__ = "NCryptIn"
- __type__ = "crypter"
- __version__ = "1.34"
-
- __pattern__ = r'http://(?:www\.)?ncrypt\.in/(?P<TYPE>folder|link|frame)-([^/\?]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """NCrypt.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- JK_KEY = "jk"
- CRYPTED_KEY = "crypted"
-
- NAME_PATTERN = r'<meta name="description" content="(?P<N>.+?)"'
-
-
- def setup(self):
- self.package = None
- self.cleanedHtml = None
- self.links_source_order = ["cnl2", "rsdf", "ccf", "dlc", "web"]
- self.protection_type = None
-
-
- def decrypt(self, pyfile):
- # Init
- self.package = pyfile.package()
- package_links = []
- package_name = self.package.name
- folder_name = self.package.folder
-
- # Deal with single links
- if self.isSingleLink():
- package_links.extend(self.handleSingleLink())
-
- # Deal with folders
- else:
-
- # Request folder home
- self.html = self.requestFolderHome()
- self.cleanedHtml = self.removeHtmlCrap(self.html)
- if not self.isOnline():
- self.offline()
-
- # Check for folder protection
- if self.isProtected():
- self.html = self.unlockProtection()
- self.cleanedHtml = self.removeHtmlCrap(self.html)
- self.handleErrors()
-
- # Prepare package name and folder
- (package_name, folder_name) = self.getPackageInfo()
-
- # Extract package links
- for link_source_type in self.links_source_order:
- package_links.extend(self.handleLinkSource(link_source_type))
- if package_links: # use only first source which provides links
- break
- package_links = set(package_links)
-
- # Pack and return links
- if package_links:
- self.packages = [(package_name, package_links, folder_name)]
-
-
- def isSingleLink(self):
- link_type = re.match(self.__pattern__, self.pyfile.url).group('TYPE')
- return link_type in ("link", "frame")
-
-
- def requestFolderHome(self):
- return self.load(self.pyfile.url, decode=True)
-
-
- def removeHtmlCrap(self, content):
- patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")',
- r'display:none;">(.*?)</(div|span)>',
- r'<div\s+class="jdownloader"(.*?)</div>',
- r'<table class="global">(.*?)</table>',
- r'<iframe\s+style="display:none(.*?)</iframe>')
- for pattern in patterns:
- rexpr = re.compile(pattern, re.S)
- content = re.sub(rexpr, "", content)
- return content
-
-
- def isOnline(self):
- if "Your folder does not exist" in self.cleanedHtml:
- self.logDebug("File not m")
- return False
- return True
-
-
- def isProtected(self):
- form = re.search(r'<form.*?name.*?protected.*?>(.*?)</form>', self.cleanedHtml, re.S)
- if form:
- content = form.group(1)
- for keyword in ("password", "captcha"):
- if keyword in content:
- self.protection_type = keyword
- self.logDebug("Links are %s protected" % self.protection_type)
- return True
- return False
-
-
- def getPackageInfo(self):
- m = re.search(self.NAME_PATTERN, self.html)
- if m:
- name = folder = m.group('N').strip()
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
- else:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not m, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
- return name, folder
-
-
- def unlockProtection(self):
- postData = {}
-
- form = re.search(r'<form name="protected"(.*?)</form>', self.cleanedHtml, re.S).group(1)
-
- # Submit package password
- if "password" in form:
- password = self.getPassword()
- self.logDebug("Submitting password [%s] for protected links" % password)
- postData['password'] = password
-
- # Resolve anicaptcha
- if "anicaptcha" in form:
- self.logDebug("Captcha protected")
- captchaUri = re.search(r'src="(/temp/anicaptcha/.+?)"', form).group(1)
- captcha = self.decryptCaptcha("http://ncrypt.in" + captchaUri)
- self.logDebug("Captcha resolved [%s]" % captcha)
- postData['captcha'] = captcha
-
- # Resolve recaptcha
- if "recaptcha" in form:
- self.logDebug("ReCaptcha protected")
- captcha_key = re.search(r'\?k=(.*?)"', form).group(1)
- self.logDebug("Resolving ReCaptcha with key [%s]" % captcha_key)
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge(captcha_key)
- postData['recaptcha_challenge_field'] = challenge
- postData['recaptcha_response_field'] = response
-
- # Resolve circlecaptcha
- if "circlecaptcha" in form:
- self.logDebug("CircleCaptcha protected")
- captcha_img_url = "http://ncrypt.in/classes/captcha/circlecaptcha.php"
- coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
- postData['circle.x'] = coords[0]
- postData['circle.y'] = coords[1]
-
- # Unlock protection
- postData['submit_protected'] = 'Continue to folder'
- return self.load(self.pyfile.url, post=postData, decode=True)
-
-
- def handleErrors(self):
- if self.protection_type == "password":
- if "This password is invalid!" in self.cleanedHtml:
- self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
- self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry"))
-
- if self.protection_type == "captcha":
- if "The securitycheck was wrong!" in self.cleanedHtml:
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
-
- def handleLinkSource(self, link_source_type):
- # Check for JS engine
- require_js_engine = link_source_type in ("cnl2", "rsdf", "ccf", "dlc")
- if require_js_engine and not self.js:
- self.logDebug("No JS engine available, skip %s links" % link_source_type)
- return []
-
- # Select suitable handler
- if link_source_type == 'single':
- return self.handleSingleLink()
- if link_source_type == 'cnl2':
- return self.handleCNL2()
- elif link_source_type in ("rsdf", "ccf", "dlc"):
- return self.handleContainer(link_source_type)
- elif link_source_type == "web":
- return self.handleWebLinks()
- else:
- self.error(_('Unknown source type "%s"') % link_source_type)
-
-
- def handleSingleLink(self):
- self.logDebug("Handling Single link")
- package_links = []
-
- # Decrypt single link
- decrypted_link = self.decryptLink(self.pyfile.url)
- if decrypted_link:
- package_links.append(decrypted_link)
-
- return package_links
-
-
- def handleCNL2(self):
- self.logDebug("Handling CNL2 links")
- package_links = []
-
- if 'cnl2_output' in self.cleanedHtml:
- try:
- (vcrypted, vjk) = self._getCipherParams()
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except Exception:
- self.fail(_("Unable to decrypt CNL2 links"))
-
- return package_links
-
-
- def handleContainers(self):
- self.logDebug("Handling Container links")
- package_links = []
-
- pattern = r'/container/(rsdf|dlc|ccf)/(\w+)'
- containersLinks = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Container links" % len(containersLinks))
- for containerLink in containersLinks:
- link = "http://ncrypt.in/container/%s/%s.%s" % (containerLink[0], containerLink[1], containerLink[0])
- package_links.append(link)
-
- return package_links
-
-
- def handleWebLinks(self):
- self.logDebug("Handling Web links")
- pattern = r'(http://ncrypt\.in/link-.*?=)'
- links = re.findall(pattern, self.html)
-
- package_links = []
- self.logDebug("Decrypting %d Web links" % len(links))
- for i, link in enumerate(links):
- self.logDebug("Decrypting Web link %d, %s" % (i + 1, link))
- decrypted_link = self.decrypt(link)
- if decrypted_link:
- package_links.append(decrypted_link)
-
- return package_links
-
-
- def decryptLink(self, link):
- try:
- url = link.replace("link-", "frame-")
- link = self.load(url, just_header=True)['location']
- return link
- except Exception, detail:
- self.logDebug("Error decrypting link %s, %s" % (link, detail))
-
-
- def _getCipherParams(self):
- pattern = r'<input.*?name="%s".*?value="(.*?)"'
-
- # Get jk
- jk_re = pattern % NCryptIn.JK_KEY
- vjk = re.findall(jk_re, self.html)
-
- # Get crypted
- crypted_re = pattern % NCryptIn.CRYPTED_KEY
- vcrypted = re.findall(crypted_re, self.html)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Block has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/NetfolderIn.py b/module/plugins/crypter/NetfolderIn.py
deleted file mode 100644
index ac76c059e..000000000
--- a/module/plugins/crypter/NetfolderIn.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class NetfolderIn(SimpleCrypter):
- __name__ = "NetfolderIn"
- __type__ = "crypter"
- __version__ = "0.72"
-
- __pattern__ = r'http://(?:www\.)?netfolder\.in/(folder\.php\?folder_id=)?(?P<ID>\w+)(?(1)|/\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """NetFolder.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- NAME_PATTERN = r'<div class="Text">Inhalt des Ordners <span.*>(?P<N>.+)</span></div>'
-
-
- def prepare(self):
- super(NetfolderIn, self).prepare()
-
- # Check for password protection
- if self.isPasswordProtected():
- self.html = self.submitPassword()
- if not self.html:
- self.fail(_("Incorrect password, please set right password on Add package form and retry"))
-
-
- def isPasswordProtected(self):
- if '<input type="password" name="password"' in self.html:
- self.logDebug("Links are password protected")
- return True
- return False
-
-
- def submitPassword(self):
- # Gather data
- try:
- m = re.match(self.__pattern__, self.pyfile.url)
- id = m.group('ID')
- except AttributeError:
- self.logDebug("Unable to get package id from url [%s]" % self.pyfile.url)
- return
- url = "http://netfolder.in/folder.php?folder_id=" + id
- password = self.getPassword()
-
- # Submit package password
- post = {'password': password, 'save': 'Absenden'}
- self.logDebug("Submitting password [%s] for protected links with id [%s]" % (password, id))
- html = self.load(url, {}, post)
-
- # Check for invalid password
- if '<div class="InPage_Error">' in html:
- self.logDebug("Incorrect password, please set right password on Edit package form and retry")
- return None
-
- return html
-
-
- def getLinks(self):
- links = re.search(r'name="list" value="(.*?)"', self.html).group(1).split(",")
- self.logDebug("Package has %d links" % len(links))
- return links
-
-
-getInfo = create_getInfo(NetfolderIn)
diff --git a/module/plugins/crypter/NosvideoCom.py b/module/plugins/crypter/NosvideoCom.py
deleted file mode 100644
index 9808837c4..000000000
--- a/module/plugins/crypter/NosvideoCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class NosvideoCom(SimpleCrypter):
- __name__ = "NosvideoCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Nosvideo.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload\.com/\?d=\w+)"'
- NAME_PATTERN = r'<[tT]itle>Watch (?P<N>.+?)<'
-
-
-getInfo = create_getInfo(NosvideoCom)
diff --git a/module/plugins/crypter/OneKhDe.py b/module/plugins/crypter/OneKhDe.py
deleted file mode 100644
index 4dcb416f5..000000000
--- a/module/plugins/crypter/OneKhDe.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.unescape import unescape
-
-from module.plugins.Crypter import Crypter
-
-
-class OneKhDe(Crypter):
- __name__ = "OneKhDe"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?1kh\.de/f/'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """1kh.de decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
- def __init__(self, parent):
- Crypter.__init__(self, parent)
- self.parent = parent
-
-
- def file_exists(self):
- """ returns True or False
- """
- return True
-
-
- def proceed(self, url, location):
- url = self.parent.url
- self.html = self.load(url)
- link_ids = re.findall(r"<a id=\"DownloadLink_(\d*)\" href=\"http://1kh.de/", self.html)
- for id in link_ids:
- new_link = unescape(
- re.search("width=\"100%\" src=\"(.*)\"></iframe>", self.load("http://1kh.de/l/" + id)).group(1))
- self.urls.append(new_link)
diff --git a/module/plugins/crypter/OronComFolder.py b/module/plugins/crypter/OronComFolder.py
deleted file mode 100644
index 9e06bdf32..000000000
--- a/module/plugins/crypter/OronComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class OronComFolder(DeadCrypter):
- __name__ = "OronComFolder"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?oron\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Oron.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("DHMH", "webmaster@pcProfil.de")]
-
-
-getInfo = create_getInfo(OronComFolder)
diff --git a/module/plugins/crypter/PastebinCom.py b/module/plugins/crypter/PastebinCom.py
deleted file mode 100644
index 751b47cc9..000000000
--- a/module/plugins/crypter/PastebinCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class PastebinCom(SimpleCrypter):
- __name__ = "PastebinCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?pastebin\.com/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Pastebin.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
- NAME_PATTERN = r'<div class="paste_box_line1" title="(?P<N>.+?)">'
-
-
-getInfo = create_getInfo(PastebinCom)
diff --git a/module/plugins/crypter/QuickshareCzFolder.py b/module/plugins/crypter/QuickshareCzFolder.py
deleted file mode 100644
index 3e38d36b4..000000000
--- a/module/plugins/crypter/QuickshareCzFolder.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class QuickshareCzFolder(Crypter):
- __name__ = "QuickshareCzFolder"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?quickshare\.cz/slozka-\d+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Quickshare.cz folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_PATTERN = r'<textarea.*?>(.*?)</textarea>'
- LINK_PATTERN = r'(http://www\.quickshare\.cz/\S+)'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- m = re.search(self.FOLDER_PATTERN, html, re.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/module/plugins/crypter/RSLayerCom.py b/module/plugins/crypter/RSLayerCom.py
deleted file mode 100644
index cc3b23bbc..000000000
--- a/module/plugins/crypter/RSLayerCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class RSLayerCom(DeadCrypter):
- __name__ = "RSLayerCom"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?rs-layer\.com/directory-'
- __config__ = []
-
- __description__ = """RS-Layer.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(RSLayerCom)
diff --git a/module/plugins/crypter/RelinkUs.py b/module/plugins/crypter/RelinkUs.py
deleted file mode 100644
index ed3da1f9b..000000000
--- a/module/plugins/crypter/RelinkUs.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import binascii
-import re
-import os
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class RelinkUs(Crypter):
- __name__ = "RelinkUs"
- __type__ = "crypter"
- __version__ = "3.12"
-
- __pattern__ = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<ID>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Relink.us decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("AndroKev", "neureither.kevin@gmail.com")]
-
-
- PREFERRED_LINK_SOURCES = ["cnl2", "dlc", "web"]
-
- OFFLINE_TOKEN = r'<title>Tattooside'
-
- PASSWORD_TOKEN = r'container_password.php'
- PASSWORD_ERROR_ROKEN = r'You have entered an incorrect password'
- PASSWORD_SUBMIT_URL = r'http://www.relink.us/container_password.php'
-
- CAPTCHA_TOKEN = r'container_captcha.php'
- CAPTCHA_ERROR_ROKEN = r'You have solved the captcha wrong'
- CAPTCHA_IMG_URL = r'http://www.relink.us/core/captcha/circlecaptcha.php'
- CAPTCHA_SUBMIT_URL = r'http://www.relink.us/container_captcha.php'
-
- FILE_TITLE_REGEX = r'<th>Title</th><td>(.*)</td></tr>'
- FILE_NOTITLE = r'No title'
-
- CNL2_FORM_REGEX = r'<form id="cnl_form-(.*?)</form>'
- CNL2_FORMINPUT_REGEX = r'<input.*?name="%s".*?value="(.*?)"'
- CNL2_JK_KEY = "jk"
- CNL2_CRYPTED_KEY = "crypted"
-
- DLC_LINK_REGEX = r'<a href=".*?" class="dlc_button" target="_blank">'
- DLC_DOWNLOAD_URL = r'http://www.relink.us/download.php'
-
- WEB_FORWARD_REGEX = r'getFile\(\'(.+)\'\)'
- WEB_FORWARD_URL = r'http://www.relink.us/frame.php'
- WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(.+)"></iframe>'
-
-
- def setup(self):
- self.fileid = None
- self.package = None
- self.captcha = False
-
-
- def decrypt(self, pyfile):
- # Init
- self.initPackage(pyfile)
-
- # Request package
- self.requestPackage()
-
- # Check for online
- if not self.isOnline():
- self.offline()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleErrors()
-
- # Get package name and folder
- (package_name, folder_name) = self.getPackageInfo()
-
- # Extract package links
- package_links = []
- for sources in self.PREFERRED_LINK_SOURCES:
- package_links.extend(self.handleLinkSource(sources))
- if package_links: # use only first source which provides links
- break
- package_links = set(package_links)
-
- # Pack
- if package_links:
- self.packages = [(package_name, package_links, folder_name)]
-
-
- def initPackage(self, pyfile):
- self.fileid = re.match(self.__pattern__, pyfile.url).group('ID')
- self.package = pyfile.package()
-
-
- def requestPackage(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
-
- def isOnline(self):
- if self.OFFLINE_TOKEN in self.html:
- self.logDebug("File not found")
- return False
- return True
-
-
- def isPasswordProtected(self):
- if self.PASSWORD_TOKEN in self.html:
- self.logDebug("Links are password protected")
- return True
-
-
- def isCaptchaProtected(self):
- if self.CAPTCHA_TOKEN in self.html:
- self.logDebug("Links are captcha protected")
- return True
- return False
-
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
-
- self.logDebug("Submitting password [%s] for protected links" % password)
-
- if password:
- passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
- passwd_data = {'id': self.fileid, 'password': password, 'pw': 'submit'}
- self.html = self.load(passwd_url, post=passwd_data, decode=True)
-
-
- def unlockCaptchaProtection(self):
- self.logDebug("Request user positional captcha resolving")
- captcha_img_url = self.CAPTCHA_IMG_URL + "?id=%s" % self.fileid
- coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
- captcha_post_url = self.CAPTCHA_SUBMIT_URL + "?id=%s" % self.fileid
- captcha_post_data = {'button.x': coords[0], 'button.y': coords[1], 'captcha': 'submit'}
- self.html = self.load(captcha_post_url, post=captcha_post_data, decode=True)
-
-
- def getPackageInfo(self):
- name = folder = None
-
- # Try to get info from web
- m = re.search(self.FILE_TITLE_REGEX, self.html)
- if m:
- title = m.group(1).strip()
- if not self.FILE_NOTITLE in title:
- name = folder = title
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
-
- # Fallback to defaults
- if not name or not folder:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
-
- # Return package info
- return name, folder
-
-
- def handleErrors(self):
- if self.PASSWORD_ERROR_ROKEN in self.html:
- msg = "Incorrect password, please set right password on 'Edit package' form and retry"
- self.logDebug(msg)
- self.fail(_(msg))
-
- if self.captcha:
- if self.CAPTCHA_ERROR_ROKEN in self.html:
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
-
- def handleLinkSource(self, source):
- if source == 'cnl2':
- return self.handleCNL2Links()
- elif source == 'dlc':
- return self.handleDLCLinks()
- elif source == 'web':
- return self.handleWEBLinks()
- else:
- self.error(_('Unknown source type "%s"') % source)
-
-
- def handleCNL2Links(self):
- self.logDebug("Search for CNL2 links")
- package_links = []
- m = re.search(self.CNL2_FORM_REGEX, self.html, re.S)
- if m:
- cnl2_form = m.group(1)
- try:
- (vcrypted, vjk) = self._getCipherParams(cnl2_form)
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except Exception:
- self.logDebug("Unable to decrypt CNL2 links")
- return package_links
-
-
- def handleDLCLinks(self):
- self.logDebug("Search for DLC links")
- package_links = []
- m = re.search(self.DLC_LINK_REGEX, self.html)
- if m:
- container_url = self.DLC_DOWNLOAD_URL + "?id=%s&dlc=1" % self.fileid
- self.logDebug("Downloading DLC container link [%s]" % container_url)
- try:
- dlc = self.load(container_url)
- dlc_filename = self.fileid + ".dlc"
- dlc_filepath = save_join(self.config['general']['download_folder'], dlc_filename)
- with open(dlc_filepath, "wb") as f:
- f.write(dlc)
- package_links.append(dlc_filepath)
-
- except Exception:
- self.fail(_("Unable to download DLC container"))
-
- return package_links
-
-
- def handleWEBLinks(self):
- self.logDebug("Search for WEB links")
-
- package_links = []
- params = re.findall(self.WEB_FORWARD_REGEX, self.html)
-
- self.logDebug("Decrypting %d Web links" % len(params))
-
- for index, param in enumerate(params):
- try:
- url = self.WEB_FORWARD_URL + "?%s" % param
-
- self.logDebug("Decrypting Web link %d, %s" % (index + 1, url))
-
- res = self.load(url, decode=True)
- link = re.search(self.WEB_LINK_REGEX, res).group(1)
-
- package_links.append(link)
-
- except Exception, detail:
- self.logDebug("Error decrypting Web link %s, %s" % (index, detail))
-
- self.setWait(4)
- self.wait()
-
- return package_links
-
-
- def _getCipherParams(self, cnl2_form):
- # Get jk
- jk_re = self.CNL2_FORMINPUT_REGEX % self.CNL2_JK_KEY
- vjk = re.findall(jk_re, cnl2_form, re.I)
-
- # Get crypted
- crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
- vcrypted = re.findall(crypted_re, cnl2_form, re.I)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Package has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/SafelinkingNet.py b/module/plugins/crypter/SafelinkingNet.py
deleted file mode 100644
index 7f0915065..000000000
--- a/module/plugins/crypter/SafelinkingNet.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from BeautifulSoup import BeautifulSoup
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import SolveMedia
-
-
-class SafelinkingNet(Crypter):
- __name__ = "SafelinkingNet"
- __type__ = "crypter"
- __version__ = "0.14"
-
- __pattern__ = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Safelinking.net decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("quareevo", "quareevo@arcor.de")]
-
-
- SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w.-]+)';"
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- if re.match(self.__pattern__, url).group(1) == "d":
-
- header = self.load(url, just_header=True)
- if 'location' in header:
- self.urls = [header['location']]
- else:
- self.error(_("Couldn't find forwarded Link"))
-
- else:
- postData = {"post-protect": "1"}
-
- self.html = self.load(url)
-
- if "link-password" in self.html:
- postData['link-password'] = self.getPassword()
-
- if "altcaptcha" in self.html:
- for _i in xrange(5):
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captchaKey = m.group(1)
- captcha = SolveMedia(self)
- captchaProvider = "Solvemedia"
- else:
- self.fail(_("Error parsing captcha"))
-
- response, challenge = captcha.challenge(captchaKey)
- postData['adcopy_challenge'] = challenge
- postData['adcopy_response'] = response
-
- self.html = self.load(url, post=postData)
- if "The password you entered was incorrect" in self.html:
- self.fail(_("Incorrect Password"))
- if not "The CAPTCHA code you entered was wrong" in self.html:
- break
-
- pyfile.package().password = ""
- soup = BeautifulSoup(self.html)
- scripts = soup.findAll("script")
- for s in scripts:
- if "d_links" in s.text:
- break
- m = re.search('d_links":(\[.*?\])', s.text)
- if m:
- linkDict = json_loads(m.group(1))
- for link in linkDict:
- if not "http://" in link['full']:
- self.urls.append("https://safelinking.net/d/" + link['full'])
- else:
- self.urls.append(link['full'])
diff --git a/module/plugins/crypter/SecuredIn.py b/module/plugins/crypter/SecuredIn.py
deleted file mode 100644
index cbfa919ac..000000000
--- a/module/plugins/crypter/SecuredIn.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SecuredIn(DeadCrypter):
- __name__ = "SecuredIn"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?secured\.in/download-[\d]+-\w{8}\.html'
- __config__ = []
-
- __description__ = """Secured.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(SecuredIn)
diff --git a/module/plugins/crypter/SexuriaCom.py b/module/plugins/crypter/SexuriaCom.py
deleted file mode 100644
index 06dcf547e..000000000
--- a/module/plugins/crypter/SexuriaCom.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class SexuriaCom(Crypter):
- __name__ = "SexuriaCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sexuria\.com/(v1/)?(Pornos_Kostenlos_.+?_(\d+)\.html|dl_links_\d+_\d+\.html|id=\d+\&part=\d+\&link=\d+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Sexuria.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("NETHead", "NETHead.AT.gmx.DOT.net")]
-
-
- PATTERN_SUPPORTED_MAIN = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?Pornos_Kostenlos_.+?_(\d+)\.html', re.I)
- PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', re.I)
- PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', re.I)
- PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', re.I)
- PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', re.I | re.S)
- PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', re.I)
- PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', re.I)
-
-
- def decrypt(self, pyfile):
- # Init
- self.pyfile = pyfile
- self.package = pyfile.package()
-
- # Get package links
- package_name, self.links, folder_name, package_pwd = self.decryptLinks(self.pyfile.url)
- self.packages = [(package_name, self.links, folder_name)]
-
-
- def decryptLinks(self, url):
- linklist = []
- name = self.package.name
- folder = self.package.folder
- password = None
-
- if re.match(self.PATTERN_SUPPORTED_MAIN, url):
- # Processing main page
- html = self.load(url)
- links = re.findall(self.PATTERN_DL_LINK_PAGE, html)
- for link in links:
- linklist.append("http://sexuria.com/v1/" + link)
-
- elif re.match(self.PATTERN_SUPPORTED_REDIRECT, url):
- # Processing direct redirect link (out.php), redirecting to main page
- id = re.search(self.PATTERN_SUPPORTED_REDIRECT, url).group('ID')
- if id:
- linklist.append("http://sexuria.com/v1/Pornos_Kostenlos_liebe_%s.html" % id)
-
- elif re.match(self.PATTERN_SUPPORTED_CRYPT, url):
- # Extract info from main file
- id = re.search(self.PATTERN_SUPPORTED_CRYPT, url).group('ID')
- html = self.load("http://sexuria.com/v1/Pornos_Kostenlos_info_%s.html" % id, decode=True)
-
- title = re.search(self.PATTERN_TITLE, html).group('TITLE').strip()
- if title:
- name = folder = title
- self.logDebug("Package info found, name [%s] and folder [%s]" % (name, folder))
-
- pwd = re.search(self.PATTERN_PASSWORD, html).group('PWD')
- if pwd:
- password = pwd.strip()
- self.logDebug("Password info [%s] found" % password)
-
- # Process link (dl_link)
- html = self.load(url)
- links = re.findall(self.PATTERN_REDIRECT_LINKS, html)
- if len(links) == 0:
- self.LogError("Broken for link %s" % link)
- else:
- for link in links:
- link = link.replace("http://sexuria.com/", "http://www.sexuria.com/")
- finallink = self.load(link, just_header=True)['location']
- if not finallink or "sexuria.com/" in finallink:
- self.LogError("Broken for link %s" % link)
- else:
- linklist.append(finallink)
-
- # Debug log
- self.logDebug("%d supported links" % len(linklist))
- for i, link in enumerate(linklist):
- self.logDebug("Supported link %d, %s" % (i + 1, link))
-
- return name, linklist, folder, password
diff --git a/module/plugins/crypter/ShareLinksBiz.py b/module/plugins/crypter/ShareLinksBiz.py
deleted file mode 100644
index f17ba54d7..000000000
--- a/module/plugins/crypter/ShareLinksBiz.py
+++ /dev/null
@@ -1,279 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import re
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-
-
-class ShareLinksBiz(Crypter):
- __name__ = "ShareLinksBiz"
- __type__ = "crypter"
- __version__ = "1.14"
-
- __pattern__ = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Share-Links.biz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- def setup(self):
- self.baseUrl = None
- self.fileId = None
- self.package = None
- self.captcha = False
-
-
- def decrypt(self, pyfile):
- # Init
- self.initFile(pyfile)
-
- # Request package
- url = self.baseUrl + '/' + self.fileId
- self.html = self.load(url, decode=True)
-
- # Unblock server (load all images)
- self.unblockServer()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleErrors()
-
- # Extract package links
- package_links = []
- package_links.extend(self.handleWebLinks())
- package_links.extend(self.handleContainers())
- package_links.extend(self.handleCNL2())
- package_links = set(package_links)
-
- # Get package info
- package_name, package_folder = self.getPackageInfo()
-
- # Pack
- self.packages = [(package_name, package_links, package_folder)]
-
-
- def initFile(self, pyfile):
- url = pyfile.url
- if 's2l.biz' in url:
- url = self.load(url, just_header=True)['location']
- self.baseUrl = "http://www.%s.biz" % re.match(self.__pattern__, url).group(1)
- self.fileId = re.match(self.__pattern__, url).group('ID')
- self.package = pyfile.package()
-
-
- def isOnline(self):
- if "No usable content was found" in self.html:
- self.logDebug("File not found")
- return False
- return True
-
-
- def isPasswordProtected(self):
- if re.search(r'''<form.*?id="passwordForm".*?>''', self.html):
- self.logDebug("Links are protected")
- return True
- return False
-
-
- def isCaptchaProtected(self):
- if '<map id="captchamap"' in self.html:
- self.logDebug("Links are captcha protected")
- return True
- return False
-
-
- def unblockServer(self):
- imgs = re.findall(r"(/template/images/.*?\.gif)", self.html)
- for img in imgs:
- self.load(self.baseUrl + img)
-
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
- self.logDebug("Submitting password [%s] for protected links" % password)
- post = {"password": password, 'login': 'Submit form'}
- url = self.baseUrl + '/' + self.fileId
- self.html = self.load(url, post=post, decode=True)
-
-
- def unlockCaptchaProtection(self):
- # Get captcha map
- captchaMap = self._getCaptchaMap()
- self.logDebug("Captcha map with [%d] positions" % len(captchaMap.keys()))
-
- # Request user for captcha coords
- m = re.search(r'<img src="/captcha.gif\?d=(.*?)&amp;PHPSESSID=(.*?)&amp;legend=1"', self.html)
- captchaUrl = self.baseUrl + '/captcha.gif?d=%s&PHPSESSID=%s' % (m.group(1), m.group(2))
- self.logDebug("Waiting user for correct position")
- coords = self.decryptCaptcha(captchaUrl, forceUser=True, imgtype="gif", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
-
- # Resolve captcha
- href = self._resolveCoords(coords, captchaMap)
- if href is None:
- self.invalidCaptcha()
- self.retry(wait_time=5)
- url = self.baseUrl + href
- self.html = self.load(url, decode=True)
-
-
- def _getCaptchaMap(self):
- mapp = {}
- for m in re.finditer(r'<area shape="rect" coords="(.*?)" href="(.*?)"', self.html):
- rect = eval('(' + m.group(1) + ')')
- href = m.group(2)
- mapp[rect] = href
- return mapp
-
-
- def _resolveCoords(self, coords, captchaMap):
- x, y = coords
- for rect, href in captchaMap.iteritems():
- x1, y1, x2, y2 = rect
- if (x >= x1 and x <= x2) and (y >= y1 and y <= y2):
- return href
-
-
- def handleErrors(self):
- if "The inserted password was wrong" in self.html:
- self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
- self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry"))
-
- if self.captcha:
- if "Your choice was wrong" in self.html:
- self.invalidCaptcha()
- self.retry(wait_time=5)
- else:
- self.correctCaptcha()
-
-
- def getPackageInfo(self):
- name = folder = None
-
- # Extract from web package header
- title_re = r'<h2><img.*?/>(.*)</h2>'
- m = re.search(title_re, self.html, re.S)
- if m:
- title = m.group(1).strip()
- if 'unnamed' not in title:
- name = folder = title
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
-
- # Fallback to defaults
- if not name or not folder:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
-
- # Return package info
- return name, folder
-
-
- def handleWebLinks(self):
- package_links = []
- self.logDebug("Handling Web links")
-
- #@TODO: Gather paginated web links
- pattern = r'javascript:_get\(\'(.*?)\', \d+, \'\'\)'
- ids = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Web links" % len(ids))
- for i, ID in enumerate(ids):
- try:
- self.logDebug("Decrypting Web link %d, [%s]" % (i + 1, ID))
-
- dwLink = self.baseUrl + "/get/lnk/" + ID
- res = self.load(dwLink)
-
- code = re.search(r'frm/(\d+)', res).group(1)
- fwLink = self.baseUrl + "/get/frm/" + code
- res = self.load(fwLink)
-
- jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', res, re.S).group(1)
- jscode = self.js.eval("f = %s" % jscode)
- jslauncher = "window=''; parent={frames:{Main:{location:{href:''}}},location:''}; %s; parent.frames.Main.location.href"
-
- dlLink = self.js.eval(jslauncher % jscode)
-
- self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
-
- package_links.append(dlLink)
- except Exception, detail:
- self.logDebug("Error decrypting Web link [%s], %s" % (ID, detail))
- return package_links
-
-
- def handleContainers(self):
- package_links = []
- self.logDebug("Handling Container links")
-
- pattern = r'javascript:_get\(\'(.*?)\', 0, \'(rsdf|ccf|dlc)\'\)'
- containersLinks = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Container links" % len(containersLinks))
- for containerLink in containersLinks:
- link = "%s/get/%s/%s" % (self.baseUrl, containerLink[1], containerLink[0])
- package_links.append(link)
- return package_links
-
-
- def handleCNL2(self):
- package_links = []
- self.logDebug("Handling CNL2 links")
-
- if '/lib/cnl2/ClicknLoad.swf' in self.html:
- try:
- (crypted, jk) = self._getCipherParams()
- package_links.extend(self._getLinks(crypted, jk))
- except Exception:
- self.fail(_("Unable to decrypt CNL2 links"))
- return package_links
-
-
- def _getCipherParams(self):
- # Request CNL2
- code = re.search(r'ClicknLoad.swf\?code=(.*?)"', self.html).group(1)
- url = "%s/get/cnl2/%s" % (self.baseUrl, code)
- res = self.load(url)
- params = res.split(";;")
-
- # Get jk
- strlist = list(params[1].decode('base64'))
- jk = ''.join(strlist[::-1])
-
- # Get crypted
- strlist = list(params[2].decode('base64'))
- crypted = ''.join(strlist[::-1])
-
- # Log and return
- return crypted, jk
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Block has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/SharingmatrixComFolder.py b/module/plugins/crypter/SharingmatrixComFolder.py
deleted file mode 100644
index 4448dd691..000000000
--- a/module/plugins/crypter/SharingmatrixComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SharingmatrixComFolder(DeadCrypter):
- __name__ = "SharingmatrixComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Sharingmatrix.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(SharingmatrixComFolder)
diff --git a/module/plugins/crypter/SpeedLoadOrgFolder.py b/module/plugins/crypter/SpeedLoadOrgFolder.py
deleted file mode 100644
index ddde7dec2..000000000
--- a/module/plugins/crypter/SpeedLoadOrgFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SpeedLoadOrgFolder(DeadCrypter):
- __name__ = "SpeedLoadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.30"
-
- __pattern__ = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
- __config__ = []
-
- __description__ = """Speedload decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(SpeedLoadOrgFolder)
diff --git a/module/plugins/crypter/StealthTo.py b/module/plugins/crypter/StealthTo.py
deleted file mode 100644
index 5173421f1..000000000
--- a/module/plugins/crypter/StealthTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class StealthTo(DeadCrypter):
- __name__ = "StealthTo"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?stealth\.to/folder/.+'
- __config__ = []
-
- __description__ = """Stealth.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
-getInfo = create_getInfo(StealthTo)
diff --git a/module/plugins/crypter/TnyCz.py b/module/plugins/crypter/TnyCz.py
deleted file mode 100644
index 719c26449..000000000
--- a/module/plugins/crypter/TnyCz.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-import re
-
-
-class TnyCz(SimpleCrypter):
- __name__ = "TnyCz"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?tny\.cz/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Tny.cz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<title>(?P<N>.+) - .+</title>'
-
-
- def getLinks(self):
- m = re.search(r'<a id=\'save_paste\' href="(.+save\.php\?hash=.+)">', self.html)
- return re.findall(".+", self.load(m.group(1), decode=True)) if m else None
-
-
-getInfo = create_getInfo(TnyCz)
diff --git a/module/plugins/crypter/TrailerzoneInfo.py b/module/plugins/crypter/TrailerzoneInfo.py
deleted file mode 100644
index 10780dd45..000000000
--- a/module/plugins/crypter/TrailerzoneInfo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class TrailerzoneInfo(DeadCrypter):
- __name__ = "TrailerzoneInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?trailerzone\.info/.+'
- __config__ = []
-
- __description__ = """TrailerZone.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
-getInfo = create_getInfo(TrailerzoneInfo)
diff --git a/module/plugins/crypter/TurbobitNetFolder.py b/module/plugins/crypter/TurbobitNetFolder.py
deleted file mode 100644
index dcbd6a5d7..000000000
--- a/module/plugins/crypter/TurbobitNetFolder.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class TurbobitNetFolder(SimpleCrypter):
- __name__ = "TurbobitNetFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Turbobit.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'src=\'/js/lib/grid/icon/folder.png\'> <span>(?P<N>.+?)</span>'
-
-
- def _getLinks(self, id, page=1):
- gridFile = self.load("http://turbobit.net/downloadfolder/gridFile",
- get={"rootId": id, "rows": 200, "page": page}, decode=True)
- grid = json_loads(gridFile)
-
- if grid['rows']:
- for i in grid['rows']:
- yield i['id']
- for id in self._getLinks(id, page + 1):
- yield id
- else:
- return
-
-
- def getLinks(self):
- id = re.match(self.__pattern__, self.pyfile.url).group('ID')
- fixurl = lambda id: "http://turbobit.net/%s.html" % id
- return map(fixurl, self._getLinks(id))
-
-
-getInfo = create_getInfo(TurbobitNetFolder)
diff --git a/module/plugins/crypter/TusfilesNetFolder.py b/module/plugins/crypter/TusfilesNetFolder.py
deleted file mode 100644
index 1cc505b81..000000000
--- a/module/plugins/crypter/TusfilesNetFolder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import math
-import re
-import urlparse
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class TusfilesNetFolder(XFSCrypter):
- __name__ = "TusfilesNetFolder"
- __type__ = "crypter"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Tusfiles.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- PAGES_PATTERN = r'>\((\d+) \w+\)<'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'https://www.tusfiles.net/go/\g<ID>/')]
-
-
- def loadPage(self, page_n):
- return self.load(urlparse.urljoin(self.pyfile.url, str(page_n)), decode=True)
-
-
- def handlePages(self, pyfile):
- pages = re.search(self.PAGES_PATTERN, self.html)
- if pages:
- pages = int(math.ceil(int(pages.group('pages')) / 25.0))
- else:
- return
-
- for p in xrange(2, pages + 1):
- self.html = self.loadPage(p)
- self.links += self.getLinks()
-
-
-getInfo = create_getInfo(TusfilesNetFolder)
diff --git a/module/plugins/crypter/UlozToFolder.py b/module/plugins/crypter/UlozToFolder.py
deleted file mode 100644
index 3f2a60cf7..000000000
--- a/module/plugins/crypter/UlozToFolder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class UlozToFolder(Crypter):
- __name__ = "UlozToFolder"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(m|soubory)/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Uloz.to folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_PATTERN = r'<ul class="profile_files">(.*?)</ul>'
- LINK_PATTERN = r'<br /><a href="/(.+?)">.+?</a>'
- NEXT_PAGE_PATTERN = r'<a class="next " href="/(.+?)">&nbsp;</a>'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- new_links = []
- for i in xrange(1, 100):
- self.logInfo(_("Fetching links from page %i") % i)
- m = re.search(self.FOLDER_PATTERN, html, re.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- new_links.extend(re.findall(self.LINK_PATTERN, m.group(1)))
- m = re.search(self.NEXT_PAGE_PATTERN, html)
- if m:
- html = self.load("http://ulozto.net/" + m.group(1))
- else:
- break
- else:
- self.logInfo(_("Limit of 99 pages reached, aborting"))
-
- if new_links:
- self.urls = [map(lambda s: "http://ulozto.net/%s" % s, new_links)]
diff --git a/module/plugins/crypter/UploadableChFolder.py b/module/plugins/crypter/UploadableChFolder.py
deleted file mode 100644
index f1eb93323..000000000
--- a/module/plugins/crypter/UploadableChFolder.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class UploadableChFolder(SimpleCrypter):
- __name__ = "UploadableChFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?uploadable\.ch/list/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """ Uploadable.ch folder decrypter plugin """
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
- NAME_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<N>.+?)</div>'
- OFFLINE_PATTERN = r'We are sorry... The URL you entered cannot be found on the server.'
- TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
-
-
-getInfo = create_getInfo(UploadableChFolder)
diff --git a/module/plugins/crypter/UploadedToFolder.py b/module/plugins/crypter/UploadedToFolder.py
deleted file mode 100644
index d54ed47eb..000000000
--- a/module/plugins/crypter/UploadedToFolder.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class UploadedToFolder(SimpleCrypter):
- __name__ = "UploadedToFolder"
- __type__ = "crypter"
- __version__ = "0.42"
-
- __pattern__ = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<ID>\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """UploadedTo decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- PLAIN_PATTERN = r'<small class="date"><a href="([\w/]+)" onclick='
- NAME_PATTERN = r'<title>(?P<N>.+?)<'
-
-
- def getLinks(self):
- m = re.search(self.PLAIN_PATTERN, self.html)
- if m is None:
- self.error(_("PLAIN_PATTERN not found"))
-
- plain_link = urlparse.urljoin("http://uploaded.net/", m.group(1))
- return self.load(plain_link).split('\n')[:-1]
-
-
-getInfo = create_getInfo(UploadedToFolder)
diff --git a/module/plugins/crypter/WiiReloadedOrg.py b/module/plugins/crypter/WiiReloadedOrg.py
deleted file mode 100644
index c3c5b8222..000000000
--- a/module/plugins/crypter/WiiReloadedOrg.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class WiiReloadedOrg(DeadCrypter):
- __name__ = "WiiReloadedOrg"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+'
- __config__ = []
-
- __description__ = """Wii-Reloaded.org decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(WiiReloadedOrg)
diff --git a/module/plugins/crypter/WuploadComFolder.py b/module/plugins/crypter/WuploadComFolder.py
deleted file mode 100644
index c4171667e..000000000
--- a/module/plugins/crypter/WuploadComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class WuploadComFolder(DeadCrypter):
- __name__ = "WuploadComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?wupload\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Wupload.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(WuploadComFolder)
diff --git a/module/plugins/crypter/XFileSharingProFolder.py b/module/plugins/crypter/XFileSharingProFolder.py
deleted file mode 100644
index 8b850271d..000000000
--- a/module/plugins/crypter/XFileSharingProFolder.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class XFileSharingProFolder(XFSCrypter):
- __name__ = "XFileSharingProFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """XFileSharingPro dummy folder decrypter plugin for hook"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def _log(self, type, args):
- msg = " | ".join(str(a).strip() for a in args if a)
- logger = getattr(self.log, type)
- logger("%s: %s: %s" % (self.__name__, self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
-
-
- def init(self):
- super(XFileSharingProFolder, self).init()
-
- self.__pattern__ = self.core.pluginManager.crypterPlugins[self.__name__]['pattern']
-
- self.HOSTER_DOMAIN = re.match(self.__pattern__, self.pyfile.url).group("DOMAIN").lower()
- self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
-
- account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
-
- if account and account.canUse():
- self.account = account
-
- elif self.account:
- self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
-
- else:
- return
-
- self.user, data = self.account.selectAccount()
- self.req = self.account.getAccountRequest(self.user)
- self.premium = self.account.isPremium(self.user)
-
-
-getInfo = create_getInfo(XFileSharingProFolder)
diff --git a/module/plugins/crypter/XupPl.py b/module/plugins/crypter/XupPl.py
deleted file mode 100644
index 5ab6750c4..000000000
--- a/module/plugins/crypter/XupPl.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter
-
-
-class XupPl(Crypter):
- __name__ = "XupPl"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:[^/]*\.)?xup\.pl/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Xup.pl decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
- def decrypt(self, pyfile):
- header = self.load(pyfile.url, just_header=True)
- if 'location' in header:
- self.urls = [header['location']]
- else:
- self.fail(_("Unable to find link"))
diff --git a/module/plugins/crypter/YoutubeComFolder.py b/module/plugins/crypter/YoutubeComFolder.py
deleted file mode 100644
index a2b02b2f1..000000000
--- a/module/plugins/crypter/YoutubeComFolder.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class YoutubeComFolder(Crypter):
- __name__ = "YoutubeComFolder"
- __type__ = "crypter"
- __version__ = "1.01"
-
- __pattern__ = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True ),
- ("likes" , "bool", "Grab user (channel) liked videos" , False),
- ("favorites" , "bool", "Grab user (channel) favorite videos", False),
- ("uploads" , "bool", "Grab channel unplaylisted videos" , True )]
-
- __description__ = """Youtube.com channel & playlist decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_KEY = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
-
-
- def api_response(self, ref, req):
- req.update({"key": self.API_KEY})
- url = urlparse.urljoin("https://www.googleapis.com/youtube/v3/", ref)
- html = self.load(url, get=req)
- return json_loads(html)
-
-
- def getChannel(self, user):
- channels = self.api_response("channels", {"part": "id,snippet,contentDetails", "forUsername": user, "maxResults": "50"})
- if channels['items']:
- channel = channels['items'][0]
- return {"id": channel['id'],
- "title": channel['snippet']['title'],
- "relatedPlaylists": channel['contentDetails']['relatedPlaylists'],
- "user": user} # One lone channel for user?
-
-
- def getPlaylist(self, p_id):
- playlists = self.api_response("playlists", {"part": "snippet", "id": p_id})
- if playlists['items']:
- playlist = playlists['items'][0]
- return {"id": p_id,
- "title": playlist['snippet']['title'],
- "channelId": playlist['snippet']['channelId'],
- "channelTitle": playlist['snippet']['channelTitle']}
-
-
- def _getPlaylists(self, id, token=None):
- req = {"part": "id", "maxResults": "50", "channelId": id}
- if token:
- req.update({"pageToken": token})
-
- playlists = self.api_response("playlists", req)
-
- for playlist in playlists['items']:
- yield playlist['id']
-
- if "nextPageToken" in playlists:
- for item in self._getPlaylists(id, playlists['nextPageToken']):
- yield item
-
-
- def getPlaylists(self, ch_id):
- return map(self.getPlaylist, self._getPlaylists(ch_id))
-
-
- def _getVideosId(self, id, token=None):
- req = {"part": "contentDetails", "maxResults": "50", "playlistId": id}
- if token:
- req.update({"pageToken": token})
-
- playlist = self.api_response("playlistItems", req)
-
- for item in playlist['items']:
- yield item['contentDetails']['videoId']
-
- if "nextPageToken" in playlist:
- for item in self._getVideosId(id, playlist['nextPageToken']):
- yield item
-
-
- def getVideosId(self, p_id):
- return list(self._getVideosId(p_id))
-
-
- def decrypt(self, pyfile):
- m = re.match(self.__pattern__, pyfile.url)
- m_id = m.group('ID')
- m_type = m.group('TYPE')
-
- if m_type == "user":
- self.logDebug("Url recognized as Channel")
- user = m_id
- channel = self.getChannel(user)
-
- if channel:
- playlists = self.getPlaylists(channel['id'])
- self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), channel['title']))
-
- relatedplaylist = {p_name: self.getPlaylist(p_id) for p_name, p_id in channel['relatedPlaylists'].iteritems()}
- self.logDebug("Channel's related playlists found = %s" % relatedplaylist.keys())
-
- relatedplaylist['uploads']['title'] = "Unplaylisted videos"
- relatedplaylist['uploads']['checkDups'] = True #: checkDups flag
-
- for p_name, p_data in relatedplaylist.iteritems():
- if self.getConfig(p_name):
- p_data['title'] += " of " + user
- playlists.append(p_data)
- else:
- playlists = []
- else:
- self.logDebug("Url recognized as Playlist")
- playlists = [self.getPlaylist(m_id)]
-
- if not playlists:
- self.fail(_("No playlist available"))
-
- addedvideos = []
- urlize = lambda x: "https://www.youtube.com/watch?v=" + x
- for p in playlists:
- p_name = p['title']
- p_videos = self.getVideosId(p['id'])
- p_folder = save_join(self.config['general']['download_folder'], p['channelTitle'], p_name)
- self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
-
- if not p_videos:
- continue
- elif "checkDups" in p:
- p_urls = [urlize(v_id) for v_id in p_videos if v_id not in addedvideos]
- self.logDebug("%s video\s available on playlist \"%s\" after duplicates cleanup" % (len(p_urls), p_name))
- else:
- p_urls = map(urlize, p_videos)
-
- self.packages.append((p_name, p_urls, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
-
- addedvideos.extend(p_videos)
diff --git a/module/plugins/crypter/__init__.py b/module/plugins/crypter/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/crypter/__init__.py
+++ /dev/null
diff --git a/module/plugins/hooks/AlldebridComHook.py b/module/plugins/hooks/AlldebridComHook.py
deleted file mode 100644
index 092921134..000000000
--- a/module/plugins/hooks/AlldebridComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class AlldebridComHook(MultiHook):
- __name__ = "AlldebridComHook"
- __type__ = "hook"
- __version__ = "0.16"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Alldebrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip()
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/AndroidPhoneNotify.py b/module/plugins/hooks/AndroidPhoneNotify.py
deleted file mode 100644
index 67a0ea4f0..000000000
--- a/module/plugins/hooks/AndroidPhoneNotify.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook, Expose
-
-
-class AndroidPhoneNotify(Hook):
- __name__ = "AndroidPhoneNotify"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("apikey" , "str" , "API key" , "" ),
- ("notifycaptcha" , "bool", "Notify captcha request" , True ),
- ("notifypackage" , "bool", "Notify package finished" , True ),
- ("notifyprocessed", "bool", "Notify packages processed" , True ),
- ("notifyupdate" , "bool", "Notify plugin updates" , True ),
- ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
- ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
- ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
- ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
-
- __description__ = """Send push notifications to your Android Phone (using notifymyandroid.com)"""
- __license__ = "GPLv3"
- __authors__ = [("Steven Kosyra" , "steven.kosyra@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- event_list = ["allDownloadsProcessed", "plugin_updated"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.last_notify = 0
- self.notifications = 0
-
-
- def plugin_updated(self, type_plugins):
- if not self.getConfig('notifyupdate'):
- return
-
- self.notify(_("Plugins updated"), str(type_plugins))
-
-
- def coreExiting(self):
- if not self.getConfig('notifyexit'):
- return
-
- if self.core.do_restart:
- self.notify(_("Restarting pyLoad"))
- else:
- self.notify(_("Exiting pyLoad"))
-
-
- def newCaptchaTask(self, task):
- if not self.getConfig('notifycaptcha'):
- return
-
- self.notify(_("Captcha"), _("New request waiting user input"))
-
-
- def packageFinished(self, pypack):
- if self.getConfig('notifypackage'):
- self.notify(_("Package finished"), pypack.name)
-
-
- def allDownloadsProcessed(self):
- if not self.getConfig('notifyprocessed'):
- return
-
- if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
- self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
- else:
- self.notify(_("All packages finished"))
-
-
- @Expose
- def notify(self,
- event,
- msg="",
- key=self.getConfig('apikey')):
-
- if not key:
- return
-
- if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
- return
-
- elapsed_time = time.time() - self.last_notify
-
- if elapsed_time < self.getConf("sendtimewait"):
- return
-
- if elapsed_time > 60:
- self.notifications = 0
-
- elif self.notifications >= self.getConf("sendpermin"):
- return
-
-
- getURL("http://www.notifymyandroid.com/publicapi/notify",
- get={'apikey' : key,
- 'application': "pyLoad",
- 'event' : event,
- 'description': msg})
-
- self.last_notify = time.time()
- self.notifications += 1
diff --git a/module/plugins/hooks/AntiVirus.py b/module/plugins/hooks/AntiVirus.py
deleted file mode 100644
index ac9373a37..000000000
--- a/module/plugins/hooks/AntiVirus.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import shutil
-import subprocess
-
-try:
- import send2trash
-except ImportError:
- pass
-
-from module.plugins.Hook import Hook, Expose, threaded
-from module.utils import fs_encode, save_join
-
-
-class AntiVirus(Hook):
- __name__ = "AntiVirus"
- __type__ = "hook"
- __version__ = "0.08"
-
- #@TODO: add trash option (use Send2Trash lib)
- __config__ = [("action" , "Antivirus default;Delete;Quarantine", "Manage infected files" , "Antivirus default"),
- ("quardir" , "folder" , "Quarantine folder" , "" ),
- ("deltotrash", "bool" , "Move to trash (recycle bin) instead delete", True ),
- ("scanfailed", "bool" , "Scan incompleted files (failed downloads)" , False ),
- ("cmdfile" , "file" , "Antivirus executable" , "" ),
- ("cmdargs" , "str" , "Scan options" , "" ),
- ("ignore-err", "bool" , "Ignore scan errors" , False )]
-
- __description__ = """Scan downloaded files with antivirus program"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @Expose
- @threaded
- def scan(self, pyfile, thread):
- file = fs_encode(pyfile.plugin.lastDownload)
- filename = os.path.basename(pyfile.plugin.lastDownload)
- cmdfile = fs_encode(self.getConfig('cmdfile'))
- cmdargs = fs_encode(self.getConfig('cmdargs').strip())
-
- if not os.path.isfile(file) or not os.path.isfile(cmdfile):
- return
-
- thread.addActive(pyfile)
- pyfile.setCustomStatus(_("virus scanning"))
- pyfile.setProgress(0)
-
- try:
- p = subprocess.Popen([cmdfile, cmdargs, file], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
- out, err = map(str.strip, p.communicate())
-
- if out:
- self.logInfo(filename, out)
-
- if err:
- self.logWarning(filename, err)
- if not self.getConfig('ignore-err'):
- self.logDebug("Delete/Quarantine task is aborted")
- return
-
- if p.returncode:
- pyfile.error = _("infected file")
- action = self.getConfig('action')
- try:
- if action == "Delete":
- if not self.getConfig('deltotrash'):
- os.remove(file)
-
- else:
- try:
- send2trash.send2trash(file)
-
- except Exception:
- self.logWarning(_("Unable to move file to trash, move to quarantine instead"))
- pyfile.setCustomStatus(_("file moving"))
- shutil.move(file, self.getConfig('quardir'))
-
- elif action == "Quarantine":
- pyfile.setCustomStatus(_("file moving"))
- shutil.move(file, self.getConfig('quardir'))
-
- except (IOError, shutil.Error), e:
- self.logError(filename, action + " action failed!", e)
-
- elif not out and not err:
- self.logDebug(filename, "No infected file found")
-
- finally:
- pyfile.setProgress(100)
- thread.finishFile(pyfile)
-
-
- def downloadFinished(self, pyfile):
- return self.scan(pyfile)
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status == 8 and self.getConfig('scanfailed'):
- return self.scan(pyfile)
diff --git a/module/plugins/hooks/BypassCaptcha.py b/module/plugins/hooks/BypassCaptcha.py
deleted file mode 100644
index 9e01edfa2..000000000
--- a/module/plugins/hooks/BypassCaptcha.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class BypassCaptchaException(Exception):
-
- def __init__(self, err):
- self.err = err
-
-
- def getCode(self):
- return self.err
-
-
- def __str__(self):
- return "<BypassCaptchaException %s>" % self.err
-
-
- def __repr__(self):
- return "<BypassCaptchaException %s>" % self.err
-
-
-class BypassCaptcha(Hook):
- __name__ = "BypassCaptcha"
- __type__ = "hook"
- __version__ = "0.06"
-
- __config__ = [("force", "bool", "Force BC even if client is connected", False),
- ("passkey", "password", "Passkey", "")]
-
- __description__ = """Send captchas to BypassCaptcha.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Godofdream", "soilfcition@gmail.com"),
- ("zoidberg" , "zoidberg@mujmail.cz" )]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32"
-
- SUBMIT_URL = "http://bypasscaptcha.com/upload.php"
- RESPOND_URL = "http://bypasscaptcha.com/check_value.php"
- GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.GETCREDITS_URL, post={"key": self.getConfig('passkey')})
-
- data = dict(x.split(' ', 1) for x in res.splitlines())
- return int(data['Left'])
-
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
-
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- res = req.load(self.SUBMIT_URL,
- post={'vendor_key': self.PYLOAD_KEY,
- 'key': self.getConfig('passkey'),
- 'gen_task_id': "1",
- 'file': (pycurl.FORM_FILE, captcha)},
- multipart=True)
- finally:
- req.close()
-
- data = dict(x.split(' ', 1) for x in res.splitlines())
- if not data or "Value" not in data:
- raise BypassCaptchaException(res)
-
- result = data['Value']
- ticket = data['TaskId']
- self.logDebug("Result %s : %s" % (ticket, result))
-
- return ticket, result
-
-
- def respond(self, ticket, success):
- try:
- res = getURL(self.RESPOND_URL, post={"task_id": ticket, "key": self.getConfig('passkey'),
- "cv": 1 if success else 0})
- except BadHeader, e:
- self.logError(_("Could not send response"), e)
-
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig('passkey'):
- return False
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- self._processCaptcha(task)
-
- else:
- self.logInfo(_("Your %s account has not enough credits") % self.__name__)
-
-
- def captchaCorrect(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- self.respond(task.data['ticket'], True)
-
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- self.respond(task.data['ticket'], False)
-
-
- @threaded
- def _processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except BypassCaptchaException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/Captcha9Kw.py b/module/plugins/hooks/Captcha9Kw.py
deleted file mode 100644
index 60482b8fc..000000000
--- a/module/plugins/hooks/Captcha9Kw.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-import time
-
-from base64 import b64encode
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL
-
-from module.plugins.Hook import Hook, threaded
-
-
-class Captcha9Kw(Hook):
- __name__ = "Captcha9Kw"
- __type__ = "hook"
- __version__ = "0.28"
-
- __config__ = [("ssl" , "bool" , "Use HTTPS" , True ),
- ("force" , "bool" , "Force captcha resolving even if client is connected" , True ),
- ("confirm" , "bool" , "Confirm Captcha (cost +6 credits)" , False ),
- ("captchaperhour", "int" , "Captcha per hour" , "9999" ),
- ("captchapermin" , "int" , "Captcha per minute" , "9999" ),
- ("prio" , "int" , "Priority (max 10)(cost +0 -> +10 credits)" , "0" ),
- ("queue" , "int" , "Max. Queue (max 999)" , "50" ),
- ("hoster_options", "string" , "Hoster options (format: pluginname:prio=1:selfsolfe=1:confirm=1:timeout=900|...)", "ShareonlineBiz:prio=0:timeout=999 | UploadedTo:prio=0:timeout=999"),
- ("selfsolve" , "bool" , "Selfsolve (manually solve your captcha in your 9kw client if active)" , "0" ),
- ("passkey" , "password", "API key" , "" ),
- ("timeout" , "int" , "Timeout in seconds (min 60, max 3999)" , "900" )]
-
- __description__ = """Send captchas to 9kw.eu"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.9kw.eu/index.cgi"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- if self.getConfig('ssl'):
- self.API_URL = self.API_URL.replace("http://", "https://")
-
-
- def getCredits(self):
- res = getURL(self.API_URL,
- get={'apikey': self.getConfig('passkey'),
- 'pyload': "1",
- 'source': "pyload",
- 'action': "usercaptchaguthaben"})
-
- if res.isdigit():
- self.logInfo(_("%s credits left") % res)
- credits = self.info['credits'] = int(res)
- return credits
- else:
- self.logError(res)
- return 0
-
-
- @threaded
- def _processCaptcha(self, task):
- try:
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
-
- except IOError, e:
- self.logError(e)
- return
-
- pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
- option = {'min' : 2,
- 'max' : 50,
- 'phrase' : 0,
- 'numeric' : 0,
- 'case_sensitive': 0,
- 'math' : 0,
- 'prio' : min(max(self.getConfig('prio'), 0), 10),
- 'confirm' : self.getConfig('confirm'),
- 'timeout' : min(max(self.getConfig('timeout'), 300), 3999),
- 'selfsolve' : self.getConfig('selfsolve'),
- 'cph' : self.getConfig('captchaperhour'),
- 'cpm' : self.getConfig('captchapermin')}
-
- for opt in str(self.getConfig('hoster_options').split('|')):
-
- details = map(str.strip, opt.split(':'))
-
- if not details or details[0].lower() != pluginname.lower():
- continue
-
- for d in details:
- hosteroption = d.split("=")
-
- if len(hosteroption) < 2 or not hosteroption[1].isdigit():
- continue
-
- o = hosteroption[0].lower()
- if o in option:
- option[o] = hosteroption[1]
-
- break
-
- post_data = {'apikey' : self.getConfig('passkey'),
- 'prio' : option['prio'],
- 'confirm' : option['confirm'],
- 'maxtimeout' : option['timeout'],
- 'selfsolve' : option['selfsolve'],
- 'captchaperhour': option['cph'],
- 'captchapermin' : option['cpm'],
- 'case-sensitive': option['case_sensitive'],
- 'min_len' : option['min'],
- 'max_len' : option['max'],
- 'phrase' : option['phrase'],
- 'numeric' : option['numeric'],
- 'math' : option['math'],
- 'oldsource' : pluginname,
- 'pyload' : "1",
- 'source' : "pyload",
- 'base64' : "1",
- 'mouse' : 1 if task.isPositional() else 0,
- 'file-upload-01': b64encode(data),
- 'action' : "usercaptchaupload"}
-
- for _i in xrange(5):
- try:
- res = getURL(self.API_URL, post=post_data)
- except BadHeader, e:
- time.sleep(3)
- else:
- if res and res.isdigit():
- break
- else:
- self.logError(_("Bad upload: %s") % res)
- return
-
- self.logDebug(_("NewCaptchaID ticket: %s") % res, task.captchaFile)
-
- task.data["ticket"] = res
-
- for _i in xrange(int(self.getConfig('timeout') / 5)):
- result = getURL(self.API_URL,
- get={'apikey': self.getConfig('passkey'),
- 'id' : res,
- 'pyload': "1",
- 'info' : "1",
- 'source': "pyload",
- 'action': "usercaptchacorrectdata"})
-
- if not result or result == "NO DATA":
- time.sleep(5)
- else:
- break
- else:
- self.logDebug("Could not send request: %s" % res)
- result = None
-
- self.logInfo(_("Captcha result for ticket %s: %s") % (res, result))
-
- task.setResult(result)
-
-
- def newCaptchaTask(self, task):
- if not task.isTextual() and not task.isPositional():
- return
-
- if not self.getConfig('passkey'):
- return
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return
-
- credits = self.getCredits()
-
- if not credits:
- self.logError(_("Your captcha 9kw.eu account has not enough credits"))
- return
-
- queue = min(self.getConfig('queue'), 999)
- timeout = min(max(self.getConfig('timeout'), 300), 3999)
- pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
-
- for _i in xrange(5):
- servercheck = getURL("http://www.9kw.eu/grafik/servercheck.txt")
- if queue < re.search(r'queue=(\d+)', servercheck).group(1):
- break
-
- time.sleep(10)
- else:
- self.fail(_("Too many captchas in queue"))
-
- for opt in str(self.getConfig('hoster_options').split('|')):
- details = map(str.strip, opt.split(':'))
-
- if not details or details[0].lower() != pluginname.lower():
- continue
-
- for d in details:
- hosteroption = d.split("=")
-
- if len(hosteroption) > 1 \
- and hosteroption[0].lower() == 'timeout' \
- and hosteroption[1].isdigit():
- timeout = int(hosteroption[1])
-
- break
-
- task.handler.append(self)
-
- task.setWaiting(timeout)
-
- self._processCaptcha(task)
-
-
- def _captchaResponse(self, task, correct):
- type = "correct" if correct else "refund"
-
- if 'ticket' not in task.data:
- self.logDebug("No CaptchaID for %s request (task: %s)" % (type, task))
- return
-
- passkey = self.getConfig('passkey')
-
- for _i in xrange(3):
- res = getURL(self.API_URL,
- get={'action' : "usercaptchacorrectback",
- 'apikey' : passkey,
- 'api_key': passkey,
- 'correct': "1" if correct else "2",
- 'pyload' : "1",
- 'source' : "pyload",
- 'id' : task.data["ticket"]})
-
- self.logDebug("Request %s: %s" % (type, res))
-
- if res == "OK":
- break
-
- time.sleep(5)
- else:
- self.logDebug("Could not send %s request: %s" % (type, res))
-
-
- def captchaCorrect(self, task):
- self._captchaResponse(task, True)
-
-
- def captchaInvalid(self, task):
- self._captchaResponse(task, False)
diff --git a/module/plugins/hooks/CaptchaBrotherhood.py b/module/plugins/hooks/CaptchaBrotherhood.py
deleted file mode 100644
index bda080037..000000000
--- a/module/plugins/hooks/CaptchaBrotherhood.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import StringIO
-import pycurl
-import time
-import urllib
-
-try:
- from PIL import Image
-except ImportError:
- import Image
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class CaptchaBrotherhoodException(Exception):
-
- def __init__(self, err):
- self.err = err
-
-
- def getCode(self):
- return self.err
-
-
- def __str__(self):
- return "<CaptchaBrotherhoodException %s>" % self.err
-
-
- def __repr__(self):
- return "<CaptchaBrotherhoodException %s>" % self.err
-
-
-class CaptchaBrotherhood(Hook):
- __name__ = "CaptchaBrotherhood"
- __type__ = "hook"
- __version__ = "0.08"
-
- __config__ = [("username", "str", "Username", ""),
- ("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Password", "")]
-
- __description__ = """Send captchas to CaptchaBrotherhood.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.captchabrotherhood.com/"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.API_URL + "askCredits.aspx",
- get={"username": self.getConfig('username'), "password": self.getConfig('passkey')})
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException(res)
- else:
- credits = int(res[3:])
- self.logInfo(_("%d credits left") % credits)
- self.info['credits'] = credits
- return credits
-
-
- def submit(self, captcha, captchaType="file", match=None):
- try:
- img = Image.open(captcha)
- output = StringIO.StringIO()
- self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode)
- if img.format in ("GIF", "JPEG"):
- img.save(output, img.format)
- else:
- if img.mode != "RGB":
- img = img.convert("RGB")
- img.save(output, "JPEG")
- data = output.getvalue()
- output.close()
- except Exception, e:
- raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e)
-
- req = getRequest()
-
- url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL,
- urllib.urlencode({'username' : self.getConfig('username'),
- 'password' : self.getConfig('passkey'),
- 'captchaSource': "pyLoad",
- 'timeout' : "80"}))
-
- req.c.setopt(pycurl.URL, url)
- req.c.setopt(pycurl.POST, 1)
- req.c.setopt(pycurl.POSTFIELDS, data)
- req.c.setopt(pycurl.HTTPHEADER, ["Content-Type: text/html"])
-
- try:
- req.c.perform()
- res = req.getResponse()
- except Exception, e:
- raise CaptchaBrotherhoodException("Submit captcha image failed")
-
- req.close()
-
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException(res[1])
-
- ticket = res[3:]
-
- for _i in xrange(15):
- time.sleep(5)
- res = self.api_response("askCaptchaResult", ticket)
- if res.startswith("OK-answered"):
- return ticket, res[12:]
-
- raise CaptchaBrotherhoodException("No solution received in time")
-
-
- def api_response(self, api, ticket):
- res = getURL("%s%s.aspx" % (self.API_URL, api),
- get={"username": self.getConfig('username'),
- "password": self.getConfig('passkey'),
- "captchaID": ticket})
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException("Unknown response: %s" % res)
-
- return res
-
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig('username') or not self.getConfig('passkey'):
- return False
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return False
-
- if self.getCredits() > 10:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- self._processCaptcha(task)
- else:
- self.logInfo(_("Your CaptchaBrotherhood Account has not enough credits"))
-
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- res = self.api_response("complainCaptcha", task.data['ticket'])
-
-
- @threaded
- def _processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except CaptchaBrotherhoodException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/Checksum.py b/module/plugins/hooks/Checksum.py
deleted file mode 100644
index ab7daf701..000000000
--- a/module/plugins/hooks/Checksum.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import hashlib
-import os
-import re
-import zlib
-
-from module.plugins.Hook import Hook
-from module.utils import save_join, fs_encode
-
-
-def computeChecksum(local_file, algorithm):
- if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")):
- h = getattr(hashlib, algorithm)()
-
- with open(local_file, 'rb') as f:
- for chunk in iter(lambda: f.read(128 * h.block_size), ''):
- h.update(chunk)
-
- return h.hexdigest()
-
- elif algorithm in ("adler32", "crc32"):
- hf = getattr(zlib, algorithm)
- last = 0
-
- with open(local_file, 'rb') as f:
- for chunk in iter(lambda: f.read(8192), ''):
- last = hf(chunk, last)
-
- return "%x" % last
-
- else:
- return None
-
-
-class Checksum(Hook):
- __name__ = "Checksum"
- __type__ = "hook"
- __version__ = "0.16"
-
- __config__ = [("check_checksum", "bool", "Check checksum? (If False only size will be verified)", True),
- ("check_action", "fail;retry;nothing", "What to do if check fails?", "retry"),
- ("max_tries", "int", "Number of retries", 2),
- ("retry_action", "fail;nothing", "What to do if all retries fail?", "fail"),
- ("wait_time", "int", "Time to wait before each retry (seconds)", 1)]
-
- __description__ = """Verify downloaded file size and checksum"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("stickell" , "l.stickell@yahoo.it")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
- methods = {'sfv' : 'crc32',
- 'crc' : 'crc32',
- 'hash': 'md5'}
- regexps = {'sfv' : r'^(?P<NAME>[^;].+)\s+(?P<HASH>[0-9A-Fa-f]{8})$',
- 'md5' : r'^(?P<NAME>[0-9A-Fa-f]{32}) (?P<FILE>.+)$',
- 'crc' : r'filename=(?P<NAME>.+)\nsize=(?P<SIZE>\d+)\ncrc32=(?P<HASH>[0-9A-Fa-f]{8})$',
- 'default': r'^(?P<HASH>[0-9A-Fa-f]+)\s+\*?(?P<NAME>.+)$'}
-
-
- def coreReady(self):
- if not self.getConfig('check_checksum'):
- self.logInfo(_("Checksum validation is disabled in plugin configuration"))
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.algorithms = sorted(
- getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse=True)
-
- self.algorithms.extend(["crc32", "adler32"])
-
- self.formats = self.algorithms + ["sfv", "crc", "hash"]
-
-
- def downloadFinished(self, pyfile):
- """
- Compute checksum for the downloaded file and compare it with the hash provided by the hoster.
- pyfile.plugin.check_data should be a dictionary which can contain:
- a) if known, the exact filesize in bytes (e.g. "size": 123456789)
- b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307")
- """
- if hasattr(pyfile.plugin, "check_data") and isinstance(pyfile.plugin.check_data, dict):
- data = pyfile.plugin.check_data.copy()
-
- elif hasattr(pyfile.plugin, "api_data") and isinstance(pyfile.plugin.api_data, dict):
- data = pyfile.plugin.api_data.copy()
-
- elif hasattr(pyfile.plugin, "info") and isinstance(pyfile.plugin.info, dict):
- data = pyfile.plugin.info.copy()
- data.pop('size', None) #@NOTE: Don't check file size until a similary matcher will be implemented
-
- else:
- return
-
- self.logDebug(data)
-
- if not pyfile.plugin.lastDownload:
- self.checkFailed(pyfile, None, "No file downloaded")
-
- local_file = fs_encode(pyfile.plugin.lastDownload)
- #download_folder = self.config['general']['download_folder']
- #local_file = fs_encode(save_join(download_folder, pyfile.package().folder, pyfile.name))
-
- if not os.path.isfile(local_file):
- self.checkFailed(pyfile, None, "File does not exist")
-
- # validate file size
- if "size" in data:
- api_size = int(data['size'])
- file_size = os.path.getsize(local_file)
-
- if api_size != file_size:
- self.logWarning(_("File %s has incorrect size: %d B (%d expected)") % (pyfile.name, file_size, api_size))
- self.checkFailed(pyfile, local_file, "Incorrect file size")
-
- data.pop('size', None)
-
- # validate checksum
- if data and self.getConfig('check_checksum'):
-
- if not 'md5' in data:
- for type in ("checksum", "hashsum", "hash"):
- if type in data:
- data['md5'] = data[type] #@NOTE: What happens if it's not an md5 hash?
- break
-
- for key in self.algorithms:
- if key in data:
- checksum = computeChecksum(local_file, key.replace("-", "").lower())
- if checksum:
- if checksum == data[key].lower():
- self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') %
- (pyfile.name, key.upper(), checksum))
- break
- else:
- self.logWarning(_("%s checksum for file %s does not match (%s != %s)") %
- (key.upper(), pyfile.name, checksum, data[key]))
- self.checkFailed(pyfile, local_file, "Checksums do not match")
- else:
- self.logWarning(_("Unsupported hashing algorithm"), key.upper())
- else:
- self.logWarning(_("Unable to validate checksum for file: ") + pyfile.name)
-
-
- def checkFailed(self, pyfile, local_file, msg):
- check_action = self.getConfig('check_action')
- if check_action == "retry":
- max_tries = self.getConfig('max_tries')
- retry_action = self.getConfig('retry_action')
- if pyfile.plugin.retries < max_tries:
- if local_file:
- os.remove(local_file)
- pyfile.plugin.retry(max_tries, self.getConfig('wait_time'), msg)
- elif retry_action == "nothing":
- return
- elif check_action == "nothing":
- return
- pyfile.plugin.fail(reason=msg)
-
-
- def packageFinished(self, pypack):
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder, "")
-
- for link in pypack.getChildren().itervalues():
- file_type = os.path.splitext(link['name'])[1][1:].lower()
-
- if file_type not in self.formats:
- continue
-
- hash_file = fs_encode(save_join(download_folder, link['name']))
- if not os.path.isfile(hash_file):
- self.logWarning(_("File not found"), link['name'])
- continue
-
- with open(hash_file) as f:
- text = f.read()
-
- for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text):
- data = m.groupdict()
- self.logDebug(link['name'], data)
-
- local_file = fs_encode(save_join(download_folder, data['NAME']))
- algorithm = self.methods.get(file_type, file_type)
- checksum = computeChecksum(local_file, algorithm)
- if checksum == data['HASH']:
- self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') %
- (data['NAME'], algorithm, checksum))
- else:
- self.logWarning(_("%s checksum for file %s does not match (%s != %s)") %
- (algorithm, data['NAME'], checksum, data['HASH']))
diff --git a/module/plugins/hooks/ClickAndLoad.py b/module/plugins/hooks/ClickAndLoad.py
deleted file mode 100644
index 2ddd66daa..000000000
--- a/module/plugins/hooks/ClickAndLoad.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import socket
-import time
-
-from threading import Lock
-
-from module.plugins.Hook import Hook, threaded
-
-
-def forward(source, destination):
- try:
- bufsize = 1024
- bufdata = source.recv(bufsize)
- while bufdata:
- destination.sendall(bufdata)
- bufdata = source.recv(bufsize)
- finally:
- destination.shutdown(socket.SHUT_WR)
- # destination.close()
-
-
-#@TODO: IPv6 support
-class ClickAndLoad(Hook):
- __name__ = "ClickAndLoad"
- __type__ = "hook"
- __version__ = "0.41"
-
- __config__ = [("activated", "bool", "Activated" , True),
- ("port" , "int" , "Port" , 9666),
- ("extern" , "bool", "Listen on the public network interface", True)]
-
- __description__ = """Click'n'Load hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.de" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def coreReady(self):
- if not self.config['webinterface']['activated']:
- return
-
- ip = "" if self.getConfig('extern') else "127.0.0.1"
- webport = self.config['webinterface']['port']
- cnlport = self.getConfig('port')
-
- self.proxy(ip, webport, cnlport)
-
-
- @threaded
- def proxy(self, ip, webport, cnlport):
- time.sleep(10) #@TODO: Remove in 0.4.10 (implement addon delay on startup)
-
- self.logInfo(_("Proxy listening on %s:%s") % (ip or "0.0.0.0", cnlport))
-
- self._server(ip, webport, cnlport)
-
- lock = Lock()
- lock.acquire()
- lock.acquire()
-
-
- @threaded
- def _server(self, ip, webport, cnlport):
- try:
- dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- dock_socket.bind((ip, cnlport))
- dock_socket.listen(5)
-
- while True:
- client_socket, client_addr = dock_socket.accept()
- self.logDebug("Connection from %s:%s" % client_addr)
-
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.connect(("127.0.0.1", webport))
-
- self.manager.startThread(forward, client_socket, server_socket)
- self.manager.startThread(forward, server_socket, client_socket)
-
- except socket.timeout:
- self.logDebug("Connection timed out, retrying...")
- return self._server(ip, webport, cnlport)
-
- except socket.error, e:
- self.logError(e)
- time.sleep(240)
- return self._server(ip, webport, cnlport)
diff --git a/module/plugins/hooks/DeathByCaptcha.py b/module/plugins/hooks/DeathByCaptcha.py
deleted file mode 100644
index f9618f011..000000000
--- a/module/plugins/hooks/DeathByCaptcha.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import re
-import time
-
-from base64 import b64encode
-
-from module.common.json_layer import json_loads
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class DeathByCaptchaException(Exception):
- DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials',
- 'invalid-credentials': 'Access denied, check your credentials',
- 'banned': 'Access denied, account is suspended',
- 'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA',
- 'invalid-captcha': 'CAPTCHA is not a valid image',
- 'service-overload': 'CAPTCHA was rejected due to service overload, try again later',
- 'invalid-request': 'Invalid request',
- 'timed-out': 'No CAPTCHA solution received in time'}
-
-
- def __init__(self, err):
- self.err = err
-
-
- def getCode(self):
- return self.err
-
-
- def getDesc(self):
- if self.err in self.DBC_ERRORS.keys():
- return self.DBC_ERRORS[self.err]
- else:
- return self.err
-
-
- def __str__(self):
- return "<DeathByCaptchaException %s>" % self.err
-
-
- def __repr__(self):
- return "<DeathByCaptchaException %s>" % self.err
-
-
-class DeathByCaptcha(Hook):
- __name__ = "DeathByCaptcha"
- __type__ = "hook"
- __version__ = "0.06"
-
- __config__ = [("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force DBC even if client is connected", False)]
-
- __description__ = """Send captchas to DeathByCaptcha.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://api.dbcapi.me/api/"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def api_response(self, api="captcha", post=False, multipart=False):
- req = getRequest()
- req.c.setopt(pycurl.HTTPHEADER, ["Accept: application/json", "User-Agent: pyLoad %s" % self.core.version])
-
- if post:
- if not isinstance(post, dict):
- post = {}
- post.update({"username": self.getConfig('username'),
- "password": self.getConfig('passkey')})
-
- res = None
- try:
- json = req.load("%s%s" % (self.API_URL, api),
- post=post,
- multipart=multipart)
- self.logDebug(json)
- res = json_loads(json)
-
- if "error" in res:
- raise DeathByCaptchaException(res['error'])
- elif "status" not in res:
- raise DeathByCaptchaException(str(res))
-
- except BadHeader, e:
- if 403 == e.code:
- raise DeathByCaptchaException('not-logged-in')
- elif 413 == e.code:
- raise DeathByCaptchaException('invalid-captcha')
- elif 503 == e.code:
- raise DeathByCaptchaException('service-overload')
- elif e.code in (400, 405):
- raise DeathByCaptchaException('invalid-request')
- else:
- raise
-
- finally:
- req.close()
-
- return res
-
-
- def getCredits(self):
- res = self.api_response("user", True)
-
- if 'is_banned' in res and res['is_banned']:
- raise DeathByCaptchaException('banned')
- elif 'balance' in res and 'rate' in res:
- self.info.update(res)
- else:
- raise DeathByCaptchaException(res)
-
-
- def getStatus(self):
- res = self.api_response("status", False)
-
- if 'is_service_overloaded' in res and res['is_service_overloaded']:
- raise DeathByCaptchaException('service-overload')
-
-
- def submit(self, captcha, captchaType="file", match=None):
- #@NOTE: Workaround multipart-post bug in HTTPRequest.py
- if re.match("^\w*$", self.getConfig('passkey')):
- multipart = True
- data = (pycurl.FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = "base64:" + b64encode(data)
-
- res = self.api_response("captcha", {"captchafile": data}, multipart)
-
- if "captcha" not in res:
- raise DeathByCaptchaException(res)
- ticket = res['captcha']
-
- for _i in xrange(24):
- time.sleep(5)
- res = self.api_response("captcha/%d" % ticket, False)
- if res['text'] and res['is_correct']:
- break
- else:
- raise DeathByCaptchaException('timed-out')
-
- result = res['text']
- self.logDebug("Result %s : %s" % (ticket, result))
-
- return ticket, result
-
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig('username') or not self.getConfig('passkey'):
- return False
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return False
-
- try:
- self.getStatus()
- self.getCredits()
- except DeathByCaptchaException, e:
- self.logError(e.getDesc())
- return False
-
- balance, rate = self.info['balance'], self.info['rate']
- self.logInfo(_("Account balance"),
- _("US$%.3f (%d captchas left at %.2f cents each)") % (balance / 100,
- balance // rate, rate))
-
- if balance > rate:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(180)
- self._processCaptcha(task)
-
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- try:
- res = self.api_response("captcha/%d/report" % task.data['ticket'], True)
-
- except DeathByCaptchaException, e:
- self.logError(e.getDesc())
-
- except Exception, e:
- self.logError(e)
-
-
- @threaded
- def _processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except DeathByCaptchaException, e:
- task.error = e.getCode()
- self.logError(e.getDesc())
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/DebridItaliaComHook.py b/module/plugins/hooks/DebridItaliaComHook.py
deleted file mode 100644
index 36b307696..000000000
--- a/module/plugins/hooks/DebridItaliaComHook.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class DebridItaliaComHook(MultiHook):
- __name__ = "DebridItaliaComHook"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Debriditalia.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell" , "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- def getHosters(self):
- return self.getURL("http://debriditalia.com/api.php", get={'hosts': ""}).replace('"', '').split(',')
diff --git a/module/plugins/hooks/DeleteFinished.py b/module/plugins/hooks/DeleteFinished.py
deleted file mode 100644
index bde4ca259..000000000
--- a/module/plugins/hooks/DeleteFinished.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.database import style
-from module.plugins.Hook import Hook
-
-
-class DeleteFinished(Hook):
- __name__ = "DeleteFinished"
- __type__ = "hook"
- __version__ = "1.12"
-
- __config__ = [("interval" , "int" , "Check interval in hours" , 72 ),
- ("deloffline", "bool", "Delete package with offline links", False)]
-
- __description__ = """Automatically delete all finished packages from queue"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
-
- MIN_CHECK_INTERVAL = 1 * 60 * 60 #: 1 hour
-
-
- ## overwritten methods ##
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = self.MIN_CHECK_INTERVAL
-
-
- def periodical(self):
- if not self.info['sleep']:
- deloffline = self.getConfig('deloffline')
- mode = '0,1,4' if deloffline else '0,4'
- msg = _('delete all finished packages in queue list (%s packages with offline links)')
- self.logInfo(msg % (_('including') if deloffline else _('excluding')))
- self.deleteFinished(mode)
- self.info['sleep'] = True
- self.addEvent('packageFinished', self.wakeup)
-
-
- # def pluginConfigChanged(self, plugin, name, value):
- # if name == "interval" and value != self.interval:
- # self.interval = value * 3600
- # self.initPeriodical()
-
-
- def unload(self):
- self.manager.removeEvent('packageFinished', self.wakeup)
-
-
- def coreReady(self):
- self.info['sleep'] = True
- # interval = self.getConfig('interval')
- # self.pluginConfigChanged(self.__name__, 'interval', interval)
- self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60 * 60)
- self.addEvent('packageFinished', self.wakeup)
-
-
- ## own methods ##
- @style.queue
- def deleteFinished(self, mode):
- self.c.execute('DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE package=packages.id AND status NOT IN (%s))' % mode)
- self.c.execute('DELETE FROM links WHERE NOT EXISTS(SELECT 1 FROM packages WHERE id=links.package)')
-
-
- def wakeup(self, pypack):
- self.manager.removeEvent('packageFinished', self.wakeup)
- self.info['sleep'] = False
-
-
- ## event managing ##
- def addEvent(self, event, func):
- """Adds an event listener for event name"""
- if event in self.manager.events:
- if func in self.manager.events[event]:
- self.logDebug("Function already registered", func)
- else:
- self.manager.events[event].append(func)
- else:
- self.manager.events[event] = [func]
diff --git a/module/plugins/hooks/DownloadScheduler.py b/module/plugins/hooks/DownloadScheduler.py
deleted file mode 100644
index 9c74ac1d4..000000000
--- a/module/plugins/hooks/DownloadScheduler.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Hook import Hook
-
-
-class DownloadScheduler(Hook):
- __name__ = "DownloadScheduler"
- __type__ = "hook"
- __version__ = "0.22"
-
- __config__ = [("timetable", "str", "List time periods as hh:mm full or number(kB/s)",
- "0:00 full, 7:00 250, 10:00 0, 17:00 150"),
- ("abort", "bool", "Abort active downloads when start period with speed 0", False)]
-
- __description__ = """Download Scheduler"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.cb = None # callback to scheduler job; will be by removed hookmanager when hook unloaded
-
-
- def coreReady(self):
- self.updateSchedule()
-
-
- def updateSchedule(self, schedule=None):
- if schedule is None:
- schedule = self.getConfig('timetable')
-
- schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)",
- schedule.lower().replace("full", "-1").replace("none", "0"))
- if not schedule:
- self.logError(_("Invalid schedule"))
- return
-
- t0 = time.localtime()
- now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X")
- schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now])
-
- self.logDebug("Schedule", schedule)
-
- for i, v in enumerate(schedule):
- if v[3] == "X":
- last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)]
- self.logDebug("Now/Last/Next", now, last, next)
-
- self.setDownloadSpeed(last[3])
-
- next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400
- self.core.scheduler.removeJob(self.cb)
- self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False)
-
-
- def setDownloadSpeed(self, speed):
- if speed == 0:
- abort = self.getConfig('abort')
- self.logInfo(_("Stopping download server. (Running downloads will %sbe aborted.)") % '' if abort else _('not '))
- self.core.api.pauseServer()
- if abort:
- self.core.api.stopAllDownloads()
- else:
- self.core.api.unpauseServer()
-
- if speed > 0:
- self.logInfo(_("Setting download speed to %d kB/s") % speed)
- self.core.api.setConfigValue("download", "limit_speed", 1)
- self.core.api.setConfigValue("download", "max_speed", speed)
- else:
- self.logInfo(_("Setting download speed to FULL"))
- self.core.api.setConfigValue("download", "limit_speed", 0)
- self.core.api.setConfigValue("download", "max_speed", -1)
diff --git a/module/plugins/hooks/EasybytezComHook.py b/module/plugins/hooks/EasybytezComHook.py
deleted file mode 100644
index 43efb5048..000000000
--- a/module/plugins/hooks/EasybytezComHook.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class EasybytezComHook(MultiHook):
- __name__ = "EasybytezComHook"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """EasyBytez.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
-
- req = self.account.getAccountRequest(user)
- html = req.load("http://www.easybytez.com")
-
- return re.search(r'</textarea>\s*Supported sites:(.*)', html).group(1).split(',')
diff --git a/module/plugins/hooks/ExpertDecoders.py b/module/plugins/hooks/ExpertDecoders.py
deleted file mode 100644
index ccd48f664..000000000
--- a/module/plugins/hooks/ExpertDecoders.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import uuid
-
-from base64 import b64encode
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class ExpertDecoders(Hook):
- __name__ = "ExpertDecoders"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Access key", "")]
-
- __description__ = """Send captchas to expertdecoders.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.fasttypers.org/imagepost.ashx"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.API_URL, post={"key": self.getConfig('passkey'), "action": "balance"})
-
- if res.isdigit():
- self.logInfo(_("%s credits left") % res)
- self.info['credits'] = credits = int(res)
- return credits
- else:
- self.logError(res)
- return 0
-
-
- @threaded
- def _processCaptcha(self, task):
- task.data['ticket'] = ticket = uuid.uuid4()
- result = None
-
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
-
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- result = req.load(self.API_URL,
- post={'action' : "upload",
- 'key' : self.getConfig('passkey'),
- 'file' : b64encode(data),
- 'gen_task_id': ticket})
- finally:
- req.close()
-
- self.logDebug("Result %s : %s" % (ticket, result))
- task.setResult(result)
-
-
- def newCaptchaTask(self, task):
- if not task.isTextual():
- return False
-
- if not self.getConfig('passkey'):
- return False
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.setWaiting(100)
- self._processCaptcha(task)
-
- else:
- self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
-
-
- def captchaInvalid(self, task):
- if "ticket" in task.data:
-
- try:
- res = getURL(self.API_URL,
- post={'action': "refund", 'key': self.getConfig('passkey'), 'gen_task_id': task.data['ticket']})
- self.logInfo(_("Request refund"), res)
-
- except BadHeader, e:
- self.logError(_("Could not send refund request"), e)
diff --git a/module/plugins/hooks/ExternalScripts.py b/module/plugins/hooks/ExternalScripts.py
deleted file mode 100644
index be0857009..000000000
--- a/module/plugins/hooks/ExternalScripts.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import subprocess
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode, save_join
-
-
-class ExternalScripts(Hook):
- __name__ = "ExternalScripts"
- __type__ = "hook"
- __version__ = "0.39"
-
- __config__ = [("activated", "bool", "Activated" , True ),
- ("waitend" , "bool", "Wait script ending", False)]
-
- __description__ = """Run external scripts"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay" , "mkaay@mkaay.de" ),
- ("RaNaN" , "ranan@pyload.org" ),
- ("spoob" , "spoob@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- event_list = ["archive_extract_failed", "archive_extracted" ,
- "package_extract_failed", "package_extracted" ,
- "all_archives_extracted", "all_archives_processed",
- "allDownloadsFinished" , "allDownloadsProcessed" ,
- "packageDeleted"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {'oldip': None}
- self.scripts = {}
-
- folders = ["pyload_start", "pyload_restart", "pyload_stop",
- "before_reconnect", "after_reconnect",
- "download_preparing", "download_failed", "download_finished",
- "archive_extract_failed", "archive_extracted",
- "package_finished", "package_deleted", "package_extract_failed", "package_extracted",
- "all_downloads_processed", "all_downloads_finished", #@TODO: Invert `all_downloads_processed`, `all_downloads_finished` order in 0.4.10
- "all_archives_extracted", "all_archives_processed"]
-
- for folder in folders:
- self.scripts[folder] = []
- for dir in (pypath, ''):
- self.initPluginType(folder, os.path.join(dir, 'scripts', folder))
-
- for script_type, names in self.scripts.iteritems():
- if names:
- self.logInfo(_("Installed scripts for: ") + script_type, ", ".join(map(os.path.basename, names)))
-
- self.pyload_start()
-
-
- def initPluginType(self, name, dir):
- if not os.path.isdir(dir):
- try:
- os.makedirs(dir)
-
- except OSError, e:
- self.logDebug(e)
- return
-
- for filename in os.listdir(dir):
- file = save_join(dir, filename)
-
- if not os.path.isfile(file):
- continue
-
- if filename[0] in ("#", "_") or filename.endswith("~") or filename.endswith(".swp"):
- continue
-
- if not os.access(file, os.X_OK):
- self.logWarning(_("Script not executable:") + " %s/%s" % (name, filename))
-
- self.scripts[name].append(file)
-
-
- def callScript(self, script, *args):
- try:
- cmd_args = [fs_encode(str(x) if not isinstance(x, basestring) else x) for x in args]
- cmd = [script] + cmd_args
-
- self.logDebug("Executing: %s" % os.path.abspath(script), "Args: " + ' '.join(cmd_args))
-
- p = subprocess.Popen(cmd, bufsize=-1) #@NOTE: output goes to pyload
- if self.getConfig('waitend'):
- p.communicate()
-
- except Exception, e:
- try:
- self.logError(_("Runtime error: %s") % os.path.abspath(script), e)
- except Exception:
- self.logError(_("Runtime error: %s") % os.path.abspath(script), _("Unknown error"))
-
-
- def pyload_start(self):
- for script in self.scripts['pyload_start']:
- self.callScript(script)
-
-
- def coreExiting(self):
- for script in self.scripts['pyload_restart' if self.core.do_restart else 'pyload_stop']:
- self.callScript(script)
-
-
- def beforeReconnecting(self, ip):
- for script in self.scripts['before_reconnect']:
- self.callScript(script, ip)
- self.info['oldip'] = ip
-
-
- def afterReconnecting(self, ip):
- for script in self.scripts['after_reconnect']:
- self.callScript(script, ip, self.info['oldip']) #@TODO: Use built-in oldip in 0.4.10
-
-
- def downloadPreparing(self, pyfile):
- for script in self.scripts['download_preparing']:
- self.callScript(script, pyfile.id, pyfile.name, None, pyfile.pluginname, pyfile.url)
-
-
- def downloadFailed(self, pyfile):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pyfile.package().folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['download_failed']:
- file = save_join(download_folder, pyfile.name)
- self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
-
-
- def downloadFinished(self, pyfile):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pyfile.package().folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['download_finished']:
- file = save_join(download_folder, pyfile.name)
- self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
-
-
- def archive_extract_failed(self, pyfile, archive):
- for script in self.scripts['archive_extract_failed']:
- self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
-
-
- def archive_extracted(self, pyfile, archive):
- for script in self.scripts['archive_extracted']:
- self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
-
-
- def packageFinished(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_finished']:
- self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
-
-
- def packageDeleted(self, pid):
- pack = self.core.api.getPackageInfo(pid)
-
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_deleted']:
- self.callScript(script, pack.id, pack.name, download_folder, pack.password)
-
-
- def package_extract_failed(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_extract_failed']:
- self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
-
-
- def package_extracted(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_extracted']:
- self.callScript(script, pypack.id, pypack.name, download_folder)
-
-
- def allDownloadsFinished(self):
- for script in self.scripts['all_downloads_finished']:
- self.callScript(script)
-
-
- def allDownloadsProcessed(self):
- for script in self.scripts['all_downloads_processed']:
- self.callScript(script)
-
-
- def all_archives_extracted(self):
- for script in self.scripts['all_archives_extracted']:
- self.callScript(script)
-
-
- def all_archives_processed(self):
- for script in self.scripts['all_archives_processed']:
- self.callScript(script)
diff --git a/module/plugins/hooks/ExtractArchive.py b/module/plugins/hooks/ExtractArchive.py
deleted file mode 100644
index 05a1e368a..000000000
--- a/module/plugins/hooks/ExtractArchive.py
+++ /dev/null
@@ -1,561 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import sys
-import traceback
-
-# monkey patch bug in python 2.6 and lower
-# http://bugs.python.org/issue6122 , http://bugs.python.org/issue1236 , http://bugs.python.org/issue1731717
-if sys.version_info < (2, 7) and os.name != "nt":
- import errno
- import subprocess
-
- def _eintr_retry_call(func, *args):
- while True:
- try:
- return func(*args)
-
- except OSError, e:
- if e.errno == errno.EINTR:
- continue
- raise
-
-
- # unsued timeout option for older python version
- def wait(self, timeout=0):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode is None:
- try:
- pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
-
- except OSError, e:
- if e.errno != errno.ECHILD:
- raise
- # This happens if SIGCLD is set to be ignored or waiting
- # for child processes has otherwise been disabled for our
- # process. This child is dead, we can't get the status.
- sts = 0
- self._handle_exitstatus(sts)
- return self.returncode
-
- subprocess.Popen.wait = wait
-
-try:
- import send2trash
-except ImportError:
- pass
-
-from copy import copy
-if os.name != "nt":
- from grp import getgrnam
- from pwd import getpwnam
-
-from module.plugins.Hook import Hook, Expose, threaded
-from module.plugins.internal.Extractor import ArchiveError, CRCError, PasswordError
-from module.plugins.internal.SimpleHoster import replace_patterns
-from module.utils import fs_encode, save_join, uniqify
-
-
-class ArchiveQueue(object):
-
- def __init__(self, plugin, storage):
- self.plugin = plugin
- self.storage = storage
-
-
- def get(self):
- try:
- return [int(pid) for pid in self.plugin.getStorage("ExtractArchive:%s" % self.storage, "").decode('base64').split()]
- except Exception:
- return []
-
-
- def set(self, value):
- if isinstance(value, list):
- item = str(value)[1:-1].replace(' ', '').replace(',', ' ')
- else:
- item = str(value).strip()
- return self.plugin.setStorage("ExtractArchive:%s" % self.storage, item.encode('base64')[:-1])
-
-
- def delete(self):
- return self.plugin.delStorage("ExtractArchive:%s" % self.storage)
-
-
- def add(self, item):
- queue = self.get()
- if item not in queue:
- return self.set(queue + [item])
- else:
- return True
-
-
- def remove(self, item):
- queue = self.get()
- try:
- queue.remove(item)
-
- except ValueError:
- pass
-
- if queue == []:
- return self.delete()
-
- return self.set(queue)
-
-
-class ExtractArchive(Hook):
- __name__ = "ExtractArchive"
- __type__ = "hook"
- __version__ = "1.42"
-
- __config__ = [("activated" , "bool" , "Activated" , True ),
- ("fullpath" , "bool" , "Extract with full paths" , True ),
- ("overwrite" , "bool" , "Overwrite files" , False ),
- ("keepbroken" , "bool" , "Try to extract broken archives" , False ),
- ("repair" , "bool" , "Repair broken archives (RAR required)" , False ),
- ("test" , "bool" , "Test archive before extracting" , False ),
- ("usepasswordfile", "bool" , "Use password file" , True ),
- ("passwordfile" , "file" , "Password file" , "archive_password.txt" ),
- ("delete" , "bool" , "Delete archive after extraction" , True ),
- ("deltotrash" , "bool" , "Move to trash (recycle bin) instead delete", True ),
- ("subfolder" , "bool" , "Create subfolder for each package" , False ),
- ("destination" , "folder" , "Extract files to folder" , "" ),
- ("extensions" , "str" , "Extract archives ending with extension" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"),
- ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db" ),
- ("recursive" , "bool" , "Extract archives in archives" , True ),
- ("waitall" , "bool" , "Run after all downloads was processed" , False ),
- ("renice" , "int" , "CPU priority" , 0 )]
-
- __description__ = """Extract different kind of archives"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- event_list = ["allDownloadsProcessed","packageDeleted"]
-
- NAME_REPLACEMENTS = [(r'\.part\d+\.rar$', ".part.rar")]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
- self.queue = ArchiveQueue(self, "Queue")
- self.failed = ArchiveQueue(self, "Failed")
-
- self.interval = 60
- self.extracting = False
- self.lastPackage = False
- self.extractors = []
- self.passwords = []
- self.repair = False
-
-
- def coreReady(self):
- for p in ("UnRar", "SevenZip", "UnZip"):
- try:
- module = self.core.pluginManager.loadModule("internal", p)
- klass = getattr(module, p)
- if klass.isUsable():
- self.extractors.append(klass)
- if klass.REPAIR:
- self.repair = self.getConfig('repair')
-
- except OSError, e:
- if e.errno == 2:
- self.logWarning(_("No %s installed") % p)
- else:
- self.logWarning(_("Could not activate: %s") % p, e)
- if self.core.debug:
- traceback.print_exc()
-
- except Exception, e:
- self.logWarning(_("Could not activate: %s") % p, e)
- if self.core.debug:
- traceback.print_exc()
-
- if self.extractors:
- self.logDebug(*["Found %s %s" % (Extractor.__name__, Extractor.VERSION) for Extractor in self.extractors])
- self.extractQueued() #: Resume unfinished extractions
- else:
- self.logInfo(_("No Extract plugins activated"))
-
-
- @threaded
- def extractQueued(self, thread):
- packages = self.queue.get()
- while packages:
- if self.lastPackage: #: called from allDownloadsProcessed
- self.lastPackage = False
- if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
- self.manager.dispatchEvent("all_archives_extracted")
- self.manager.dispatchEvent("all_archives_processed")
- else:
- if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
- pass
-
- packages = self.queue.get() #: check for packages added during extraction
-
-
- @Expose
- def extractPackage(self, *ids):
- """ Extract packages with given id"""
- for id in ids:
- self.queue.add(id)
- if not self.getConfig('waitall') and not self.extracting:
- self.extractQueued()
-
-
- def packageDeleted(self, pid):
- self.queue.remove(pid)
-
-
- def packageFinished(self, pypack):
- self.queue.add(pypack.id)
- if not self.getConfig('waitall') and not self.extracting:
- self.extractQueued()
-
-
- def allDownloadsProcessed(self):
- self.lastPackage = True
- if not self.extracting:
- self.extractQueued()
-
-
- @Expose
- def extract(self, ids, thread=None): #@TODO: Use pypack, not pid to improve method usability
- if not ids:
- return False
-
- self.extracting = True
-
- processed = []
- extracted = []
- failed = []
-
- toList = lambda string: string.replace(' ', '').replace(',', '|').replace(';', '|').split('|')
-
- destination = self.getConfig('destination')
- subfolder = self.getConfig('subfolder')
- fullpath = self.getConfig('fullpath')
- overwrite = self.getConfig('overwrite')
- renice = self.getConfig('renice')
- recursive = self.getConfig('recursive')
- delete = self.getConfig('delete')
- keepbroken = self.getConfig('keepbroken')
-
- extensions = [x.lstrip('.').lower() for x in toList(self.getConfig('extensions'))]
- excludefiles = toList(self.getConfig('excludefiles'))
-
- if extensions:
- self.logDebug("Use for extensions: %s" % "|.".join(extensions))
-
- # reload from txt file
- self.reloadPasswords()
-
- download_folder = self.config['general']['download_folder']
-
- # iterate packages -> extractors -> targets
- for pid in ids:
- pypack = self.core.files.getPackage(pid)
-
- if not pypack:
- self.queue.remove(pid)
- continue
-
- self.logInfo(_("Check package: %s") % pypack.name)
-
- # determine output folder
- out = save_join(download_folder, pypack.folder, destination, "") #: force trailing slash
-
- if subfolder:
- out = save_join(out, pypack.folder)
-
- if not os.path.exists(out):
- os.makedirs(out)
-
- matched = False
- success = True
- files_ids = dict((pylink['name'],((save_join(download_folder, pypack.folder, pylink['name'])), pylink['id'], out)) for pylink \
- in sorted(pypack.getChildren().itervalues(), key=lambda k: k['name'])).values() #: remove duplicates
-
- # check as long there are unseen files
- while files_ids:
- new_files_ids = []
-
- if extensions:
- files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids \
- if filter(lambda ext: fname.lower().endswith(ext), extensions)]
-
- for Extractor in self.extractors:
- targets = Extractor.getTargets(files_ids)
- if targets:
- self.logDebug("Targets for %s: %s" % (Extractor.__name__, targets))
- matched = True
-
- for fname, fid, fout in targets:
- name = os.path.basename(fname)
-
- if not os.path.exists(fname):
- self.logDebug(name, "File not found")
- continue
-
- self.logInfo(name, _("Extract to: %s") % fout)
- try:
- pyfile = self.core.files.getFile(fid)
- archive = Extractor(self,
- fname,
- fout,
- fullpath,
- overwrite,
- excludefiles,
- renice,
- delete,
- keepbroken,
- fid)
-
- thread.addActive(pyfile)
- archive.init()
-
- try:
- new_files = self._extract(pyfile, archive, pypack.password)
-
- finally:
- pyfile.setProgress(100)
- thread.finishFile(pyfile)
-
- except Exception, e:
- self.logError(name, e)
- success = False
- continue
-
- # remove processed file and related multiparts from list
- files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids \
- if fname not in archive.getDeleteFiles()]
- self.logDebug("Extracted files: %s" % new_files)
- self.setPermissions(new_files)
-
- for filename in new_files:
- file = fs_encode(save_join(os.path.dirname(archive.filename), filename))
- if not os.path.exists(file):
- self.logDebug("New file %s does not exists" % filename)
- continue
-
- if recursive and os.path.isfile(file):
- new_files_ids.append((filename, fid, os.path.dirname(filename))) #: append as new target
-
- self.manager.dispatchEvent("archive_extracted", pyfile, archive)
-
- files_ids = new_files_ids #: also check extracted files
-
- if matched:
- if success:
- extracted.append(pid)
- self.manager.dispatchEvent("package_extracted", pypack)
-
- else:
- failed.append(pid)
- self.manager.dispatchEvent("package_extract_failed", pypack)
-
- self.failed.add(pid)
- else:
- self.logInfo(_("No files found to extract"))
-
- if not matched or not success and subfolder:
- try:
- os.rmdir(out)
-
- except OSError:
- pass
-
- self.queue.remove(pid)
-
- self.extracting = False
- return True if not failed else False
-
-
- def _extract(self, pyfile, archive, password):
- name = os.path.basename(archive.filename)
-
- pyfile.setStatus("processing")
-
- encrypted = False
- try:
- self.logDebug("Password: %s" % (password or "None provided"))
- passwords = uniqify([password] + self.getPasswords(False)) if self.getConfig('usepasswordfile') else [password]
- for pw in passwords:
- try:
- if self.getConfig('test') or self.repair:
- pyfile.setCustomStatus(_("archive testing"))
- if pw:
- self.logDebug("Testing with password: %s" % pw)
- pyfile.setProgress(0)
- archive.verify(pw)
- pyfile.setProgress(100)
- else:
- archive.check(pw)
-
- self.addPassword(pw)
- break
-
- except PasswordError:
- if not encrypted:
- self.logInfo(name, _("Password protected"))
- encrypted = True
-
- except CRCError, e:
- self.logDebug(name, e)
- self.logInfo(name, _("CRC Error"))
-
- if self.repair:
- self.logWarning(name, _("Repairing..."))
-
- pyfile.setCustomStatus(_("archive repairing"))
- pyfile.setProgress(0)
- repaired = archive.repair()
- pyfile.setProgress(100)
-
- if not repaired and not self.getConfig('keepbroken'):
- raise CRCError("Archive damaged")
-
- self.addPassword(pw)
- break
-
- raise CRCError("Archive damaged")
-
- except ArchiveError, e:
- raise ArchiveError(e)
-
- pyfile.setCustomStatus(_("extracting"))
- pyfile.setProgress(0)
-
- if not encrypted or not self.getConfig('usepasswordfile'):
- self.logDebug("Extracting using password: %s" % (password or "None"))
- archive.extract(password)
- else:
- for pw in filter(None, uniqify([password] + self.getPasswords(False))):
- try:
- self.logDebug("Extracting using password: %s" % pw)
-
- archive.extract(pw)
- self.addPassword(pw)
- break
-
- except PasswordError:
- self.logDebug("Password was wrong")
- else:
- raise PasswordError
-
- pyfile.setProgress(100)
- pyfile.setStatus("processing")
-
- delfiles = archive.getDeleteFiles()
- self.logDebug("Would delete: " + ", ".join(delfiles))
-
- if self.getConfig('delete'):
- self.logInfo(_("Deleting %s files") % len(delfiles))
-
- deltotrash = self.getConfig('deltotrash')
- for f in delfiles:
- file = fs_encode(f)
- if not os.path.exists(file):
- continue
-
- if not deltotrash:
- os.remove(file)
-
- else:
- try:
- send2trash.send2trash(file)
-
- except Exception:
- self.logWarning(_("Unable to move %s to trash") % os.path.basename(f))
-
- self.logInfo(name, _("Extracting finished"))
- extracted_files = archive.files or archive.list()
-
- return extracted_files
-
- except PasswordError:
- self.logError(name, _("Wrong password" if password else "No password found"))
-
- except CRCError, e:
- self.logError(name, _("CRC mismatch"), e)
-
- except ArchiveError, e:
- self.logError(name, _("Archive error"), e)
-
- except Exception, e:
- self.logError(name, _("Unknown error"), e)
- if self.core.debug:
- traceback.print_exc()
-
- self.manager.dispatchEvent("archive_extract_failed", pyfile, archive)
-
- raise Exception(_("Extract failed"))
-
-
- @Expose
- def getPasswords(self, reload=True):
- """ List of saved passwords """
- if reload:
- self.reloadPasswords()
-
- return self.passwords
-
-
- def reloadPasswords(self):
- try:
- passwords = []
-
- file = fs_encode(self.getConfig('passwordfile'))
- with open(file) as f:
- for pw in f.read().splitlines():
- passwords.append(pw)
-
- except IOError, e:
- self.logError(e)
-
- else:
- self.passwords = passwords
-
-
- @Expose
- def addPassword(self, password):
- """ Adds a password to saved list"""
- try:
- self.passwords = uniqify([password] + self.passwords)
-
- file = fs_encode(self.getConfig('passwordfile'))
- with open(file, "wb") as f:
- for pw in self.passwords:
- f.write(pw + '\n')
-
- except IOError, e:
- self.logError(e)
-
-
- def setPermissions(self, files):
- for f in files:
- if not os.path.exists(f):
- continue
-
- try:
- if self.config['permission']['change_file']:
- if os.path.isfile(f):
- os.chmod(f, int(self.config['permission']['file'], 8))
-
- elif os.path.isdir(f):
- os.chmod(f, int(self.config['permission']['folder'], 8))
-
- if self.config['permission']['change_dl'] and os.name != "nt":
- uid = getpwnam(self.config['permission']['user'])[2]
- gid = getgrnam(self.config['permission']['group'])[2]
- os.chown(f, uid, gid)
-
- except Exception, e:
- self.logWarning(_("Setting User and Group failed"), e)
diff --git a/module/plugins/hooks/FastixRuHook.py b/module/plugins/hooks/FastixRuHook.py
deleted file mode 100644
index 16e30e93a..000000000
--- a/module/plugins/hooks/FastixRuHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class FastixRuHook(MultiHook):
- __name__ = "FastixRuHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Fastix.ru hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def getHosters(self):
- html = self.getURL("http://fastix.ru/api_v2",
- get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y",
- 'sub' : "allowed_sources"})
- host_list = json_loads(html)
- host_list = host_list['allow']
- return host_list
diff --git a/module/plugins/hooks/FreeWayMeHook.py b/module/plugins/hooks/FreeWayMeHook.py
deleted file mode 100644
index baea44540..000000000
--- a/module/plugins/hooks/FreeWayMeHook.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class FreeWayMeHook(MultiHook):
- __name__ = "FreeWayMeHook"
- __type__ = "hook"
- __version__ = "0.15"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """FreeWay.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def getHosters(self):
- # Get account data
- if not self.account or not self.account.canUse():
- hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
- else:
- self.logDebug("AccountInfo available - Get HosterList with User Pass")
- (user, data) = self.account.selectAccount()
- hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3, "user": user, "pass": data['password']}).replace("\"", "").strip()
-
- self.logDebug("hosters: %s" % hostis)
- return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/module/plugins/hooks/HotFolder.py b/module/plugins/hooks/HotFolder.py
deleted file mode 100644
index f771cf232..000000000
--- a/module/plugins/hooks/HotFolder.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import time
-
-from shutil import move
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode, save_join
-
-
-class HotFolder(Hook):
- __name__ = "HotFolder"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("folder" , "str" , "Folder to observe" , "container"),
- ("watch_file", "bool", "Observe link file" , False ),
- ("keep" , "bool", "Keep added containers", True ),
- ("file" , "str" , "Link file" , "links.txt")]
-
- __description__ = """Observe folder and file for changes and add container and links"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.de")]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = 30
-
-
- def periodical(self):
- folder = fs_encode(self.getConfig('folder'))
- file = fs_encode(self.getConfig('file'))
-
- try:
- if not os.path.isdir(os.path.join(folder, "finished")):
- os.makedirs(os.path.join(folder, "finished"))
-
- if self.getConfig('watch_file'):
- with open(file, "a+") as f:
- f.seek(0)
- content = f.read().strip()
-
- if content:
- f = open(file, "wb")
- f.close()
-
- name = "%s_%s.txt" % (file, time.strftime("%H-%M-%S_%d%b%Y"))
-
- with open(save_join(folder, "finished", name), "wb") as f:
- f.write(content)
-
- self.core.api.addPackage(f.name, [f.name], 1)
-
- for f in os.listdir(folder):
- path = os.path.join(folder, f)
-
- if not os.path.isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
- continue
-
- newpath = os.path.join(folder, "finished", f if self.getConfig('keep') else "tmp_" + f)
- move(path, newpath)
-
- self.logInfo(_("Added %s from HotFolder") % f)
- self.core.api.addPackage(f, [newpath], 1)
-
- except (IOError, OSError), e:
- self.logError(e)
diff --git a/module/plugins/hooks/IRCInterface.py b/module/plugins/hooks/IRCInterface.py
deleted file mode 100644
index 33fde3d20..000000000
--- a/module/plugins/hooks/IRCInterface.py
+++ /dev/null
@@ -1,433 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import socket
-import ssl
-import time
-import traceback
-
-from select import select
-from threading import Thread
-
-from module.Api import PackageDoesNotExists, FileDoesNotExists
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook
-from module.utils import formatSize
-
-
-class IRCInterface(Thread, Hook):
- __name__ = "IRCInterface"
- __type__ = "hook"
- __version__ = "0.13"
-
- __config__ = [("host", "str", "IRC-Server Address", "Enter your server here!"),
- ("port", "int", "IRC-Server Port", 6667),
- ("ident", "str", "Clients ident", "pyload-irc"),
- ("realname", "str", "Realname", "pyload-irc"),
- ("ssl", "bool", "Use SSL", False),
- ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"),
- ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"),
- ("info_file", "bool", "Inform about every file finished", False),
- ("info_pack", "bool", "Inform about every package finished", True),
- ("captcha", "bool", "Send captcha requests", True)]
-
- __description__ = """Connect to irc and let owner perform different tasks"""
- __license__ = "GPLv3"
- __authors__ = [("Jeix", "Jeix@hasnomail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def __init__(self, core, manager):
- Thread.__init__(self)
- Hook.__init__(self, core, manager)
- self.setDaemon(True)
-
-
- def coreReady(self):
- self.abort = False
- self.more = []
- self.new_package = {}
-
- self.start()
-
-
- def packageFinished(self, pypack):
- try:
- if self.getConfig('info_pack'):
- self.response(_("Package finished: %s") % pypack.name)
- except Exception:
- pass
-
-
- def downloadFinished(self, pyfile):
- try:
- if self.getConfig('info_file'):
- self.response(
- _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname})
- except Exception:
- pass
-
-
- def newCaptchaTask(self, task):
- if self.getConfig('captcha') and task.isTextual():
- task.handler.append(self)
- task.setWaiting(60)
-
- html = getURL("http://www.freeimagehosting.net/upload.php",
- post={"attached": (pycurl.FORM_FILE, task.captchaFile)}, multipart=True)
-
- url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", html).group(1)
- self.response(_("New Captcha Request: %s") % url)
- self.response(_("Answer with 'c %s text on the captcha'") % task.id)
-
-
- def run(self):
- # connect to IRC etc.
- self.sock = socket.socket()
- host = self.getConfig('host')
- self.sock.connect((host, self.getConfig('port')))
-
- if self.getConfig('ssl'):
- self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE) #@TODO: support certificate
-
- nick = self.getConfig('nick')
- self.sock.send("NICK %s\r\n" % nick)
- self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick))
- for t in self.getConfig('owner').split():
- if t.strip().startswith("#"):
- self.sock.send("JOIN %s\r\n" % t.strip())
- self.logInfo(_("Connected to"), host)
- self.logInfo(_("Switching to listening mode!"))
- try:
- self.main_loop()
-
- except IRCError, ex:
- self.sock.send("QUIT :byebye\r\n")
- traceback.print_exc()
- self.sock.close()
-
-
- def main_loop(self):
- readbuffer = ""
- while True:
- time.sleep(1)
- fdset = select([self.sock], [], [], 0)
- if self.sock not in fdset[0]:
- continue
-
- if self.abort:
- raise IRCError("quit")
-
- readbuffer += self.sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- line = line.rstrip()
- first = line.split()
-
- if first[0] == "PING":
- self.sock.send("PONG %s\r\n" % first[1])
-
- if first[0] == "ERROR":
- raise IRCError(line)
-
- msg = line.split(None, 3)
- if len(msg) < 4:
- continue
-
- msg = {
- "origin": msg[0][1:],
- "action": msg[1],
- "target": msg[2],
- "text": msg[3][1:]
- }
-
- self.handle_events(msg)
-
-
- def handle_events(self, msg):
- if not msg['origin'].split("!", 1)[0] in self.getConfig('owner').split():
- return
-
- if msg['target'].split("!", 1)[0] != self.getConfig('nick'):
- return
-
- if msg['action'] != "PRIVMSG":
- return
-
- # HANDLE CTCP ANTI FLOOD/BOT PROTECTION
- if msg['text'] == "\x01VERSION\x01":
- self.logDebug("Sending CTCP VERSION")
- self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
- return
- elif msg['text'] == "\x01TIME\x01":
- self.logDebug("Sending CTCP TIME")
- self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
- return
- elif msg['text'] == "\x01LAG\x01":
- self.logDebug("Received CTCP LAG") #: don't know how to answer
- return
-
- trigger = "pass"
- args = None
-
- try:
- temp = msg['text'].split()
- trigger = temp[0]
- if len(temp) > 1:
- args = temp[1:]
- except Exception:
- pass
-
- handler = getattr(self, "event_%s" % trigger, self.event_pass)
- try:
- res = handler(args)
- for line in res:
- self.response(line, msg['origin'])
- except Exception, e:
- self.logError(e)
-
-
- def response(self, msg, origin=""):
- if origin == "":
- for t in self.getConfig('owner').split():
- self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg))
- else:
- self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg))
-
-
- #### Events
-
- def event_pass(self, args):
- return []
-
-
- def event_status(self, args):
- downloads = self.core.api.statusDownloads()
- if not downloads:
- return ["INFO: There are no active downloads currently."]
-
- temp_progress = ""
- lines = ["ID - Name - Status - Speed - ETA - Progress"]
- for data in downloads:
-
- if data.status == 5:
- temp_progress = data.format_wait
- else:
- temp_progress = "%d%% (%s)" % (data.percent, data.format_size)
-
- lines.append("#%d - %s - %s - %s - %s - %s" %
- (
- data.fid,
- data.name,
- data.statusmsg,
- "%s/s" % formatSize(data.speed),
- "%s" % data.format_eta,
- temp_progress
- ))
- return lines
-
-
- def event_queue(self, args):
- ps = self.core.api.getQueueData()
-
- if not ps:
- return ["INFO: There are no packages in queue."]
-
- lines = []
- for pack in ps:
- lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
-
- return lines
-
-
- def event_collector(self, args):
- ps = self.core.api.getCollectorData()
- if not ps:
- return ["INFO: No packages in collector!"]
-
- lines = []
- for pack in ps:
- lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
-
- return lines
-
-
- def event_info(self, args):
- if not args:
- return ["ERROR: Use info like this: info <id>"]
-
- info = None
- try:
- info = self.core.api.getFileData(int(args[0]))
-
- except FileDoesNotExists:
- return ["ERROR: Link doesn't exists."]
-
- return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)]
-
-
- def event_packinfo(self, args):
- if not args:
- return ["ERROR: Use packinfo like this: packinfo <id>"]
-
- lines = []
- pack = None
- try:
- pack = self.core.api.getPackageData(int(args[0]))
-
- except PackageDoesNotExists:
- return ["ERROR: Package doesn't exists."]
-
- id = args[0]
-
- self.more = []
-
- lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links)))
- for pyfile in pack.links:
- self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size,
- pyfile.statusmsg, pyfile.plugin))
-
- if len(self.more) < 6:
- lines.extend(self.more)
- self.more = []
- else:
- lines.extend(self.more[:6])
- self.more = self.more[6:]
- lines.append("%d more links do display." % len(self.more))
-
- return lines
-
-
- def event_more(self, args):
- if not self.more:
- return ["No more information to display."]
-
- lines = self.more[:6]
- self.more = self.more[6:]
- lines.append("%d more links do display." % len(self.more))
-
- return lines
-
-
- def event_start(self, args):
- self.core.api.unpauseServer()
- return ["INFO: Starting downloads."]
-
-
- def event_stop(self, args):
- self.core.api.pauseServer()
- return ["INFO: No new downloads will be started."]
-
-
- def event_add(self, args):
- if len(args) < 2:
- return ['ERROR: Add links like this: "add <packagename|id> links". ',
- "This will add the link <link> to to the package <package> / the package with id <id>!"]
-
- pack = args[0].strip()
- links = [x.strip() for x in args[1:]]
-
- count_added = 0
- count_failed = 0
- try:
- id = int(pack)
- pack = self.core.api.getPackageData(id)
- if not pack:
- return ["ERROR: Package doesn't exists."]
-
- #TODO add links
-
- return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack['name'], id)]
-
- except Exception:
- # create new package
- id = self.core.api.addPackage(pack, links, 1)
- return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))]
-
-
- def event_del(self, args):
- if len(args) < 2:
- return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
-
- if args[0] == "-p":
- ret = self.core.api.deletePackages(map(int, args[1:]))
- return ["INFO: Deleted %d packages!" % len(args[1:])]
-
- elif args[0] == "-l":
- ret = self.core.api.delLinks(map(int, args[1:]))
- return ["INFO: Deleted %d links!" % len(args[1:])]
-
- else:
- return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
-
-
- def event_push(self, args):
- if not args:
- return ["ERROR: Push package to queue like this: push <package id>"]
-
- id = int(args[0])
- try:
- info = self.core.api.getPackageInfo(id)
- except PackageDoesNotExists:
- return ["ERROR: Package #%d does not exist." % id]
-
- self.core.api.pushToQueue(id)
- return ["INFO: Pushed package #%d to queue." % id]
-
-
- def event_pull(self, args):
- if not args:
- return ["ERROR: Pull package from queue like this: pull <package id>."]
-
- id = int(args[0])
- if not self.core.api.getPackageData(id):
- return ["ERROR: Package #%d does not exist." % id]
-
- self.core.api.pullFromQueue(id)
- return ["INFO: Pulled package #%d from queue to collector." % id]
-
-
- def event_c(self, args):
- """ captcha answer """
- if not args:
- return ["ERROR: Captcha ID missing."]
-
- task = self.core.captchaManager.getTaskByID(args[0])
- if not task:
- return ["ERROR: Captcha Task with ID %s does not exists." % args[0]]
-
- task.setResult(" ".join(args[1:]))
- return ["INFO: Result %s saved." % " ".join(args[1:])]
-
-
- def event_help(self, args):
- lines = ["The following commands are available:",
- "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)",
- "queue Shows all packages in the queue",
- "collector Shows all packages in collector",
- "del -p|-l <id> [...] Deletes all packages|links with the ids specified",
- "info <id> Shows info of the link with id <id>",
- "packinfo <id> Shows info of the package with id <id>",
- "more Shows more info when the result was truncated",
- "start Starts all downloads",
- "stop Stops the download (but not abort active downloads)",
- "push <id> Push package to queue",
- "pull <id> Pull package from queue",
- "status Show general download status",
- "help Shows this help message"]
- return lines
-
-
-class IRCError(Exception):
-
- def __init__(self, value):
- self.value = value
-
-
- def __str__(self):
- return repr(self.value)
diff --git a/module/plugins/hooks/ImageTyperz.py b/module/plugins/hooks/ImageTyperz.py
deleted file mode 100644
index f1fcacb71..000000000
--- a/module/plugins/hooks/ImageTyperz.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import re
-
-from base64 import b64encode
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class ImageTyperzException(Exception):
-
- def __init__(self, err):
- self.err = err
-
-
- def getCode(self):
- return self.err
-
-
- def __str__(self):
- return "<ImageTyperzException %s>" % self.err
-
-
- def __repr__(self):
- return "<ImageTyperzException %s>" % self.err
-
-
-class ImageTyperz(Hook):
- __name__ = "ImageTyperz"
- __type__ = "hook"
- __version__ = "0.06"
-
- __config__ = [("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force IT even if client is connected", False)]
-
- __description__ = """Send captchas to ImageTyperz.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx"
- RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx"
- GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.GETCREDITS_URL,
- post={'action': "REQUESTBALANCE",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey')})
-
- if res.startswith('ERROR'):
- raise ImageTyperzException(res)
-
- try:
- balance = float(res)
- except Exception:
- raise ImageTyperzException("Invalid response")
-
- self.logInfo(_("Account balance: $%s left") % res)
- return balance
-
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- #@NOTE: Workaround multipart-post bug in HTTPRequest.py
- if re.match("^\w*$", self.getConfig('passkey')):
- multipart = True
- data = (pycurl.FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = b64encode(data)
-
- res = req.load(self.SUBMIT_URL,
- post={'action': "UPLOADCAPTCHA",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey'), "file": data},
- multipart=multipart)
- finally:
- req.close()
-
- if res.startswith("ERROR"):
- raise ImageTyperzException(res)
- else:
- data = res.split('|')
- if len(data) == 2:
- ticket, result = data
- else:
- raise ImageTyperzException("Unknown response: %s" % res)
-
- return ticket, result
-
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig('username') or not self.getConfig('passkey'):
- return False
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- self._processCaptcha(task)
-
- else:
- self.logInfo(_("Your %s account has not enough credits") % self.__name__)
-
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- res = getURL(self.RESPOND_URL,
- post={'action': "SETBADIMAGE",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey'),
- 'imageid': task.data['ticket']})
-
- if res == "SUCCESS":
- self.logInfo(_("Bad captcha solution received, requested refund"))
- else:
- self.logError(_("Bad captcha solution received, refund request failed"), res)
-
-
- @threaded
- def _processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except ImageTyperzException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/JustPremium.py b/module/plugins/hooks/JustPremium.py
deleted file mode 100644
index d18afc524..000000000
--- a/module/plugins/hooks/JustPremium.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-
-
-class JustPremium(Hook):
- __name__ = "JustPremium"
- __type__ = "hook"
- __version__ = "0.22"
-
- __config__ = [("excluded", "str", "Exclude hosters (comma separated)", ""),
- ("included", "str", "Include hosters (comma separated)", "")]
-
- __description__ = """Remove not-premium links from added urls"""
- __license__ = "GPLv3"
- __authors__ = [("mazleu" , "mazleica@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("immenz" , "immenz@gmx.net" )]
-
-
- event_list = ["linksAdded"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def linksAdded(self, links, pid):
- hosterdict = self.core.pluginManager.hosterPlugins
- linkdict = self.core.api.checkURLs(links)
-
- premiumplugins = set(account.type for account in self.core.api.getAccounts(False) \
- if account.valid and account.premium)
- multihosters = set(hoster for hoster in self.core.pluginManager.hosterPlugins \
- if 'new_name' in hosterdict[hoster] \
- and hosterdict[hoster]['new_name'] in premiumplugins)
-
- excluded = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
- self.getConfig('excluded').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
- included = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
- self.getConfig('included').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
-
- hosterlist = (premiumplugins | multihosters).union(excluded).difference(included)
-
- #: Found at least one hoster with account or multihoster
- if not any( True for pluginname in linkdict if pluginname in hosterlist ):
- return
-
- for pluginname in set(linkdict.keys()) - hosterlist:
- self.logInfo(_("Remove links of plugin: %s") % pluginname)
- for link in linkdict[pluginname]:
- self.logDebug("Remove link: %s" % link)
- links.remove(link)
diff --git a/module/plugins/hooks/LinkdecrypterComHook.py b/module/plugins/hooks/LinkdecrypterComHook.py
deleted file mode 100644
index 596a397a4..000000000
--- a/module/plugins/hooks/LinkdecrypterComHook.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class LinkdecrypterComHook(MultiHook):
- __name__ = "LinkdecrypterComHook"
- __type__ = "hook"
- __version__ = "1.04"
-
- __config__ = [("activated" , "bool" , "Activated" , True ),
- ("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Linkdecrypter.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def getCrypters(self):
- return re.search(r'>Supported\(\d+\)</b>: <i>(.[\w.\-, ]+)',
- self.getURL("http://linkdecrypter.com/", decode=True).replace("(g)", "")).group(1).split(', ')
diff --git a/module/plugins/hooks/LinksnappyComHook.py b/module/plugins/hooks/LinksnappyComHook.py
deleted file mode 100644
index 72282575b..000000000
--- a/module/plugins/hooks/LinksnappyComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class LinksnappyComHook(MultiHook):
- __name__ = "LinksnappyComHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Linksnappy.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://gen.linksnappy.com/lseAPI.php", get={'act': "FILEHOSTS"})
- json_data = json_loads(json_data)
-
- return json_data['return'].keys()
diff --git a/module/plugins/hooks/MegaDebridEuHook.py b/module/plugins/hooks/MegaDebridEuHook.py
deleted file mode 100644
index 0de7b4dcf..000000000
--- a/module/plugins/hooks/MegaDebridEuHook.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MegaDebridEuHook(MultiHook):
- __name__ = "MegaDebridEuHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Mega-debrid.eu hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
-
-
- def getHosters(self):
- reponse = self.getURL("http://www.mega-debrid.eu/api.php", get={'action': "getHosters"})
- json_data = json_loads(reponse)
-
- if json_data['response_code'] == "ok":
- host_list = [element[0] for element in json_data['hosters']]
- else:
- self.logError(_("Unable to retrieve hoster list"))
- host_list = list()
-
- return host_list
diff --git a/module/plugins/hooks/MegaRapidoNetHook.py b/module/plugins/hooks/MegaRapidoNetHook.py
deleted file mode 100644
index e113b305e..000000000
--- a/module/plugins/hooks/MegaRapidoNetHook.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MegaRapidoNetHook(MultiHook):
- __name__ = "MegaRapidoNetHook"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """MegaRapido.net hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def getHosters(self):
- hosters = {'1fichier' : [],#leave it there are so many possible addresses?
- '1st-files' : ['1st-files.com'],
- '2shared' : ['2shared.com'],
- '4shared' : ['4shared.com', '4shared-china.com'],
- 'asfile' : ['http://asfile.com/'],
- 'bitshare' : ['bitshare.com'],
- 'brupload' : ['brupload.net'],
- 'crocko' : ['crocko.com','easy-share.com'],
- 'dailymotion' : ['dailymotion.com'],
- 'depfile' : ['depfile.com'],
- 'depositfiles': ['depositfiles.com', 'dfiles.eu'],
- 'dizzcloud' : ['dizzcloud.com'],
- 'dl.dropbox' : [],
- 'extabit' : ['extabit.com'],
- 'extmatrix' : ['extmatrix.com'],
- 'facebook' : [],
- 'file4go' : ['file4go.com'],
- 'filecloud' : ['filecloud.io','ifile.it','mihd.net'],
- 'filefactory' : ['filefactory.com'],
- 'fileom' : ['fileom.com'],
- 'fileparadox' : ['fileparadox.in'],
- 'filepost' : ['filepost.com', 'fp.io'],
- 'filerio' : ['filerio.in','filerio.com','filekeen.com'],
- 'filesflash' : ['filesflash.com'],
- 'firedrive' : ['firedrive.com', 'putlocker.com'],
- 'flashx' : [],
- 'freakshare' : ['freakshare.net', 'freakshare.com'],
- 'gigasize' : ['gigasize.com'],
- 'hipfile' : ['hipfile.com'],
- 'junocloud' : ['junocloud.me'],
- 'letitbit' : ['letitbit.net','shareflare.net'],
- 'mediafire' : ['mediafire.com'],
- 'mega' : ['mega.co.nz'],
- 'megashares' : ['megashares.com'],
- 'metacafe' : ['metacafe.com'],
- 'netload' : ['netload.in'],
- 'oboom' : ['oboom.com'],
- 'rapidgator' : ['rapidgator.net'],
- 'rapidshare' : ['rapidshare.com'],
- 'rarefile' : ['rarefile.net'],
- 'ryushare' : ['ryushare.com'],
- 'sendspace' : ['sendspace.com'],
- 'turbobit' : ['turbobit.net', 'unextfiles.com'],
- 'uploadable' : ['uploadable.ch'],
- 'uploadbaz' : ['uploadbaz.com'],
- 'uploaded' : ['uploaded.to', 'uploaded.net', 'ul.to'],
- 'uploadhero' : ['uploadhero.com'],
- 'uploading' : ['uploading.com'],
- 'uptobox' : ['uptobox.com'],
- 'xvideos' : ['xvideos.com'],
- 'youtube' : ['youtube.com']}
-
- hoster_list = []
-
- for item in hosters.itervalues():
- hoster_list.extend(item)
-
- return hoster_list
diff --git a/module/plugins/hooks/MergeFiles.py b/module/plugins/hooks/MergeFiles.py
deleted file mode 100644
index 941938920..000000000
--- a/module/plugins/hooks/MergeFiles.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import re
-import traceback
-
-from module.plugins.Hook import Hook, threaded
-from module.utils import save_join
-
-
-class MergeFiles(Hook):
- __name__ = "MergeFiles"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Merges parts splitted with hjsplit"""
- __license__ = "GPLv3"
- __authors__ = [("and9000", "me@has-no-mail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- BUFFER_SIZE = 4096
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @threaded
- def packageFinished(self, pack):
- files = {}
- fid_dict = {}
- for fid, data in pack.getChildren().iteritems():
- if re.search("\.\d{3}$", data['name']):
- if data['name'][:-4] not in files:
- files[data['name'][:-4]] = []
- files[data['name'][:-4]].append(data['name'])
- files[data['name'][:-4]].sort()
- fid_dict[data['name']] = fid
-
- download_folder = self.config['general']['download_folder']
-
- if self.config['general']['folder_per_package']:
- download_folder = save_join(download_folder, pack.folder)
-
- for name, file_list in files.iteritems():
- self.logInfo(_("Starting merging of"), name)
-
- with open(save_join(download_folder, name), "wb") as final_file:
- for splitted_file in file_list:
- self.logDebug("Merging part", splitted_file)
-
- pyfile = self.core.files.getFile(fid_dict[splitted_file])
-
- pyfile.setStatus("processing")
-
- try:
- with open(save_join(download_folder, splitted_file), "rb") as s_file:
- size_written = 0
- s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file)))
- while True:
- f_buffer = s_file.read(self.BUFFER_SIZE)
- if f_buffer:
- final_file.write(f_buffer)
- size_written += self.BUFFER_SIZE
- pyfile.setProgress((size_written * 100) / s_file_size)
- else:
- break
- self.logDebug("Finished merging part", splitted_file)
-
- except Exception, e:
- traceback.print_exc()
-
- finally:
- pyfile.setProgress(100)
- pyfile.setStatus("finished")
- pyfile.release()
-
- self.logInfo(_("Finished merging of"), name)
diff --git a/module/plugins/hooks/MultiHome.py b/module/plugins/hooks/MultiHome.py
deleted file mode 100644
index a26d139c0..000000000
--- a/module/plugins/hooks/MultiHome.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Hook import Hook
-
-
-class MultiHome(Hook):
- __name__ = "MultiHome"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("interfaces", "str", "Interfaces", "None")]
-
- __description__ = """Ip address changer"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.register = {}
- self.interfaces = []
-
- self.parseInterfaces(self.getConfig('interfaces').split(";"))
-
- if not self.interfaces:
- self.parseInterfaces([self.config['download']['interface']])
- self.setConfig("interfaces", self.toConfig())
-
-
- def toConfig(self):
- return ";".join(i.adress for i in self.interfaces)
-
-
- def parseInterfaces(self, interfaces):
- for interface in interfaces:
- if not interface or str(interface).lower() == "none":
- continue
- self.interfaces.append(Interface(interface))
-
-
- def coreReady(self):
- requestFactory = self.core.requestFactory
- oldGetRequest = requestFactory.getRequest
-
-
- def getRequest(pluginName, account=None):
- iface = self.bestInterface(pluginName, account)
- if iface:
- iface.useFor(pluginName, account)
- requestFactory.iface = lambda: iface.adress
- self.logDebug("Using address", iface.adress)
- return oldGetRequest(pluginName, account)
-
- requestFactory.getRequest = getRequest
-
-
- def bestInterface(self, pluginName, account):
- best = None
- for interface in self.interfaces:
- if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account):
- best = interface
- return best
-
-
-class Interface(object):
-
- def __init__(self, adress):
- self.adress = adress
- self.history = {}
-
-
- def lastPluginAccess(self, pluginName, account):
- if (pluginName, account) in self.history:
- return self.history[(pluginName, account)]
- return 0
-
-
- def useFor(self, pluginName, account):
- self.history[(pluginName, account)] = time.time()
-
-
- def __repr__(self):
- return "<Interface - %s>" % self.adress
diff --git a/module/plugins/hooks/MultihostersComHook.py b/module/plugins/hooks/MultihostersComHook.py
deleted file mode 100644
index 7b5e49c49..000000000
--- a/module/plugins/hooks/MultihostersComHook.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hooks.ZeveraComHook import ZeveraComHook
-
-
-class MultihostersComHook(ZeveraComHook):
- __name__ = "MultihostersComHook"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed", "bool" , "Revert to standard download if download fails", False),
- ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
-
- __description__ = """Multihosters.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
diff --git a/module/plugins/hooks/MultishareCzHook.py b/module/plugins/hooks/MultishareCzHook.py
deleted file mode 100644
index 6052b7673..000000000
--- a/module/plugins/hooks/MultishareCzHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MultishareCzHook(MultiHook):
- __name__ = "MultishareCzHook"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """MultiShare.cz hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="(.+?)"></td>\s*<td class="stav">[^>]*?alt="OK"'
-
-
- def getHosters(self):
- html = self.getURL("http://www.multishare.cz/monitoring/")
- return re.findall(self.HOSTER_PATTERN, html)
diff --git a/module/plugins/hooks/MyfastfileComHook.py b/module/plugins/hooks/MyfastfileComHook.py
deleted file mode 100644
index 20a1cfac2..000000000
--- a/module/plugins/hooks/MyfastfileComHook.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MyfastfileComHook(MultiHook):
- __name__ = "MyfastfileComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Myfastfile.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://myfastfile.com/api.php", get={'hosts': ""}, decode=True)
- self.logDebug("JSON data", json_data)
- json_data = json_loads(json_data)
-
- return json_data['hosts']
diff --git a/module/plugins/hooks/NoPremiumPlHook.py b/module/plugins/hooks/NoPremiumPlHook.py
deleted file mode 100644
index b5a007ff9..000000000
--- a/module/plugins/hooks/NoPremiumPlHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class NoPremiumPlHook(MultiHook):
- __name__ = "NoPremiumPlHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """NoPremium.pl hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
-
- def getHosters(self):
- hostings = json_loads(self.getURL("https://www.nopremium.pl/clipboard.php?json=3").strip())
- hostings_domains = [domain for row in hostings for domain in row["domains"] if row["sdownload"] == "0"]
-
- self.logDebug(hostings_domains)
-
- return hostings_domains
diff --git a/module/plugins/hooks/OverLoadMeHook.py b/module/plugins/hooks/OverLoadMeHook.py
deleted file mode 100644
index d608a2ecd..000000000
--- a/module/plugins/hooks/OverLoadMeHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class OverLoadMeHook(MultiHook):
- __name__ = "OverLoadMeHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Over-Load.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://api.over-load.me/hoster.php",
- get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip()
- self.logDebug("Hosterlist", html)
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/PremiumToHook.py b/module/plugins/hooks/PremiumToHook.py
deleted file mode 100644
index ef2a84223..000000000
--- a/module/plugins/hooks/PremiumToHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class PremiumToHook(MultiHook):
- __name__ = "PremiumToHook"
- __type__ = "hook"
- __version__ = "0.08"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Premium.to hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- html = self.getURL("http://premium.to/api/hosters.php",
- get={'username': self.account.username, 'password': self.account.password})
- return [x.strip() for x in html.replace("\"", "").split(";")]
diff --git a/module/plugins/hooks/PremiumizeMeHook.py b/module/plugins/hooks/PremiumizeMeHook.py
deleted file mode 100644
index e081fb389..000000000
--- a/module/plugins/hooks/PremiumizeMeHook.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class PremiumizeMeHook(MultiHook):
- __name__ = "PremiumizeMeHook"
- __type__ = "hook"
- __version__ = "0.17"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Premiumize.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
-
-
- def getHosters(self):
- # Get account data
- user, data = self.account.selectAccount()
-
- # Get supported hosters list from premiumize.me using the
- # json API v1 (see https://secure.premiumize.me/?show=api)
- answer = self.getURL("https://api.premiumize.me/pm-api/v1.php",
- get={'method': "hosterlist", 'params[login]': user, 'params[pass]': data['password']})
- data = json_loads(answer)
-
- # If account is not valid thera are no hosters available
- if data['status'] != 200:
- return []
-
- # Extract hosters from json file
- return data['result']['hosterlist']
diff --git a/module/plugins/hooks/PutdriveComHook.py b/module/plugins/hooks/PutdriveComHook.py
deleted file mode 100644
index c3ebf4ff3..000000000
--- a/module/plugins/hooks/PutdriveComHook.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hooks.ZeveraComHook import ZeveraComHook
-
-
-class PutdriveComHook(ZeveraComHook):
- __name__ = "PutdriveComHook"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed", "bool" , "Revert to standard download if download fails", False),
- ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
-
- __description__ = """Putdrive.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/module/plugins/hooks/RPNetBizHook.py b/module/plugins/hooks/RPNetBizHook.py
deleted file mode 100644
index 10332948d..000000000
--- a/module/plugins/hooks/RPNetBizHook.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RPNetBizHook(MultiHook):
- __name__ = "RPNetBizHook"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """RPNet.biz hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def getHosters(self):
- # Get account data
- user, data = self.account.selectAccount()
-
- res = self.getURL("https://premium.rpnet.biz/client_api.php",
- get={'username': user, 'password': data['password'], 'action': "showHosterList"})
- hoster_list = json_loads(res)
-
- # If account is not valid thera are no hosters available
- if 'error' in hoster_list:
- return []
-
- # Extract hosters from json file
- return hoster_list['hosters']
diff --git a/module/plugins/hooks/RapideoPlHook.py b/module/plugins/hooks/RapideoPlHook.py
deleted file mode 100644
index 0400f07ba..000000000
--- a/module/plugins/hooks/RapideoPlHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RapideoPlHook(MultiHook):
- __name__ = "RapideoPlHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Rapideo.pl hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
-
- def getHosters(self):
- hostings = json_loads(self.getURL("https://www.rapideo.pl/clipboard.php?json=3").strip())
- hostings_domains = [domain for row in hostings for domain in row["domains"] if row["sdownload"] == "0"]
-
- self.logDebug(hostings_domains)
-
- return hostings_domains
diff --git a/module/plugins/hooks/RealdebridComHook.py b/module/plugins/hooks/RealdebridComHook.py
deleted file mode 100644
index aa0c9f640..000000000
--- a/module/plugins/hooks/RealdebridComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RealdebridComHook(MultiHook):
- __name__ = "RealdebridComHook"
- __type__ = "hook"
- __version__ = "0.46"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Real-Debrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/RehostToHook.py b/module/plugins/hooks/RehostToHook.py
deleted file mode 100644
index a2415129a..000000000
--- a/module/plugins/hooks/RehostToHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RehostToHook(MultiHook):
- __name__ = "RehostToHook"
- __type__ = "hook"
- __version__ = "0.50"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Rehost.to hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
- html = self.getURL("http://rehost.to/api.php",
- get={'cmd' : "get_supported_och_dl",
- 'long_ses': self.account.getAccountInfo(user)['session']})
- return [x.strip() for x in html.replace("\"", "").split(",")]
diff --git a/module/plugins/hooks/RestartFailed.py b/module/plugins/hooks/RestartFailed.py
deleted file mode 100644
index cab986d9e..000000000
--- a/module/plugins/hooks/RestartFailed.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hook import Hook
-
-
-class RestartFailed(Hook):
- __name__ = "RestartFailed"
- __type__ = "hook"
- __version__ = "1.58"
-
- __config__ = [("interval", "int", "Check interval in minutes", 90)]
-
- __description__ = """Restart all the failed downloads in queue"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
-
- MIN_CHECK_INTERVAL = 15 * 60 #: 15 minutes
-
-
- # def pluginConfigChanged(self, plugin, name, value):
- # if name == "interval":
- # interval = value * 60
- # if self.MIN_CHECK_INTERVAL <= interval != self.interval:
- # self.core.scheduler.removeJob(self.cb)
- # self.interval = interval
- # self.initPeriodical()
- # else:
- # self.logDebug("Invalid interval value, kept current")
-
-
- def periodical(self):
- self.logDebug(_("Restart failed downloads"))
- self.core.api.restartFailed()
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = self.MIN_CHECK_INTERVAL
-
-
- def coreReady(self):
- # self.pluginConfigChanged(self.__name__, "interval", self.getConfig('interval'))
- self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60)
diff --git a/module/plugins/hooks/SimplyPremiumComHook.py b/module/plugins/hooks/SimplyPremiumComHook.py
deleted file mode 100644
index 116e3a76e..000000000
--- a/module/plugins/hooks/SimplyPremiumComHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SimplyPremiumComHook(MultiHook):
- __name__ = "SimplyPremiumComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Simply-Premium.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://www.simply-premium.com/api/hosts.php", get={'format': "json", 'online': 1})
- json_data = json_loads(json_data)
-
- host_list = [element['regex'] for element in json_data['result']]
-
- return host_list
diff --git a/module/plugins/hooks/SimplydebridComHook.py b/module/plugins/hooks/SimplydebridComHook.py
deleted file mode 100644
index 01629df99..000000000
--- a/module/plugins/hooks/SimplydebridComHook.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SimplydebridComHook(MultiHook):
- __name__ = "SimplydebridComHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Simply-Debrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def getHosters(self):
- html = self.getURL("http://simply-debrid.com/api.php", get={'list': 1})
- return [x.strip() for x in html.rstrip(';').replace("\"", "").split(";")]
diff --git a/module/plugins/hooks/SkipRev.py b/module/plugins/hooks/SkipRev.py
deleted file mode 100644
index 7901ca540..000000000
--- a/module/plugins/hooks/SkipRev.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-import urlparse
-
-from types import MethodType
-
-from module.PyFile import PyFile
-from module.plugins.Hook import Hook
-from module.plugins.Plugin import SkipDownload
-
-
-class SkipRev(Hook):
- __name__ = "SkipRev"
- __type__ = "hook"
- __version__ = "0.29"
-
- __config__ = [("mode" , "Auto;Manual", "Choose recovery archives to skip" , "Auto"),
- ("revtokeep", "int" , "Number of recovery archives to keep for package", 0 )]
-
- __description__ = """Skip recovery archives (.rev)"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @staticmethod
- def _setup(self):
- self.pyfile.plugin._setup()
- if self.pyfile.hasStatus("skipped"):
- raise SkipDownload(self.pyfile.statusname or self.pyfile.pluginname)
-
-
- def _name(self, pyfile):
- if hasattr(pyfile.pluginmodule, "getInfo"): #@NOTE: getInfo is deprecated in 0.4.10
- return pyfile.pluginmodule.getInfo([pyfile.url]).next()[0]
- else:
- self.logWarning("Unable to grab file name")
- return urlparse.urlparse(urllib.unquote(pyfile.url)).path.split('/')[-1]
-
-
- def _pyfile(self, link):
- return PyFile(self.core.files,
- link.fid,
- link.url,
- link.name,
- link.size,
- link.status,
- link.error,
- link.plugin,
- link.packageID,
- link.order)
-
-
- def downloadPreparing(self, pyfile):
- name = self._name(pyfile)
-
- if pyfile.statusname is _("unskipped") or not name.endswith(".rev") or not ".part" in name:
- return
-
- revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
-
- if revtokeep:
- status_list = (1, 4, 8, 9, 14) if revtokeep < 0 else (1, 3, 4, 8, 9, 14)
- pyname = re.compile(r'%s\.part\d+\.rev$' % name.rsplit('.', 2)[0].replace('.', '\.'))
-
- queued = [True for link in self.core.api.getPackageData(pyfile.package().id).links \
- if link.status not in status_list and pyname.match(link.name)].count(True)
-
- if not queued or queued < revtokeep: #: keep one rev at least in auto mode
- return
-
- pyfile.setCustomStatus("SkipRev", "skipped")
-
- if not hasattr(pyfile.plugin, "_setup"):
- # Work-around: inject status checker inside the preprocessing routine of the plugin
- pyfile.plugin._setup = pyfile.plugin.setup
- pyfile.plugin.setup = MethodType(self._setup, pyfile.plugin)
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status != 8 or pyfile.name.rsplit('.', 1)[-1].strip() not in ("rar", "rev"):
- return
-
- revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
-
- if not revtokeep:
- return
-
- pyname = re.compile(r'%s\.part\d+\.rev$' % pyfile.name.rsplit('.', 2)[0].replace('.', '\.'))
-
- for link in self.core.api.getPackageData(pyfile.package().id).links:
- if link.status is 4 and pyname.match(link.name):
- pylink = self._pyfile(link)
-
- if revtokeep > -1 or pyfile.name.endswith(".rev"):
- pylink.setStatus("queued")
- else:
- pylink.setCustomStatus(_("unskipped"), "queued")
-
- self.core.files.save()
- pylink.release()
- return
diff --git a/module/plugins/hooks/SmoozedComHook.py b/module/plugins/hooks/SmoozedComHook.py
deleted file mode 100644
index 24b7c8df0..000000000
--- a/module/plugins/hooks/SmoozedComHook.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SmoozedComHook(MultiHook):
- __name__ = "SmoozedComHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Smoozed.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
- return self.account.getAccountInfo(user)["hosters"]
diff --git a/module/plugins/hooks/UnSkipOnFail.py b/module/plugins/hooks/UnSkipOnFail.py
deleted file mode 100644
index 9059d0350..000000000
--- a/module/plugins/hooks/UnSkipOnFail.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.PyFile import PyFile
-from module.plugins.Hook import Hook
-
-
-class UnSkipOnFail(Hook):
- __name__ = "UnSkipOnFail"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Restart skipped duplicates when download fails"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status != 8:
- return
-
- msg = _("Looking for skipped duplicates of: %s (pid:%s)")
- self.logInfo(msg % (pyfile.name, pyfile.package().id))
-
- link = self.findDuplicate(pyfile)
- if link:
- self.logInfo(_("Queue found duplicate: %s (pid:%s)") % (link.name, link.packageID))
-
- #: Change status of "link" to "new_status".
- # "link" has to be a valid FileData object,
- # "new_status" has to be a valid status name
- # (i.e. "queued" for this Plugin)
- # It creates a temporary PyFile object using
- # "link" data, changes its status, and tells
- # the core.files-manager to save its data.
- pylink = self._pyfile(link)
-
- pylink.setCustomStatus(_("unskipped"), "queued")
-
- self.core.files.save()
- pylink.release()
-
- else:
- self.logInfo(_("No duplicates found"))
-
-
- def findDuplicate(self, pyfile):
- """ Search all packages for duplicate links to "pyfile".
- Duplicates are links that would overwrite "pyfile".
- To test on duplicity the package-folder and link-name
- of twolinks are compared (link.name).
- So this method returns a list of all links with equal
- package-folders and filenames as "pyfile", but except
- the data for "pyfile" iotselöf.
- It does MOT check the link's status.
- """
- queue = self.core.api.getQueue() #: get packages (w/o files, as most file data is useless here)
-
- for package in queue:
- #: check if package-folder equals pyfile's package folder
- if package.folder != pyfile.package().folder:
- continue
-
- #: now get packaged data w/ files/links
- pdata = self.core.api.getPackageData(package.pid)
- for link in pdata.links:
- #: check if link is "skipped"
- if link.status != 4:
- continue
-
- #: check if link name collides with pdata's name
- #: AND at last check if it is not pyfile itself
- if link.name == pyfile.name and link.fid != pyfile.id:
- return link
-
-
- def _pyfile(self, link):
- return PyFile(self.core.files,
- link.fid,
- link.url,
- link.name,
- link.size,
- link.status,
- link.error,
- link.plugin,
- link.packageID,
- link.order)
diff --git a/module/plugins/hooks/UnrestrictLiHook.py b/module/plugins/hooks/UnrestrictLiHook.py
deleted file mode 100644
index f1ffd1886..000000000
--- a/module/plugins/hooks/UnrestrictLiHook.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class UnrestrictLiHook(MultiHook):
- __name__ = "UnrestrictLiHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("history" , "bool" , "Delete History" , False)]
-
- __description__ = """Unrestrict.li hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://unrestrict.li/api/jdownloader/hosts.php", get={'format': "json"})
- json_data = json_loads(json_data)
-
- return [element['host'] for element in json_data['result']]
diff --git a/module/plugins/hooks/UpdateManager.py b/module/plugins/hooks/UpdateManager.py
deleted file mode 100644
index 5779e7c80..000000000
--- a/module/plugins/hooks/UpdateManager.py
+++ /dev/null
@@ -1,329 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import re
-import sys
-import time
-
-from operator import itemgetter
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Expose, Hook, threaded
-from module.utils import save_join
-
-
-# Case-sensitive os.path.exists
-def exists(path):
- if os.path.exists(path):
- if os.name == 'nt':
- dir, name = os.path.split(path)
- return name in os.listdir(dir)
- else:
- return True
- else:
- return False
-
-
-class UpdateManager(Hook):
- __name__ = "UpdateManager"
- __type__ = "hook"
- __version__ = "0.51"
-
- __config__ = [("activated" , "bool", "Activated" , True ),
- ("checkinterval", "int" , "Check interval in hours" , 8 ),
- ("autorestart" , "bool", "Auto-restart pyLoad when required" , True ),
- ("checkonstart" , "bool", "Check for updates on startup" , True ),
- ("checkperiod" , "bool", "Check for updates periodically" , True ),
- ("reloadplugins", "bool", "Monitor plugin code changes in debug mode", True ),
- ("nodebugupdate", "bool", "Don't update plugins in debug mode" , False)]
-
- __description__ = """ Check for updates """
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0
-
- SERVER_URL = "http://updatemanager.pyload.org"
- MIN_CHECK_INTERVAL = 3 * 60 * 60 #: 3 hours
-
- event_list = ["allDownloadsProcessed"]
-
-
- def coreReady(self):
- if self.checkonstart:
- self.update()
-
- self.initPeriodical()
-
-
- def setup(self):
- self.interval = 10
- self.info = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()}
- self.mtimes = {} #: store modification time for each plugin
-
- if self.getConfig('checkonstart'):
- self.core.api.pauseServer()
- self.checkonstart = True
- else:
- self.checkonstart = False
-
- self.do_restart = False
-
-
- def allDownloadsProcessed(self):
- if self.do_restart is True:
- self.logWarning(_("Downloads are done, restarting pyLoad to reload the updated plugins"))
- self.core.api.restart()
-
-
- def periodical(self):
- if self.core.debug:
- if self.getConfig('reloadplugins'):
- self.autoreloadPlugins()
-
- if self.getConfig('nodebugupdate'):
- return
-
- if self.getConfig('checkperiod') \
- and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']:
- self.update()
-
-
- @Expose
- def autoreloadPlugins(self):
- """ reload and reindex all modified plugins """
- modules = filter(
- lambda m: m and (m.__name__.startswith("module.plugins.") or
- m.__name__.startswith("userplugins.")) and
- m.__name__.count(".") >= 2, sys.modules.itervalues()
- )
-
- reloads = []
-
- for m in modules:
- root, type, name = m.__name__.rsplit(".", 2)
- id = (type, name)
- if type in self.core.pluginManager.plugins:
- f = m.__file__.replace(".pyc", ".py")
- if not os.path.isfile(f):
- continue
-
- mtime = os.stat(f).st_mtime
-
- if id not in self.mtimes:
- self.mtimes[id] = mtime
- elif self.mtimes[id] < mtime:
- reloads.append(id)
- self.mtimes[id] = mtime
-
- return True if self.core.pluginManager.reloadPlugins(reloads) else False
-
-
- def server_response(self):
- try:
- return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
-
- except Exception:
- self.logWarning(_("Unable to retrieve server to get updates"))
-
-
- @Expose
- @threaded
- def update(self):
- """ check for updates """
-
- self.core.api.pauseServer()
-
- if self._update() is 2 and self.getConfig('autorestart'):
- if not self.core.api.statusDownloads():
- self.core.api.restart()
- else:
- self.do_restart = True
- self.logWarning(_("Downloads are active, will restart once the download is done"))
- else:
- self.core.api.unpauseServer()
-
-
- def _update(self):
- data = self.server_response()
-
- self.info['last_check'] = time.time()
-
- if not data:
- exitcode = 0
-
- elif data[0] == "None":
- self.logInfo(_("No new pyLoad version available"))
- exitcode = self._updatePlugins(data[1:])
-
- elif onlyplugin:
- exitcode = 0
-
- else:
- self.logInfo(_("*** New pyLoad Version %s available ***") % data[0])
- self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
- self.info['pyload'] = True
- self.info['version'] = data[0]
- exitcode = 3
-
- # Exit codes:
- # -1 = No plugin updated, new pyLoad version available
- # 0 = No plugin updated
- # 1 = Plugins updated
- # 2 = Plugins updated, but restart required
- return exitcode
-
-
- def _updatePlugins(self, data):
- """ check for plugin updates """
-
- exitcode = 0
- updated = []
-
- url = data[0]
- schema = data[1].split('|')
-
- VERSION = re.compile(r'__version__.*=.*("|\')([\d.]+)')
-
- if "BLACKLIST" in data:
- blacklist = data[data.index('BLACKLIST') + 1:]
- updatelist = data[2:data.index('BLACKLIST')]
- else:
- blacklist = []
- updatelist = data[2:]
-
- updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist]
- blacklist = [dict(zip(schema, x.split('|'))) for x in blacklist]
-
- if blacklist:
- type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist]
-
- # Protect UpdateManager from self-removing
- try:
- type_plugins.remove(("hook", "UpdateManager"))
- except ValueError:
- pass
-
- for t, n in type_plugins:
- for idx, plugin in enumerate(updatelist):
- if n == plugin['name'] and t == plugin['type']:
- updatelist.pop(idx)
- break
-
- for t, n in self.removePlugins(sorted(type_plugins)):
- self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % {
- 'type': t,
- 'name': n,
- })
-
- for plugin in sorted(updatelist, key=itemgetter("type", "name")):
- filename = plugin['name']
- prefix = plugin['type']
- version = plugin['version']
-
- if filename.endswith(".pyc"):
- name = filename[:filename.find("_")]
- else:
- name = filename.replace(".py", "")
-
- #@TODO: Remove in 0.4.10
- if prefix.endswith("s"):
- type = prefix[:-1]
- else:
- type = prefix
-
- plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
-
- oldver = float(plugins[name]['v']) if name in plugins else None
- newver = float(version)
-
- if not oldver:
- msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)"
- elif newver > oldver:
- msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)"
- else:
- continue
-
- self.logInfo(_(msg) % {'type' : type,
- 'name' : name,
- 'oldver': oldver,
- 'newver': newver})
- try:
- content = getURL(url % plugin)
- m = VERSION.search(content)
-
- if m and m.group(2) == version:
- with open(save_join("userplugins", prefix, filename), "wb") as f:
- f.write(content)
-
- updated.append((prefix, name))
- else:
- raise Exception, _("Version mismatch")
-
- except Exception, e:
- self.logError(_("Error updating plugin: %s") % filename, e)
-
- if updated:
- self.logInfo(_("*** Plugins updated ***"))
-
- if self.core.pluginManager.reloadPlugins(updated):
- exitcode = 1
- else:
- self.logWarning(_("pyLoad restart required to reload the updated plugins"))
- self.info['plugins'] = True
- exitcode = 2
-
- self.manager.dispatchEvent("plugin_updated", updated)
- else:
- self.logInfo(_("No plugin updates available"))
-
- # Exit codes:
- # 0 = No plugin updated
- # 1 = Plugins updated
- # 2 = Plugins updated, but restart required
- return exitcode
-
-
- @Expose
- def removePlugins(self, type_plugins):
- """ delete plugins from disk """
-
- if not type_plugins:
- return
-
- removed = set()
-
- self.logDebug("Requested deletion of plugins: %s" % type_plugins)
-
- for type, name in type_plugins:
- rootplugins = os.path.join(pypath, "module", "plugins")
-
- for dir in ("userplugins", rootplugins):
- py_filename = save_join(dir, type, name + ".py")
- pyc_filename = py_filename + "c"
-
- if type == "hook":
- try:
- self.manager.deactivateHook(name)
-
- except Exception, e:
- self.logDebug(e)
-
- for filename in (py_filename, pyc_filename):
- if not exists(filename):
- continue
-
- try:
- os.remove(filename)
-
- except OSError, e:
- self.logError(_("Error removing: %s") % filename, e)
-
- else:
- id = (type, name)
- removed.add(id)
-
- return list(removed) #: return a list of the plugins successfully removed
diff --git a/module/plugins/hooks/UserAgentSwitcher.py b/module/plugins/hooks/UserAgentSwitcher.py
deleted file mode 100644
index 912c2ef09..000000000
--- a/module/plugins/hooks/UserAgentSwitcher.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import pycurl
-import random
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode
-
-
-class UserAgentSwitcher(Hook):
- __name__ = "UserAgentSwitcher"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("activated", "bool", "Activated" , True ),
- ("uaf" , "file", "Random user-agents file" , "" ),
- ("uar" , "bool", "Random user-agent" , False ),
- ("uas" , "str" , "Custom user-agent string", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")]
-
- __description__ = """Custom user-agent"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def downloadPreparing(self, pyfile):
- uar = self.getConfig('uar')
- uaf = fs_encode(self.getConfig('uaf'))
-
- if uar and os.path.isfile(uaf):
- with open(uaf) as f:
- uas = random.choice([ua for ua in f.read().splitlines()])
- else:
- uas = self.getConfig('uas')
-
- if uas:
- self.logDebug("Use custom user-agent string: " + uas)
- pyfile.plugin.req.http.c.setopt(pycurl.USERAGENT, uas.encode('utf-8'))
diff --git a/module/plugins/hooks/WindowsPhoneNotify.py b/module/plugins/hooks/WindowsPhoneNotify.py
deleted file mode 100644
index e61057f9f..000000000
--- a/module/plugins/hooks/WindowsPhoneNotify.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import httplib
-import time
-
-from module.plugins.Hook import Hook, Expose
-
-
-class WindowsPhoneNotify(Hook):
- __name__ = "WindowsPhoneNotify"
- __type__ = "hook"
- __version__ = "0.09"
-
- __config__ = [("id" , "str" , "Push ID" , "" ),
- ("url" , "str" , "Push url" , "" ),
- ("notifycaptcha" , "bool", "Notify captcha request" , True ),
- ("notifypackage" , "bool", "Notify package finished" , True ),
- ("notifyprocessed", "bool", "Notify packages processed" , True ),
- ("notifyupdate" , "bool", "Notify plugin updates" , True ),
- ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
- ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
- ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
- ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
-
- __description__ = """Send push notifications to Windows Phone"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt" , "phone-support@hotmail.de"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- event_list = ["allDownloadsProcessed", "plugin_updated"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.last_notify = 0
- self.notifications = 0
-
-
- def plugin_updated(self, type_plugins):
- if not self.getConfig('notifyupdate'):
- return
-
- self.notify(_("Plugins updated"), str(type_plugins))
-
-
- def coreExiting(self):
- if not self.getConfig('notifyexit'):
- return
-
- if self.core.do_restart:
- self.notify(_("Restarting pyLoad"))
- else:
- self.notify(_("Exiting pyLoad"))
-
-
- def newCaptchaTask(self, task):
- if not self.getConfig('notifycaptcha'):
- return
-
- self.notify(_("Captcha"), _("New request waiting user input"))
-
-
- def packageFinished(self, pypack):
- if self.getConfig('notifypackage'):
- self.notify(_("Package finished"), pypack.name)
-
-
- def allDownloadsProcessed(self):
- if not self.getConfig('notifyprocessed'):
- return
-
- if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
- self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
- else:
- self.notify(_("All packages finished"))
-
-
- def getXmlData(self, msg):
- return ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
- "<wp:Toast> <wp:Text1>pyLoad</wp:Text1> <wp:Text2>%s</wp:Text2> "
- "</wp:Toast> </wp:Notification>" % msg)
-
-
- @Expose
- def notify(self,
- event,
- msg="",
- key=(self.getConfig('id'), self.getConfig('url'))):
-
- id, url = key
-
- if not id or not url:
- return
-
- if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
- return
-
- elapsed_time = time.time() - self.last_notify
-
- if elapsed_time < self.getConf("sendtimewait"):
- return
-
- if elapsed_time > 60:
- self.notifications = 0
-
- elif self.notifications >= self.getConf("sendpermin"):
- return
-
-
- request = self.getXmlData("%s: %s" % (event, msg) if msg else event)
- webservice = httplib.HTTP(url)
-
- webservice.putrequest("POST", id)
- webservice.putheader("Host", url)
- webservice.putheader("Content-type", "text/xml")
- webservice.putheader("X-NotificationClass", "2")
- webservice.putheader("X-WindowsPhone-Target", "toast")
- webservice.putheader("Content-length", "%d" % len(request))
- webservice.endheaders()
- webservice.send(request)
- webservice.close()
-
- self.last_notify = time.time()
- self.notifications += 1
diff --git a/module/plugins/hooks/XFileSharingPro.py b/module/plugins/hooks/XFileSharingPro.py
deleted file mode 100644
index d80515a22..000000000
--- a/module/plugins/hooks/XFileSharingPro.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-
-
-class XFileSharingPro(Hook):
- __name__ = "XFileSharingPro"
- __type__ = "hook"
- __version__ = "0.37"
-
- __config__ = [("activated" , "bool", "Activated" , True ),
- ("use_hoster_list" , "bool", "Load listed hosters only" , False),
- ("use_crypter_list", "bool", "Load listed crypters only" , False),
- ("use_builtin_list", "bool", "Load built-in plugin list" , True ),
- ("hoster_list" , "str" , "Hoster list (comma separated)" , "" ),
- ("crypter_list" , "str" , "Crypter list (comma separated)", "" )]
-
- __description__ = """Load XFileSharingPro based hosters and crypter which don't need a own plugin to run"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
- interval = 0 #@TODO: Remove in 0.4.10
- regexp = {'hoster' : (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)',
- r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:embed-)?\w+'),
- 'crypter': (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+',
- r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:user|folder)s?/\w+')}
-
- HOSTER_BUILTIN = [#WORKING HOSTERS:
- "backin.net", "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "fileparadox.in",
- "filevice.com", "hostingbulk.com", "junkyvideo.com", "linestorage.com", "ravishare.com", "ryushare.com",
- "salefiles.com", "sendmyway.com", "sharesix.com", "thefile.me", "verzend.be", "xvidstage.com",
- #NOT TESTED:
- "101shared.com", "4upfiles.com", "filemaze.ws", "filenuke.com", "linkzhost.com", "mightyupload.com",
- "rockdizfile.com", "sharebeast.com", "sharerepo.com", "shareswift.com", "uploadbaz.com", "uploadc.com",
- "vidbull.com", "worldbytez.com", "zalaa.com", "zomgupload.com",
- #NOT WORKING:
- "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"]
- CRYPTER_BUILTIN = ["junocloud.me", "rapidfileshare.net"]
-
-
- # def pluginConfigChanged(self, plugin, name, value):
- # self.loadPattern()
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def coreReady(self):
- self.loadPattern()
-
-
- def loadPattern(self):
- use_builtin_list = self.getConfig('use_builtin_list')
-
- for type, plugin in (("hoster", "XFileSharingPro"),
- ("crypter", "XFileSharingProFolder")):
- every_plugin = not self.getConfig("use_%s_list" % type)
-
- if every_plugin:
- self.logInfo(_("Handling any %s I can!") % type)
- pattern = self.regexp[type][0]
- else:
- plugins = self.getConfig('%s_list' % type)
- plugin_set = set(plugins.replace(' ', '').replace('\\', '').replace('|', ',').replace(';', ',').lower().split(','))
-
- if use_builtin_list:
- plugin_set |= set(x.lower() for x in getattr(self, "%s_BUILTIN" % type.upper()))
-
- plugin_set -= set(('', u''))
-
- if not plugin_set:
- self.logInfo(_("No %s to handle") % type)
- self._unload(type, plugin)
- return
-
- match_list = '|'.join(sorted(plugin_set))
-
- len_match_list = len(plugin_set)
- self.logInfo(_("Handling %d %s%s: %s") % (len_match_list,
- type,
- "" if len_match_list == 1 else "s",
- match_list.replace('|', ', ')))
-
- pattern = self.regexp[type][1] % match_list.replace('.', '\.')
-
- dict = self.core.pluginManager.plugins[type][plugin]
- dict['pattern'] = pattern
- dict['re'] = re.compile(pattern)
-
- self.logDebug("Loaded %s pattern: %s" % (type, pattern))
-
-
- def _unload(self, type, plugin):
- dict = self.core.pluginManager.plugins[type][plugin]
- dict['pattern'] = r'^unmatchable$'
- dict['re'] = re.compile(dict['pattern'])
-
-
- def unload(self):
- # self.unloadHoster("BasePlugin")
- for type, plugin in (("hoster", "XFileSharingPro"),
- ("crypter", "XFileSharingProFolder")):
- self._unload(type, plugin)
-
-
- def unloadHoster(self, hoster):
- hdict = self.core.pluginManager.hosterPlugins[hoster]
- if "new_name" in hdict and hdict['new_name'] == "XFileSharingPro":
- if "module" in hdict:
- hdict.pop('module', None)
-
- if "new_module" in hdict:
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
- return True
- else:
- return False
-
-
- # def downloadFailed(self, pyfile):
- # if pyfile.pluginname == "BasePlugin" \
- # and pyfile.hasStatus("failed") \
- # and not self.getConfig('use_hoster_list') \
- # and self.unloadHoster("BasePlugin"):
- # self.logDebug("Unloaded XFileSharingPro from BasePlugin")
- # pyfile.setStatus("queued")
diff --git a/module/plugins/hooks/XMPPInterface.py b/module/plugins/hooks/XMPPInterface.py
deleted file mode 100644
index b61428392..000000000
--- a/module/plugins/hooks/XMPPInterface.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from pyxmpp import streamtls
-from pyxmpp.all import JID, Message
-from pyxmpp.interface import implements
-from pyxmpp.interfaces import *
-from pyxmpp.jabber.client import JabberClient
-
-from module.plugins.hooks.IRCInterface import IRCInterface
-
-
-class XMPPInterface(IRCInterface, JabberClient):
- __name__ = "XMPPInterface"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("jid", "str", "Jabber ID", "user@exmaple-jabber-server.org"),
- ("pw", "str", "Password", ""),
- ("tls", "bool", "Use TLS", False),
- ("owners", "str", "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"),
- ("info_file", "bool", "Inform about every file finished", False),
- ("info_pack", "bool", "Inform about every package finished", True),
- ("captcha", "bool", "Send captcha requests", True)]
-
- __description__ = """Connect to jabber and let owner perform different tasks"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- implements(IMessageHandlersProvider)
-
-
- def __init__(self, core, manager):
- IRCInterface.__init__(self, core, manager)
-
- self.jid = JID(self.getConfig('jid'))
- password = self.getConfig('pw')
-
- # if bare JID is provided add a resource -- it is required
- if not self.jid.resource:
- self.jid = JID(self.jid.node, self.jid.domain, "pyLoad")
-
- if self.getConfig('tls'):
- tls_settings = streamtls.TLSSettings(require=True, verify_peer=False)
- auth = ("sasl:PLAIN", "sasl:DIGEST-MD5")
- else:
- tls_settings = None
- auth = ("sasl:DIGEST-MD5", "digest")
-
- # setup client with provided connection information
- # and identity data
- JabberClient.__init__(self, self.jid, password,
- disco_name="pyLoad XMPP Client", disco_type="bot",
- tls_settings=tls_settings, auth_methods=auth)
-
- self.interface_providers = [
- VersionHandler(self),
- self,
- ]
-
-
- def coreReady(self):
- self.new_package = {}
-
- self.start()
-
-
- def packageFinished(self, pypack):
- try:
- if self.getConfig('info_pack'):
- self.announce(_("Package finished: %s") % pypack.name)
- except Exception:
- pass
-
-
- def downloadFinished(self, pyfile):
- try:
- if self.getConfig('info_file'):
- self.announce(
- _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname})
- except Exception:
- pass
-
-
- def run(self):
- # connect to IRC etc.
- self.connect()
- try:
- self.loop()
- except Exception, ex:
- self.logError(ex)
-
-
- def stream_state_changed(self, state, arg):
- """This one is called when the state of stream connecting the component
- to a server changes. This will usually be used to let the user
- know what is going on."""
- self.logDebug("*** State changed: %s %r ***" % (state, arg))
-
-
- def disconnected(self):
- self.logDebug("Client was disconnected")
-
-
- def stream_closed(self, stream):
- self.logDebug("Stream was closed", stream)
-
-
- def stream_error(self, err):
- self.logDebug("Stream Error", err)
-
-
- def get_message_handlers(self):
- """Return list of (message_type, message_handler) tuples.
-
- The handlers returned will be called when matching message is received
- in a client session."""
- return [("normal", self.message)]
-
-
- def message(self, stanza):
- """Message handler for the component."""
- subject = stanza.get_subject()
- body = stanza.get_body()
- t = stanza.get_type()
- self.logDebug("Message from %s received." % unicode(stanza.get_from()))
- self.logDebug("Body: %s Subject: %s Type: %s" % (body, subject, t))
-
- if t == "headline":
- # 'headline' messages should never be replied to
- return True
- if subject:
- subject = u"Re: " + subject
-
- to_jid = stanza.get_from()
- from_jid = stanza.get_to()
-
- #j = JID()
- to_name = to_jid.as_utf8()
- from_name = from_jid.as_utf8()
-
- names = self.getConfig('owners').split(";")
-
- if to_name in names or to_jid.node + "@" + to_jid.domain in names:
- messages = []
-
- trigger = "pass"
- args = None
-
- try:
- temp = body.split()
- trigger = temp[0]
- if len(temp) > 1:
- args = temp[1:]
- except Exception:
- pass
-
- handler = getattr(self, "event_%s" % trigger, self.event_pass)
- try:
- res = handler(args)
- for line in res:
- m = Message(
- to_jid=to_jid,
- from_jid=from_jid,
- stanza_type=stanza.get_type(),
- subject=subject,
- body=line)
-
- messages.append(m)
- except Exception, e:
- self.logError(e)
-
- return messages
-
- else:
- return True
-
-
- def response(self, msg, origin=""):
- return self.announce(msg)
-
-
- def announce(self, message):
- """ send message to all owners"""
- for user in self.getConfig('owners').split(";"):
- self.logDebug("Send message to", user)
-
- to_jid = JID(user)
-
- m = Message(from_jid=self.jid,
- to_jid=to_jid,
- stanza_type="chat",
- body=message)
-
- stream = self.get_stream()
- if not stream:
- self.connect()
- stream = self.get_stream()
-
- stream.send(m)
-
-
- def beforeReconnecting(self, ip):
- self.disconnect()
-
-
- def afterReconnecting(self, ip):
- self.connect()
-
-
-class VersionHandler(object):
- """Provides handler for a version query.
-
- This class will answer version query and announce 'jabber:iq:version' namespace
- in the client's disco#info results."""
-
- implements(IIqHandlersProvider, IFeaturesProvider)
-
-
- def __init__(self, client):
- """Just remember who created this."""
- self.client = client
-
-
- def get_features(self):
- """Return namespace which should the client include in its reply to a
- disco#info query."""
- return ["jabber:iq:version"]
-
-
- def get_iq_get_handlers(self):
- """Return list of tuples (element_name, namespace, handler) describing
- handlers of <iq type='get'/> stanzas"""
- return [("query", "jabber:iq:version", self.get_version)]
-
-
- def get_iq_set_handlers(self):
- """Return empty list, as this class provides no <iq type='set'/> stanza handler."""
- return []
-
-
- def get_version(self, iq):
- """Handler for jabber:iq:version queries.
-
- jabber:iq:version queries are not supported directly by PyXMPP, so the
- XML node is accessed directly through the libxml2 API. This should be
- used very carefully!"""
- iq = iq.make_result_response()
- q = iq.new_query("jabber:iq:version")
- q.newTextChild(q.ns(), "name", "Echo component")
- q.newTextChild(q.ns(), "version", "1.0")
- return iq
diff --git a/module/plugins/hooks/ZeveraComHook.py b/module/plugins/hooks/ZeveraComHook.py
deleted file mode 100644
index 21c1741d2..000000000
--- a/module/plugins/hooks/ZeveraComHook.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class ZeveraComHook(MultiHook):
- __name__ = "ZeveraComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Zevera.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- def getHosters(self):
- html = self.account.api_response(pyreq.getHTTPRequest(timeout=120), cmd="gethosters")
- return [x.strip() for x in html.split(",")]
diff --git a/module/plugins/hooks/__init__.py b/module/plugins/hooks/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/hooks/__init__.py
+++ /dev/null
diff --git a/module/plugins/hoster/AlldebridCom.py b/module/plugins/hoster/AlldebridCom.py
deleted file mode 100644
index 2ed09f58c..000000000
--- a/module/plugins/hoster/AlldebridCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class AlldebridCom(MultiHoster):
- __name__ = "AlldebridCom"
- __type__ = "hoster"
- __version__ = "0.46"
-
- __pattern__ = r'https?://(?:www\.|s\d+\.)?alldebrid\.com/dl/[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Alldebrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def setup(self):
- self.chunkLimit = 16
-
-
- def handlePremium(self, pyfile):
- password = self.getPassword()
-
- data = json_loads(self.load("http://www.alldebrid.com/service.php",
- get={'link': pyfile.url, 'json': "true", 'pw': password}))
-
- self.logDebug("Json data", data)
-
- if data['error']:
- if data['error'] == "This link isn't available on the hoster website.":
- self.offline()
- else:
- self.logWarning(data['error'])
- self.tempOffline()
- else:
- if pyfile.name and not pyfile.name.endswith('.tmp'):
- pyfile.name = data['filename']
- pyfile.size = parseFileSize(data['filesize'])
- self.link = data['link']
-
- if self.getConfig('ssl'):
- self.link = self.link.replace("http://", "https://")
- else:
- self.link = self.link.replace("https://", "http://")
-
-
-getInfo = create_getInfo(AlldebridCom)
diff --git a/module/plugins/hoster/AndroidfilehostCom.py b/module/plugins/hoster/AndroidfilehostCom.py
deleted file mode 100644
index 08005de0f..000000000
--- a/module/plugins/hoster/AndroidfilehostCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://www.androidfilehost.com/?fid=95916177934518197
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class AndroidfilehostCom(SimpleHoster):
- __name__ = "AndroidfilehostCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?androidfilehost\.com/\?fid=\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Androidfilehost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<br />(?P<N>.*?)</h1>'
- SIZE_PATTERN = r'<h4>size</h4>\s*<p>(?P<S>[\d.,]+)(?P<U>[\w^_]+)</p>'
- HASHSUM_PATTERN = r'<h4>(?P<T>.*?)</h4>\s*<p><code>(?P<H>.*?)</code></p>'
-
- OFFLINE_PATTERN = r'404 not found'
-
- WAIT_PATTERN = r'users must wait <strong>(\d+) secs'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- wait = re.search(self.WAIT_PATTERN, self.html)
- self.logDebug("Waiting time: %s seconds" % wait.group(1))
-
- fid = re.search(r'id="fid" value="(\d+)" />', self.html).group(1)
- self.logDebug("fid: %s" % fid)
-
- html = self.load("https://www.androidfilehost.com/libs/otf/mirrors.otf.php",
- post={'submit': 'submit',
- 'action': 'getdownloadmirrors',
- 'fid' : fid},
- decode=True)
-
- self.link = re.findall('"url":"(.*?)"', html)[0].replace("\\", "")
- mirror_host = self.link.split("/")[2]
-
- self.logDebug("Mirror Host: %s" % mirror_host)
-
- html = self.load("https://www.androidfilehost.com/libs/otf/stats.otf.php",
- get={'fid' : fid,
- 'w' : 'download',
- 'mirror': mirror_host},
- decode=True)
-
-
-getInfo = create_getInfo(AndroidfilehostCom)
diff --git a/module/plugins/hoster/BasePlugin.py b/module/plugins/hoster/BasePlugin.py
deleted file mode 100644
index 2228516aa..000000000
--- a/module/plugins/hoster/BasePlugin.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-import urlparse
-
-from module.network.HTTPRequest import BadHeader
-from module.plugins.internal.SimpleHoster import create_getInfo, getFileURL
-from module.plugins.Hoster import Hoster
-
-
-class BasePlugin(Hoster):
- __name__ = "BasePlugin"
- __type__ = "hoster"
- __version__ = "0.43"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """Base Plugin when any other didnt fit"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- @classmethod
- def getInfo(cls, url="", html=""): #@TODO: Move to hoster class in 0.4.10
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- return {'name' : (url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 'size' : 0,
- 'status': 3 if url else 8,
- 'url' : url}
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def process(self, pyfile):
- """main function"""
-
- pyfile.name = self.getInfo(pyfile.url)['name']
-
- if not pyfile.url.startswith("http"):
- self.fail(_("No plugin matched"))
-
- for _i in xrange(5):
- try:
- link = getFileURL(self, urllib.unquote(pyfile.url))
-
- if link:
- self.download(link, ref=False, disposition=True)
- else:
- self.fail(_("File not found"))
-
- except BadHeader, e:
- if e.code is 404:
- self.offline()
-
- elif e.code in (401, 403):
- self.logDebug("Auth required", "Received HTTP status code: %d" % e.code)
-
- account = self.core.accountManager.getAccountPlugin('Http')
- servers = [x['login'] for x in account.getAllAccounts()]
- server = urlparse.urlparse(pyfile.url).netloc
-
- if server in servers:
- self.logDebug("Logging on to %s" % server)
- self.req.addAuth(account.getAccountData(server)['password'])
- else:
- pwd = self.getPassword()
- if ':' in pwd:
- self.req.addAuth(pwd)
- else:
- self.fail(_("Authorization required"))
- else:
- self.fail(e)
- else:
- break
- else:
- self.fail(_("No file downloaded")) #@TODO: Move to hoster class in 0.4.10
-
- errmsg = self.checkDownload({'Empty file' : re.compile(r'\A\s*\Z'),
- 'Html error' : re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'),
- 'Html file' : re.compile(r'\A\s*<!DOCTYPE html'),
- 'Request error': re.compile(r'([Aa]n error occured while processing your request)')})
- if not errmsg:
- return
-
- try:
- errmsg += " | " + self.lastCheck.group(1).strip()
- except Exception:
- pass
-
- self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
- self.retry(3, 60, errmsg)
-
-
-getInfo = create_getInfo(BasePlugin)
diff --git a/module/plugins/hoster/BasketbuildCom.py b/module/plugins/hoster/BasketbuildCom.py
deleted file mode 100644
index 89e4d39f9..000000000
--- a/module/plugins/hoster/BasketbuildCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://s.basketbuild.com/filedl/devs?dev=pacman&dl=pacman/falcon/RC-3/pac_falcon-RC-3-20141103.zip
-# https://s.basketbuild.com/filedl/gapps?dl=gapps-gb-20110828-signed.zip
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BasketbuildCom(SimpleHoster):
- __name__ = "BasketbuildCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(?:\w\.)?basketbuild\.com/filedl/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """basketbuild.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'File Name:</strong> (?P<N>.+?)<br/>'
- SIZE_PATTERN = r'File Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'404 - Page Not Found'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- try:
- link1 = re.search(r'href="(.+dlgate/.+)"', self.html).group(1)
- self.html = self.load(link1)
-
- except AttributeError:
- self.error(_("Hop #1 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link1)
-
- try:
- wait = re.search(r'var sec = (\d+)', self.html).group(1)
- self.logDebug("Wait %s seconds" % wait)
- self.wait(wait)
-
- except AttributeError:
- self.logDebug("No wait time found")
-
- try:
- self.link = re.search(r'id="dlLink">\s*<a href="(.+?)"', self.html).group(1)
-
- except AttributeError:
- self.error(_("DL-Link not found"))
-
-
-getInfo = create_getInfo(BasketbuildCom)
diff --git a/module/plugins/hoster/BayfilesCom.py b/module/plugins/hoster/BayfilesCom.py
deleted file mode 100644
index 92058d634..000000000
--- a/module/plugins/hoster/BayfilesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class BayfilesCom(DeadHoster):
- __name__ = "BayfilesCom"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)'
- __config__ = []
-
- __description__ = """Bayfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(BayfilesCom)
diff --git a/module/plugins/hoster/BezvadataCz.py b/module/plugins/hoster/BezvadataCz.py
deleted file mode 100644
index fbb17635c..000000000
--- a/module/plugins/hoster/BezvadataCz.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BezvadataCz(SimpleHoster):
- __name__ = "BezvadataCz"
- __type__ = "hoster"
- __version__ = "0.26"
-
- __pattern__ = r'http://(?:www\.)?bezvadata\.cz/stahnout/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """BezvaData.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
- SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
- OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- #download button
- m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
- if m is None:
- self.error(_("Page 1 URL not found"))
- url = "http://bezvadata.cz%s" % m.group(1)
-
- #captcha form
- self.html = self.load(url)
- self.checkErrors()
- for _i in xrange(5):
- action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
- if not inputs:
- self.error(_("FreeForm"))
-
- m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
- if m is None:
- self.error(_("Wrong captcha image"))
-
- #captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url
- self.load, proper_load = self.loadcaptcha, self.load
- try:
- inputs['captcha'] = self.decryptCaptcha(m.group(1), imgtype='png')
- finally:
- self.load = proper_load
-
- if '<img src="data:image/png;base64' in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("No valid captcha code entered"))
-
- #download url
- self.html = self.load("http://bezvadata.cz%s" % action, post=inputs)
- self.checkErrors()
- m = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html)
- if m is None:
- self.error(_("Page 2 URL not found"))
- url = "http://bezvadata.cz%s" % m.group(1)
- self.logDebug("DL URL %s" % url)
-
- #countdown
- m = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html)
- wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 120
- self.wait(wait_time, False)
-
- self.link = url
-
-
- def checkErrors(self):
- if 'images/button-download-disable.png' in self.html:
- self.longWait(5 * 60, 24) #: parallel dl limit
- elif '<div class="infobox' in self.html:
- self.tempOffline()
-
- self.info.pop('error', None)
-
-
- def loadcaptcha(self, data, *args, **kwargs):
- return data.decode('base64')
-
-
-getInfo = create_getInfo(BezvadataCz)
diff --git a/module/plugins/hoster/BillionuploadsCom.py b/module/plugins/hoster/BillionuploadsCom.py
deleted file mode 100644
index 7d7e2624a..000000000
--- a/module/plugins/hoster/BillionuploadsCom.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class BillionuploadsCom(XFSHoster):
- __name__ = "BillionuploadsCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?billionuploads\.com/\w{12}'
-
- __description__ = """Billionuploads.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<td class="dofir" title="(?P<N>.+?)"'
- SIZE_PATTERN = r'<td class="dofir">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
-
-getInfo = create_getInfo(BillionuploadsCom)
diff --git a/module/plugins/hoster/BitshareCom.py b/module/plugins/hoster/BitshareCom.py
deleted file mode 100644
index 79aaedcd9..000000000
--- a/module/plugins/hoster/BitshareCom.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BitshareCom(SimpleHoster):
- __name__ = "BitshareCom"
- __type__ = "hoster"
- __version__ = "0.53"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/(files/)?(?(1)|\?f=)(?P<ID>\w+)(?(1)/(?P<NAME>.+?)\.html)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Bitshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Paul King", None),
- ("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- COOKIES = [("bitshare.com", "language_selection", "EN")]
-
- INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>'
- OFFLINE_PATTERN = r'[Ff]ile (not available|was deleted|was not found)'
-
- AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
- TRAFFIC_USED_UP = r'Your Traffic is used up for today'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- def process(self, pyfile):
- if self.premium:
- self.account.relogin(self.user)
-
- # File id
- m = re.match(self.__pattern__, pyfile.url)
- self.file_id = max(m.group('ID1'), m.group('ID2'))
- self.logDebug("File id is [%s]" % self.file_id)
-
- # Load main page
- self.html = self.load(pyfile.url, ref=False, decode=True)
-
- # Check offline
- if re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
-
- # Check Traffic used up
- if re.search(self.TRAFFIC_USED_UP, self.html):
- self.logInfo(_("Your Traffic is used up for today"))
- self.wait(30 * 60, True)
- self.retry()
-
- # File name
- m = re.match(self.__pattern__, pyfile.url)
- name1 = m.group('NAME') if m else None
-
- m = re.search(self.INFO_PATTERN, self.html)
- name2 = m.group('N') if m else None
-
- pyfile.name = max(name1, name2)
-
- # Ajax file id
- self.ajaxid = re.search(self.AJAXID_PATTERN, self.html).group(1)
- self.logDebug("File ajax id is [%s]" % self.ajaxid)
-
- # This may either download our file or forward us to an error page
- self.link = self.getDownloadUrl()
-
- if self.checkDownload({"error": ">Error occured<"}):
- self.retry(5, 5 * 60, "Bitshare host : Error occured")
-
-
- def getDownloadUrl(self):
- # Return location if direct download is active
- if self.premium:
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header:
- return header['location']
-
- # Get download info
- self.logDebug("Getting download info")
- res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "generateID", "ajaxid": self.ajaxid})
-
- self.handleErrors(res, ':')
-
- parts = res.split(":")
- filetype = parts[0]
- wait = int(parts[1])
- captcha = int(parts[2])
-
- self.logDebug("Download info [type: '%s', waiting: %d, captcha: %d]" % (filetype, wait, captcha))
-
- # Waiting
- if wait > 0:
- self.logDebug("Waiting %d seconds." % wait)
- if wait < 120:
- self.wait(wait, False)
- else:
- self.wait(wait - 55, True)
- self.retry()
-
- # Resolve captcha
- if captcha == 1:
- self.logDebug("File is captcha protected")
- recaptcha = ReCaptcha(self)
-
- # Try up to 3 times
- for i in xrange(3):
- response, challenge = recaptcha.challenge()
- res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request" : "validateCaptcha",
- "ajaxid" : self.ajaxid,
- "recaptcha_challenge_field": challenge,
- "recaptcha_response_field" : response})
- if self.handleCaptchaErrors(res):
- break
-
- # Get download URL
- self.logDebug("Getting download url")
- res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
-
- self.handleErrors(res, '#')
-
- url = res.split("#")[-1]
-
- return url
-
-
- def handleErrors(self, res, separator):
- self.logDebug("Checking response [%s]" % res)
- if "ERROR:Session timed out" in res:
- self.retry()
- elif "ERROR" in res:
- msg = res.split(separator)[-1]
- self.fail(msg)
-
-
- def handleCaptchaErrors(self, res):
- self.logDebug("Result of captcha resolving [%s]" % res)
- if "SUCCESS" in res:
- self.correctCaptcha()
- return True
- elif "ERROR:SESSION ERROR" in res:
- self.retry()
-
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(BitshareCom)
diff --git a/module/plugins/hoster/BoltsharingCom.py b/module/plugins/hoster/BoltsharingCom.py
deleted file mode 100644
index 4309eefac..000000000
--- a/module/plugins/hoster/BoltsharingCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class BoltsharingCom(DeadHoster):
- __name__ = "BoltsharingCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?boltsharing\.com/\w{12}'
- __config__ = []
-
- __description__ = """Boltsharing.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(BoltsharingCom)
diff --git a/module/plugins/hoster/CatShareNet.py b/module/plugins/hoster/CatShareNet.py
deleted file mode 100644
index 9e3f28cfa..000000000
--- a/module/plugins/hoster/CatShareNet.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class CatShareNet(SimpleHoster):
- __name__ = "CatShareNet"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?catshare\.net/\w{16}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """CatShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com"),
- ("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- TEXT_ENCODING = True
-
- INFO_PATTERN = r'<title>(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<'
- OFFLINE_PATTERN = r'<div class="alert alert-error"'
-
- IP_BLOCKED_PATTERN = ur'>Nasz serwis wykrył ÅŒe Twój adres IP nie pochodzi z Polski.<'
- WAIT_PATTERN = r'var\scount\s=\s(\d+);'
-
- LINK_FREE_PATTERN = r'<form action="(.+?)" method="GET">'
- LINK_PREMIUM_PATTERN = r'<form action="(.+?)" method="GET">'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
-
- def checkErrors(self):
- m = re.search(self.IP_BLOCKED_PATTERN, self.html)
- if m:
- self.fail(_("Only connections from Polish IP address are allowed"))
-
- return super(CatShareNet, self).checkErrors()
-
-
- def handleFree(self, pyfile):
- recaptcha = ReCaptcha(self)
-
- response, challenge = recaptcha.challenge()
- self.html = self.load(pyfile.url,
- post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(CatShareNet)
diff --git a/module/plugins/hoster/CloudzerNet.py b/module/plugins/hoster/CloudzerNet.py
deleted file mode 100644
index d2d5e460a..000000000
--- a/module/plugins/hoster/CloudzerNet.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class CloudzerNet(DeadHoster):
- __name__ = "CloudzerNet"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+'
- __config__ = []
-
- __description__ = """Cloudzer.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("gs", "I-_-I-_-I@web.de"),
- ("z00nx", "z00nx0@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(CloudzerNet)
diff --git a/module/plugins/hoster/CloudzillaTo.py b/module/plugins/hoster/CloudzillaTo.py
deleted file mode 100644
index 362a8d953..000000000
--- a/module/plugins/hoster/CloudzillaTo.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class CloudzillaTo(SimpleHoster):
- __name__ = "CloudzillaTo"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?cloudzilla\.to/share/file/(?P<ID>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Cloudzilla.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'title="(?P<N>.+?)">\1</span> <span class="size">\((?P<S>[\d.]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>File not found...<'
-
- PASSWORD_PATTERN = r'<div id="pwd_protected">'
-
-
- def checkErrors(self):
- m = re.search(self.PASSWORD_PATTERN, self.html)
- if m:
- self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
-
- if re.search(self.PASSWORD_PATTERN, self.html):
- self.retry(reason="Wrong password")
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://www.cloudzilla.to/generateticket/",
- post={'file_id': self.info['pattern']['ID'], 'key': self.getPassword()})
-
- ticket = dict(re.findall(r'<(.+?)>([^<>]+?)</', self.html))
-
- self.logDebug(ticket)
-
- if 'error' in ticket:
- if "File is password protected" in ticket['error']:
- self.retry(reason="Wrong password")
- else:
- self.fail(ticket['error'])
-
- if 'wait' in ticket:
- self.wait(ticket['wait'], int(ticket['wait']) > 5)
-
- self.link = "http://%(server)s/download/%(file_id)s/%(ticket_id)s" % {'server' : ticket['server'],
- 'file_id' : self.info['pattern']['ID'],
- 'ticket_id': ticket['ticket_id']}
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
-getInfo = create_getInfo(CloudzillaTo)
diff --git a/module/plugins/hoster/CramitIn.py b/module/plugins/hoster/CramitIn.py
deleted file mode 100644
index 44dac958d..000000000
--- a/module/plugins/hoster/CramitIn.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class CramitIn(XFSHoster):
- __name__ = "CramitIn"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?cramit\.in/\w{12}'
-
- __description__ = """Cramit.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
-
- LINK_PATTERN = r'href="(http://cramit\.in/file_download/.*?)"'
-
-
-getInfo = create_getInfo(CramitIn)
diff --git a/module/plugins/hoster/CrockoCom.py b/module/plugins/hoster/CrockoCom.py
deleted file mode 100644
index 1d7468520..000000000
--- a/module/plugins/hoster/CrockoCom.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class CrockoCom(SimpleHoster):
- __name__ = "CrockoCom"
- __type__ = "hoster"
- __version__ = "0.19"
-
- __pattern__ = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Crocko hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
- SIZE_PATTERN = r'<span class="tip1"><span class="inner">(?P<S>[^<]+)</span></span>'
- OFFLINE_PATTERN = r'<h1>Sorry,<br />the page you\'re looking for <br />isn\'t here.</h1>|File not found'
-
- CAPTCHA_PATTERN = r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';"
-
- FORM_PATTERN = r'<form method="post" action="(.+?)">(.*?)</form>'
- FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?.*?>'
-
- NAME_REPLACEMENTS = [(r'<.*?>', '')]
-
-
- def handleFree(self, pyfile):
- if "You need Premium membership to download this file." in self.html:
- self.fail(_("You need Premium membership to download this file"))
-
- for _i in xrange(5):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- url, wait_time = 'http://crocko.com' + m.group(1), int(m.group(2))
- self.wait(wait_time)
- self.html = self.load(url)
- else:
- break
-
- m = re.search(self.FORM_PATTERN, self.html, re.S)
- if m is None:
- self.error(_("FORM_PATTERN not found"))
-
- action, form = m.groups()
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
- self.download(action, post=inputs)
-
- if self.checkDownload({"captcha": recaptcha.KEY_AJAX_PATTERN}):
- self.invalidCaptcha()
- else:
- break
- else:
- self.fail(_("No valid captcha solution received"))
-
-
-getInfo = create_getInfo(CrockoCom)
diff --git a/module/plugins/hoster/CyberlockerCh.py b/module/plugins/hoster/CyberlockerCh.py
deleted file mode 100644
index d85a27282..000000000
--- a/module/plugins/hoster/CyberlockerCh.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class CyberlockerCh(DeadHoster):
- __name__ = "CyberlockerCh"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?cyberlocker\.ch/\w+'
- __config__ = []
-
- __description__ = """Cyberlocker.ch hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(CyberlockerCh)
diff --git a/module/plugins/hoster/CzshareCom.py b/module/plugins/hoster/CzshareCom.py
deleted file mode 100644
index 8f72f2148..000000000
--- a/module/plugins/hoster/CzshareCom.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://czshare.com/5278880/random.bin
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class CzshareCom(SimpleHoster):
- __name__ = "CzshareCom"
- __type__ = "hoster"
- __version__ = "0.99"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """CZshare.com hoster plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=.*?>(?P<N>[^<]+)</a>'
- SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[\d .,]+)(?P<U>[\w^_]+)\s*</div>'
- OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
-
- SIZE_REPLACEMENTS = [(' ', '')]
- URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
-
- CHECK_TRAFFIC = True
-
- FREE_URL_PATTERN = r'<a href="(.+?)" class="page-download">[^>]*alt="(.+?)" /></a>'
- FREE_FORM_PATTERN = r'<form action="download\.php" method="post">\s*<img src="captcha\.php" id="captcha" />(.*?)</form>'
- PREMIUM_FORM_PATTERN = r'<form action="/profi_down\.php" method="post">(.*?)</form>'
- FORM_INPUT_PATTERN = r'<input[^>]* name="(.+?)" value="(.+?)"[^>]*/>'
- MULTIDL_PATTERN = r'<p><font color=\'red\'>Z[^<]*PROFI.</font></p>'
- USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([\d .,]+)(\w+)</strong>\s*</div><!-- .credit -->'
-
-
- def checkTrafficLeft(self):
- # check if user logged in
- m = re.search(self.USER_CREDIT_PATTERN, self.html)
- if m is None:
- self.account.relogin(self.user)
- self.html = self.load(self.pyfile.url, decode=True)
- m = re.search(self.USER_CREDIT_PATTERN, self.html)
- if m is None:
- return False
-
- # check user credit
- try:
- credit = parseFileSize(m.group(1).replace(' ', ''), m.group(2))
- self.logInfo(_("Premium download for %i KiB of Credit") % (self.pyfile.size / 1024))
- self.logInfo(_("User %s has %i KiB left") % (self.user, credit / 1024))
- if credit < self.pyfile.size:
- self.logInfo(_("Not enough credit to download file: %s") % self.pyfile.name)
- return False
- except Exception, e:
- # let's continue and see what happens...
- self.logError(e)
-
- return True
-
-
- def handlePremium(self, pyfile):
- # parse download link
- try:
- form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.S).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- except Exception, e:
- self.logError(e)
- self.resetAccount()
-
- # download the file, destination is determined by pyLoad
- self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
-
-
- def handleFree(self, pyfile):
- # get free url
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m is None:
- self.error(_("FREE_URL_PATTERN not found"))
-
- parsed_url = "http://sdilej.cz" + m.group(1)
-
- self.logDebug("PARSED_URL:" + parsed_url)
-
- # get download ticket and parse html
- self.html = self.load(parsed_url, decode=True)
- if re.search(self.MULTIDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
-
- try:
- form = re.search(self.FREE_FORM_PATTERN, self.html, re.S).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- pyfile.size = int(inputs['size'])
-
- except Exception, e:
- self.logError(e)
- self.error(_("Form"))
-
- # get and decrypt captcha
- captcha_url = 'http://sdilej.cz/captcha.php'
- for _i in xrange(5):
- inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
- self.html = self.load(parsed_url, post=inputs, decode=True)
-
- if u"<li>ZadanÜ ověřovací kód nesouhlasí!</li>" in self.html:
- self.invalidCaptcha()
-
- elif re.search(self.MULTIDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
-
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("No valid captcha code entered"))
-
- m = re.search("countdown_number = (\d+);", self.html)
- self.setWait(int(m.group(1)) if m else 50)
-
- # download the file, destination is determined by pyLoad
- self.logDebug("WAIT URL", self.req.lastEffectiveURL)
-
- m = re.search("free_wait.php\?server=(.*?)&(.*)", self.req.lastEffectiveURL)
- if m is None:
- self.error(_("Download URL not found"))
-
- self.link = "http://%s/download.php?%s" % (m.group(1), m.group(2))
-
- self.wait()
-
-
- def checkFile(self, rules={}):
- # check download
- check = self.checkDownload({
- "temp offline" : re.compile(r"^Soubor je do.*asn.* nedostupn.*$"),
- "credit" : re.compile(r"^Nem.*te dostate.*n.* kredit.$"),
- "multi-dl" : re.compile(self.MULTIDL_PATTERN),
- "captcha" : "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
- })
-
- if check == "temp offline":
- self.fail(_("File not available - try later"))
-
- elif check == "credit":
- self.resetAccount()
-
- elif check == "multi-dl":
- self.longWait(5 * 60, 12)
-
- elif check == "captcha":
- self.invalidCaptcha()
- self.retry()
-
- return super(CzshareCom, self).checkFile(rules)
-
-
-getInfo = create_getInfo(CzshareCom)
diff --git a/module/plugins/hoster/DailymotionCom.py b/module/plugins/hoster/DailymotionCom.py
deleted file mode 100644
index c6939e5e5..000000000
--- a/module/plugins/hoster/DailymotionCom.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.PyFile import statusMap
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-
-
-def getInfo(urls):
- result = []
- regex = re.compile(DailymotionCom.__pattern__)
- apiurl = "https://api.dailymotion.com/video/%s"
- request = {"fields": "access_error,status,title"}
-
- for url in urls:
- id = regex.match(url).group('ID')
- html = getURL(apiurl % id, get=request)
- info = json_loads(html)
-
- name = info['title'] + ".mp4" if "title" in info else url
-
- if "error" in info or info['access_error']:
- status = "offline"
- else:
- status = info['status']
- if status in ("ready", "published"):
- status = "online"
- elif status in ("waiting", "processing"):
- status = "temp. offline"
- else:
- status = "offline"
-
- result.append((name, 0, statusMap[status], url))
-
- return result
-
-
-class DailymotionCom(Hoster):
- __name__ = "DailymotionCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'https?://(?:www\.)?dailymotion\.com/.*video/(?P<ID>[\w^_]+)'
- __config__ = [("quality", "Lowest;LD 144p;LD 240p;SD 384p;HQ 480p;HD 720p;HD 1080p;Highest", "Quality", "Highest")]
-
- __description__ = """Dailymotion.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def getStreams(self):
- streams = []
-
- for result in re.finditer(r"\"(?P<URL>http:\\/\\/www.dailymotion.com\\/cdn\\/H264-(?P<QF>.*?)\\.*?)\"",
- self.html):
- url = result.group('URL')
- qf = result.group('QF')
-
- link = url.replace("\\", "")
- quality = tuple(int(x) for x in qf.split("x"))
-
- streams.append((quality, link))
-
- return sorted(streams, key=lambda x: x[0][::-1])
-
-
- def getQuality(self):
- q = self.getConfig('quality')
-
- if q == "Lowest":
- quality = 0
- elif q == "Highest":
- quality = -1
- else:
- quality = int(q.rsplit(" ")[1][:-1])
-
- return quality
-
-
- def getLink(self, streams, quality):
- if quality > 0:
- for x, s in [item for item in enumerate(streams)][::-1]:
- qf = s[0][1]
- if qf <= quality:
- idx = x
- break
- else:
- idx = 0
- else:
- idx = quality
-
- s = streams[idx]
-
- self.logInfo(_("Download video quality %sx%s") % s[0])
-
- return s[1]
-
-
- def checkInfo(self, pyfile):
- pyfile.name, pyfile.size, pyfile.status, pyfile.url = getInfo([pyfile.url])[0]
-
- if pyfile.status == 1:
- self.offline()
-
- elif pyfile.status == 6:
- self.tempOffline()
-
-
- def process(self, pyfile):
- self.checkInfo(pyfile)
-
- id = re.match(self.__pattern__, pyfile.url).group('ID')
- self.html = self.load("http://www.dailymotion.com/embed/video/" + id, decode=True)
-
- streams = self.getStreams()
- quality = self.getQuality()
-
- self.download(self.getLink(streams, quality))
diff --git a/module/plugins/hoster/DataHu.py b/module/plugins/hoster/DataHu.py
deleted file mode 100644
index 955c94437..000000000
--- a/module/plugins/hoster/DataHu.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://data.hu/get/6381232/random.bin
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DataHu(SimpleHoster):
- __name__ = "DataHu"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?data\.hu/get/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Data.hu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("crash", None),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
- OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
- LINK_FREE_PATTERN = r'<div class="download_box_button"><a href="(.+?)">'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
-
-getInfo = create_getInfo(DataHu)
diff --git a/module/plugins/hoster/DataportCz.py b/module/plugins/hoster/DataportCz.py
deleted file mode 100644
index ad514f5eb..000000000
--- a/module/plugins/hoster/DataportCz.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DataportCz(SimpleHoster):
- __name__ = "DataportCz"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __pattern__ = r'http://(?:www\.)?dataport\.cz/file/(.+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dataport.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
- SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
-
- CAPTCHA_PATTERN = r'<section id="captcha_bg">\s*<img src="(.*?)"'
- FREE_SLOTS_PATTERN = ur'Počet volnÜch slotů: <span class="darkblue">(\d+)</span><br />'
-
-
- def handleFree(self, pyfile):
- captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
-
- for _i in xrange(60):
- action, inputs = self.parseHtmlForm('free_download_form')
- self.logDebug(action, inputs)
- if not action or not inputs:
- self.error(_("free_download_form"))
-
- if "captchaId" in inputs and inputs['captchaId'] in captchas:
- inputs['captchaCode'] = captchas[inputs['captchaId']]
- else:
- self.error(_("captcha"))
-
- self.download("http://www.dataport.cz%s" % action, post=inputs)
-
- check = self.checkDownload({"captcha": 'alert("\u0160patn\u011b opsan\u00fd k\u00f3d z obr\u00e1zu");',
- "slot" : 'alert("Je n\u00e1m l\u00edto, ale moment\u00e1ln\u011b nejsou'})
- if check == "captcha":
- self.error(_("invalid captcha"))
-
- elif check == "slot":
- self.logDebug("No free slots - wait 60s and retry")
- self.wait(60, False)
- self.html = self.load(pyfile.url, decode=True)
- continue
-
- else:
- break
-
-
-getInfo = create_getInfo(DataportCz)
diff --git a/module/plugins/hoster/DateiTo.py b/module/plugins/hoster/DateiTo.py
deleted file mode 100644
index 71a9eb4bf..000000000
--- a/module/plugins/hoster/DateiTo.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DateiTo(SimpleHoster):
- __name__ = "DateiTo"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Datei.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
- SIZE_PATTERN = r'Dateigr&ouml;&szlig;e:</td>\s*<td colspan="2">(?P<S>.*?)</'
- OFFLINE_PATTERN = r'>Datei wurde nicht gefunden<|>Bitte wÀhle deine Datei aus... <'
-
- WAIT_PATTERN = r'countdown\({seconds: (\d+)'
- MULTIDL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
-
- DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
-
-
- def handleFree(self, pyfile):
- url = 'http://datei.to/ajax/download.php'
- data = {'P': 'I', 'ID': self.info['pattern']['ID']}
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(10):
- self.logDebug("URL", url, "POST", data)
- self.html = self.load(url, post=data)
- self.checkErrors()
-
- if url.endswith('download.php') and 'P' in data:
- if data['P'] == 'I':
- self.doWait()
-
- elif data['P'] == 'IV':
- break
-
- m = re.search(self.DATA_PATTERN, self.html)
- if m is None:
- self.error(_("data"))
- url = 'http://datei.to/' + m.group(1)
- data = dict(x.split('=') for x in m.group(2).split('&'))
-
- if url.endswith('recaptcha.php'):
- data['recaptcha_response_field'], data['recaptcha_challenge_field'] = recaptcha.challenge()
- else:
- self.fail(_("Too bad..."))
-
- self.link = self.html
-
-
- def checkErrors(self):
- m = re.search(self.MULTIDL_PATTERN, self.html)
- if m:
- m = re.search(self.WAIT_PATTERN, self.html)
- wait_time = int(m.group(1)) if m else 30
-
- errmsg = self.info['error'] = _("Parallel downloads")
- self.retry(wait_time=wait_time, reason=errmsg)
-
- self.info.pop('error', None)
-
-
- def doWait(self):
- m = re.search(self.WAIT_PATTERN, self.html)
- wait_time = int(m.group(1)) if m else 30
-
- self.load('http://datei.to/ajax/download.php', post={'P': 'Ads'})
- self.wait(wait_time, False)
-
-
-getInfo = create_getInfo(DateiTo)
diff --git a/module/plugins/hoster/DdlstorageCom.py b/module/plugins/hoster/DdlstorageCom.py
deleted file mode 100644
index d67b4f30f..000000000
--- a/module/plugins/hoster/DdlstorageCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DdlstorageCom(DeadHoster):
- __name__ = "DdlstorageCom"
- __type__ = "hoster"
- __version__ = "1.02"
-
- __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/\w+'
- __config__ = []
-
- __description__ = """DDLStorage.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DdlstorageCom)
diff --git a/module/plugins/hoster/DebridItaliaCom.py b/module/plugins/hoster/DebridItaliaCom.py
deleted file mode 100644
index 4f64215fa..000000000
--- a/module/plugins/hoster/DebridItaliaCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class DebridItaliaCom(MultiHoster):
- __name__ = "DebridItaliaCom"
- __type__ = "hoster"
- __version__ = "0.17"
-
- __pattern__ = r'https?://(?:www\.|s\d+\.)?debriditalia\.com/dl/\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Debriditalia.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [("https://", "http://")]
-
-
- def handlePremium(self, pyfile):
- self.html = self.load("http://www.debriditalia.com/api.php",
- get={'generate': "on", 'link': pyfile.url, 'p': self.getPassword()})
-
- if "ERROR:" not in self.html:
- self.link = self.html.strip()
- else:
- self.info['error'] = re.search(r'ERROR:(.*)', self.html).group(1).strip()
-
- self.html = self.load("http://debriditalia.com/linkgen2.php",
- post={'xjxfun' : "convertiLink",
- 'xjxargs[]': "S<![CDATA[%s]]>" % pyfile.url,
- 'xjxargs[]': "S%s" % self.getPassword()})
- try:
- self.link = re.search(r'<a href="(.+?)"', self.html).group(1)
- except AttributeError:
- pass
-
-
-getInfo = create_getInfo(DebridItaliaCom)
diff --git a/module/plugins/hoster/DepositfilesCom.py b/module/plugins/hoster/DepositfilesCom.py
deleted file mode 100644
index 8559bbc36..000000000
--- a/module/plugins/hoster/DepositfilesCom.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DepositfilesCom(SimpleHoster):
- __name__ = "DepositfilesCom"
- __type__ = "hoster"
- __version__ = "0.55"
-
- __pattern__ = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Depositfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
- SIZE_PATTERN = r': <b>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</b>'
- OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
-
- NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
- (r'.*<b title="(?P<N>.+?)".*', "\g<N>")]
- URL_REPLACEMENTS = [(__pattern__ + ".*", "https://dfiles.eu/files/\g<ID>")]
-
- COOKIES = [("dfiles.eu", "lang_current", "en")]
-
- WAIT_PATTERN = r'(?:download_waiter_remain">|html_download_api-limit_interval">|>Please wait|>Try in).+'
- ERROR_PATTER = r'File is checked, please try again in a minute'
-
- LINK_FREE_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
- LINK_PREMIUM_PATTERN = r'class="repeat"><a href="(.+?)"'
- LINK_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
-
-
- def handleFree(self, pyfile):
- self.html = self.load(pyfile.url, post={'gateway_result': "1"})
-
- self.checkErrors()
-
- m = re.search(r"var fid = '(\w+)';", self.html)
- if m is None:
- self.retry(wait_time=5)
- params = {'fid': m.group(1)}
- self.logDebug("FID: %s" % params['fid'])
-
- self.checkErrors()
-
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
- if captcha_key is None:
- return
-
- self.html = self.load("https://dfiles.eu/get_file.php", get=params)
-
- if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
- params['response'], params['challenge'] = recaptcha.challenge(captcha_key)
- self.html = self.load("https://dfiles.eu/get_file.php", get=params)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = urllib.unquote(m.group(1))
-
-
- def handlePremium(self, pyfile):
- if '<span class="html_download_api-gold_traffic_limit">' in self.html:
- self.logWarning(_("Download limit reached"))
- self.retry(25, 60 * 60, "Download limit reached")
-
- elif 'onClick="show_gold_offer' in self.html:
- self.account.relogin(self.user)
- self.retry()
-
- else:
- link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- mirror = re.search(self.LINK_MIRROR_PATTERN, self.html)
-
- if link:
- self.link = link.group(1)
-
- elif mirror:
- self.link = mirror.group(1)
-
-
-getInfo = create_getInfo(DepositfilesCom)
diff --git a/module/plugins/hoster/DevhostSt.py b/module/plugins/hoster/DevhostSt.py
deleted file mode 100644
index e7a23f6b1..000000000
--- a/module/plugins/hoster/DevhostSt.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://d-h.st/mM8
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DevhostSt(SimpleHoster):
- __name__ = "DevhostSt"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """d-h.st hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<span title="(?P<N>.*?)"'
- SIZE_PATTERN = r'</span> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<br'
- HASHSUM_PATTERN = r'>(?P<T>.*?) Sum</span>: &nbsp;(?P<H>.*?)<br'
-
- OFFLINE_PATTERN = r'>File Not Found'
- LINK_FREE_PATTERN = r'var product_download_url= \'(.+?)\''
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(DevhostSt)
diff --git a/module/plugins/hoster/DlFreeFr.py b/module/plugins/hoster/DlFreeFr.py
deleted file mode 100644
index 72d15852c..000000000
--- a/module/plugins/hoster/DlFreeFr.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.network.Browser import Browser
-from module.network.CookieJar import CookieJar
-from module.plugins.internal.CaptchaService import AdYouLike
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns
-
-
-class CustomBrowser(Browser):
-
- def __init__(self, bucket=None, options={}):
- Browser.__init__(self, bucket, options)
-
-
- def load(self, *args, **kwargs):
- post = kwargs.get("post")
-
- if post is None and len(args) > 2:
- post = args[2]
-
- if post:
- self.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.http.c.setopt(pycurl.POST, 1)
- self.http.c.setopt(pycurl.CUSTOMREQUEST, "POST")
- else:
- self.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.http.c.setopt(pycurl.POST, 0)
- self.http.c.setopt(pycurl.CUSTOMREQUEST, "GET")
-
- return Browser.load(self, *args, **kwargs)
-
-
-class DlFreeFr(SimpleHoster):
- __name__ = "DlFreeFr"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dl.free.fr hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("the-razer", "daniel_ AT gmx DOT net"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Toilal", "toilal.dev@gmail.com")]
-
-
- NAME_PATTERN = r'Fichier:</td>\s*<td.*?>(?P<N>[^>]*)</td>'
- SIZE_PATTERN = r'Taille:</td>\s*<td.*?>(?P<S>[\d.,]+\w)o'
- OFFLINE_PATTERN = r'Erreur 404 - Document non trouv|Fichier inexistant|Le fichier demand&eacute; n\'a pas &eacute;t&eacute; trouv&eacute;'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.limitDL = 5
- self.chunkLimit = 1
-
-
- def init(self):
- factory = self.core.requestFactory
- self.req = CustomBrowser(factory.bucket, factory.getOptions())
-
-
- def process(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
- valid_url = pyfile.url
- headers = self.load(valid_url, just_header=True)
-
- if headers.get('code') == 302:
- valid_url = headers.get('location')
- headers = self.load(valid_url, just_header=True)
-
- if headers.get('code') == 200:
- content_type = headers.get('content-type')
- if content_type and content_type.startswith("text/html"):
- # Undirect acces to requested file, with a web page providing it (captcha)
- self.html = self.load(valid_url)
- self.handleFree(pyfile)
- else:
- # Direct access to requested file for users using free.fr as Internet Service Provider.
- self.link = valid_url
-
- elif headers.get('code') == 404:
- self.offline()
-
- else:
- self.fail(_("Invalid return code: ") + str(headers.get('code')))
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('action="getfile.pl"')
-
- adyoulike = AdYouLike(self)
- response, challenge = adyoulike.challenge()
- inputs.update(response)
-
- self.load("http://dl.free.fr/getfile.pl", post=inputs)
- headers = self.getLastHeaders()
- if headers.get("code") == 302 and "set-cookie" in headers and "location" in headers:
- m = re.search("(.*?)=(.*?); path=(.*?); domain=(.*?)", headers.get("set-cookie"))
- cj = CookieJar(__name__)
- if m:
- cj.setCookie(m.group(4), m.group(1), m.group(2), m.group(3))
- else:
- self.fail(_("Cookie error"))
-
- self.link = headers.get("location")
-
- self.req.setCookieJar(cj)
- else:
- self.fail(_("Invalid response"))
-
-
- def getLastHeaders(self):
- #parse header
- header = {"code": self.req.code}
- for line in self.req.http.header.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
- return header
-
-
-getInfo = create_getInfo(DlFreeFr)
diff --git a/module/plugins/hoster/DodanePl.py b/module/plugins/hoster/DodanePl.py
deleted file mode 100644
index 96f87a21c..000000000
--- a/module/plugins/hoster/DodanePl.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DodanePl(DeadHoster):
- __name__ = "DodanePl"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?dodane\.pl/file/\d+'
- __config__ = []
-
- __description__ = """Dodane.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
-getInfo = create_getInfo(DodanePl)
diff --git a/module/plugins/hoster/DropboxCom.py b/module/plugins/hoster/DropboxCom.py
deleted file mode 100644
index eec968f5a..000000000
--- a/module/plugins/hoster/DropboxCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DropboxCom(SimpleHoster):
- __name__ = "DropboxCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?dropbox\.com/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dropbox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<title>Dropbox - (?P<N>.+?)<'
- SIZE_PATTERN = r'&nbsp;&middot;&nbsp; (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'<title>Dropbox - (404|Shared link error)<'
-
- COOKIES = [("dropbox.com", "lang", "en")]
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- self.download(pyfile.url, get={'dl': "1"})
-
-
-getInfo = create_getInfo(DropboxCom)
diff --git a/module/plugins/hoster/DuploadOrg.py b/module/plugins/hoster/DuploadOrg.py
deleted file mode 100644
index 542240bab..000000000
--- a/module/plugins/hoster/DuploadOrg.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DuploadOrg(DeadHoster):
- __name__ = "DuploadOrg"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/\w{12}'
- __config__ = []
-
- __description__ = """Dupload.grg hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DuploadOrg)
diff --git a/module/plugins/hoster/EasybytezCom.py b/module/plugins/hoster/EasybytezCom.py
deleted file mode 100644
index 693910c1b..000000000
--- a/module/plugins/hoster/EasybytezCom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class EasybytezCom(XFSHoster):
- __name__ = "EasybytezCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?easybytez\.com/\w{12}'
-
- __description__ = """Easybytez.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- OFFLINE_PATTERN = r'>File not available'
-
- LINK_PATTERN = r'(http://(\w+\.(easybytez|easyload|ezbytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/.+?)["\'<]'
-
-
-getInfo = create_getInfo(EasybytezCom)
diff --git a/module/plugins/hoster/EdiskCz.py b/module/plugins/hoster/EdiskCz.py
deleted file mode 100644
index b8485001e..000000000
--- a/module/plugins/hoster/EdiskCz.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class EdiskCz(SimpleHoster):
- __name__ = "EdiskCz"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Edisk.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<span class="fl" title="(?P<N>.+?)">\s*.*?\((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</h1></span>'
- OFFLINE_PATTERN = r'<h3>This file does not exist due to one of the following:</h3><ul><li>'
-
- ACTION_PATTERN = r'/en/download/(\d+/.*\.html)'
- LINK_FREE_PATTERN = r'http://.*edisk\.cz.*\.html'
-
-
- def setup(self):
- self.multiDL = False
-
-
- def process(self, pyfile):
- url = re.sub("/(stahni|sk/stahni)/", "/en/download/", pyfile.url)
-
- self.logDebug("URL:" + url)
-
- m = re.search(self.ACTION_PATTERN, url)
- if m is None:
- self.error(_("ACTION_PATTERN not found"))
- action = m.group(1)
-
- self.html = self.load(url, decode=True)
- self.getFileInfo()
-
- self.html = self.load(re.sub("/en/download/", "/en/download-slow/", url))
-
- url = self.load(re.sub("/en/download/", "/x-download/", url), post={
- "action": action
- })
-
- if not re.match(self.LINK_FREE_PATTERN, url):
- self.fail(_("Unexpected server response"))
-
- self.link = url
-
-
-getInfo = create_getInfo(EdiskCz)
diff --git a/module/plugins/hoster/EgoFilesCom.py b/module/plugins/hoster/EgoFilesCom.py
deleted file mode 100644
index 46d0fac6a..000000000
--- a/module/plugins/hoster/EgoFilesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EgoFilesCom(DeadHoster):
- __name__ = "EgoFilesCom"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https?://(?:www\.)?egofiles\.com/\w+'
- __config__ = []
-
- __description__ = """Egofiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(EgoFilesCom)
diff --git a/module/plugins/hoster/EnteruploadCom.py b/module/plugins/hoster/EnteruploadCom.py
deleted file mode 100644
index b2306ce69..000000000
--- a/module/plugins/hoster/EnteruploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EnteruploadCom(DeadHoster):
- __name__ = "EnteruploadCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?enterupload\.com/\w+'
- __config__ = []
-
- __description__ = """EnterUpload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(EnteruploadCom)
diff --git a/module/plugins/hoster/EpicShareNet.py b/module/plugins/hoster/EpicShareNet.py
deleted file mode 100644
index e2a44b172..000000000
--- a/module/plugins/hoster/EpicShareNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EpicShareNet(DeadHoster):
- __name__ = "EpicShareNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?epicshare\.net/\w{12}'
- __config__ = []
-
- __description__ = """EpicShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(EpicShareNet)
diff --git a/module/plugins/hoster/EuroshareEu.py b/module/plugins/hoster/EuroshareEu.py
deleted file mode 100644
index 842e0cb87..000000000
--- a/module/plugins/hoster/EuroshareEu.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class EuroshareEu(SimpleHoster):
- __name__ = "EuroshareEu"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Euroshare.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<span style="float: left;"><strong>(?P<N>.+?)</strong> \((?P<S>.+?)\)</span>'
- OFFLINE_PATTERN = ur'<h2>S.bor sa nena.iel</h2>|Poşadovaná stránka neexistuje!'
-
- LINK_FREE_PATTERN = r'<a href="(/file/\d+/[^/]*/download/)"><div class="downloadButton"'
-
- ERR_PARDL_PATTERN = r'<h2>Prebieha s.ahovanie</h2>|<p>Naraz je z jednej IP adresy mo.n. s.ahova. iba jeden s.bor'
- ERR_NOT_LOGGED_IN_PATTERN = r'href="/customer-zone/login/"'
-
- URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
-
-
- def handlePremium(self, pyfile):
- if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
- self.account.relogin(self.user)
- self.retry(reason=_("User not logged in"))
-
- self.link = pyfile.url.rstrip('/') + "/download/"
-
- check = self.checkDownload({"login": re.compile(self.ERR_NOT_LOGGED_IN_PATTERN),
- "json" : re.compile(r'\{"status":"error".*?"message":"(.*?)"')})
-
- if check == "login" or (check == "json" and self.lastCheck.group(1) == "Access token expired"):
- self.account.relogin(self.user)
- self.retry(reason=_("Access token expired"))
-
- elif check == "json":
- self.fail(self.lastCheck.group(1))
-
-
- def handleFree(self, pyfile):
- if re.search(self.ERR_PARDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = "http://euroshare.eu%s" % m.group(1)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({"multi-dl": re.compile(self.ERR_PARDL_PATTERN)})
- self.longWait(5 * 60, 12)
-
- return super(EuroshareEu, self).checkFile(rules)
-
-
-getInfo = create_getInfo(EuroshareEu)
diff --git a/module/plugins/hoster/ExashareCom.py b/module/plugins/hoster/ExashareCom.py
deleted file mode 100644
index 536c09b6d..000000000
--- a/module/plugins/hoster/ExashareCom.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class ExashareCom(XFSHoster):
- __name__ = "ExashareCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?exashare\.com/\w{12}'
-
- __description__ = """Exashare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'>(?P<NAME>.+?)<small>\( (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- LINK_FREE_PATTERN = r'file: "(.+?)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
- else:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(ExashareCom)
diff --git a/module/plugins/hoster/ExtabitCom.py b/module/plugins/hoster/ExtabitCom.py
deleted file mode 100644
index f3b4d28be..000000000
--- a/module/plugins/hoster/ExtabitCom.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, secondsToMidnight
-
-
-class ExtabitCom(SimpleHoster):
- __name__ = "ExtabitCom"
- __type__ = "hoster"
- __version__ = "0.65"
-
- __pattern__ = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Extabit.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>.+?)">'
- SIZE_PATTERN = r'<th>Size:</th>\s*<td class="col-fileinfo">(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'>File not found<'
- TEMP_OFFLINE_PATTERN = r'>(File is temporary unavailable|No download mirror)<'
-
- LINK_FREE_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]'
-
-
- def handleFree(self, pyfile):
- if r">Only premium users can download this file" in self.html:
- self.fail(_("Only premium users can download this file"))
-
- m = re.search(r"Next free download from your ip will be available in <b>(\d+)\s*minutes", self.html)
- if m:
- self.wait(int(m.group(1)) * 60, True)
- elif "The daily downloads limit from your IP is exceeded" in self.html:
- self.logWarning(_("You have reached your daily downloads limit for today"))
- self.wait(secondsToMidnight(gmt=2), True)
-
- self.logDebug("URL: " + self.req.http.lastEffectiveURL)
- m = re.match(self.__pattern__, self.req.http.lastEffectiveURL)
- fileID = m.group('ID') if m else self.info['pattern']['ID']
-
- m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
- if m:
- recaptcha = ReCaptcha(self)
- captcha_key = m.group(1)
-
- for _i in xrange(5):
- get_data = {"type": "recaptcha"}
- get_data['capture'], get_data['challenge'] = recaptcha.challenge(captcha_key)
- res = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
- if "ok" in res:
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
- else:
- self.fail(_("Invalid captcha"))
- else:
- self.error(_("Captcha"))
-
- if not "href" in res:
- self.error(_("Bad JSON response"))
-
- self.html = self.load("http://extabit.com/file/%s%s" % (fileID, res['href']))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(ExtabitCom)
diff --git a/module/plugins/hoster/FastixRu.py b/module/plugins/hoster/FastixRu.py
deleted file mode 100644
index cc50f4229..000000000
--- a/module/plugins/hoster/FastixRu.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class FastixRu(MultiHoster):
- __name__ = "FastixRu"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?fastix\.(ru|it)/file/\w{24}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Fastix multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def setup(self):
- self.chunkLimit = 3
-
-
- def handlePremium(self, pyfile):
- api_key = self.account.getAccountData(self.user)
- api_key = api_key['api']
-
- self.html = self.load("http://fastix.ru/api_v2/",
- get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url})
-
- data = json_loads(self.html)
-
- self.logDebug("Json data", data)
-
- if "error\":true" in self.html:
- self.offline()
- else:
- self.link = data['downloadlink']
-
-
-getInfo = create_getInfo(FastixRu)
diff --git a/module/plugins/hoster/FastshareCz.py b/module/plugins/hoster/FastshareCz.py
deleted file mode 100644
index 330a6e3b9..000000000
--- a/module/plugins/hoster/FastshareCz.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FastshareCz(SimpleHoster):
- __name__ = "FastshareCz"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FastShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
- URL_REPLACEMENTS = [("#.*", "")]
-
- COOKIES = [("fastshare.cz", "lang", "en")]
-
- NAME_PATTERN = r'<h3 class="section_title">(?P<N>.+?)<'
- SIZE_PATTERN = r'>Size\s*:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
-
- LINK_FREE_PATTERN = r'>Enter the code\s*:</em>\s*<span><img src="(.+?)"'
- LINK_PREMIUM_PATTERN = r'(http://\w+\.fastshare\.cz/download\.php\?id=\d+&)'
-
- SLOT_ERROR = "> 100% of FREE slots are full"
- CREDIT_ERROR = " credit for "
-
-
- def checkErrors(self):
- if self.SLOT_ERROR in self.html:
- errmsg = self.info['error'] = _("No free slots")
- self.retry(12, 60, errmsg)
-
- if self.CREDIT_ERROR in self.html:
- errmsg = self.info['error'] = _("Not enough traffic left")
- self.logWarning(errmsg)
- self.resetAccount()
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m:
- action, captcha_src = m.groups()
- else:
- self.error(_("FREE_URL_PATTERN not found"))
-
- baseurl = "http://www.fastshare.cz"
- captcha = self.decryptCaptcha(urlparse.urljoin(baseurl, captcha_src))
- self.download(urlparse.urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18})
-
-
- def checkFile(self, rules={}):
- check = self.checkDownload({
- 'paralell-dl' : re.compile(r"<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)"),
- 'wrong captcha': re.compile(r'Download for FREE'),
- 'credit' : re.compile(self.CREDIT_ERROR)
- })
-
- if check == "paralell-dl":
- self.retry(6, 10 * 60, _("Paralell download"))
-
- elif check == "wrong captcha":
- self.retry(max_tries=5, reason=_("Wrong captcha"))
-
- elif check == "credit":
- self.resetAccount()
-
- return super(FastshareCz, self).checkFile(rules)
-
-
-getInfo = create_getInfo(FastshareCz)
diff --git a/module/plugins/hoster/FileApeCom.py b/module/plugins/hoster/FileApeCom.py
deleted file mode 100644
index 314f069f9..000000000
--- a/module/plugins/hoster/FileApeCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FileApeCom(DeadHoster):
- __name__ = "FileApeCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+'
- __config__ = []
-
- __description__ = """FileApe.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("espes", None)]
-
-
-getInfo = create_getInfo(FileApeCom)
diff --git a/module/plugins/hoster/FileSharkPl.py b/module/plugins/hoster/FileSharkPl.py
deleted file mode 100644
index de030be9c..000000000
--- a/module/plugins/hoster/FileSharkPl.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FileSharkPl(SimpleHoster):
- __name__ = "FileSharkPl"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?fileshark\.pl/pobierz/\d+/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FileShark.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h2 class="name-file">(?P<N>.+)</h2>'
- SIZE_PATTERN = r'<p class="size-file">(.*?)<strong>(?P<S>\d+\.?\d*)\s(?P<U>\w+)</strong></p>'
- OFFLINE_PATTERN = r'(P|p)lik zosta. (usuni.ty|przeniesiony)'
-
- LINK_FREE_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-free">'
- LINK_PREMIUM_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-premium">'
-
- WAIT_PATTERN = r'var timeToDownload = (\d+);'
- ERROR_PATTERN = r'<p class="lead text-center alert alert-warning">(.*?)</p>'
- IP_ERROR_PATTERN = r'Strona jest dost.pna wy..cznie dla u.ytkownik.w znajduj.cych si. na terenie Polski'
- SLOT_ERROR_PATTERN = r'Osi.gni.to maksymaln. liczb. .ci.ganych jednocze.nie plik.w\.'
-
- CAPTCHA_PATTERN = r'<img src="data:image/jpeg;base64,(.*?)" title="captcha"'
- TOKEN_PATTERN = r'name="form\[_token\]" value="(.*?)" />'
-
-
- def setup(self):
- self.resumeDownload = True
-
- if self.premium:
- self.multiDL = True
- self.limitDL = 20
- else:
- self.multiDL = False
-
-
- def checkErrors(self):
- # check if file is now available for download (-> file name can be found in html body)
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = _("Another download already run")
- self.retry(15, int(m.group(1)), errmsg)
-
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- alert = m.group(1)
-
- if re.match(self.IP_ERROR_PATTERN, alert):
- self.fail(_("Only connections from Polish IP are allowed"))
-
- elif re.match(self.SLOT_ERROR_PATTERN, alert):
- errmsg = self.info['error'] = _("No free download slots available")
- self.logWarning(errmsg)
- self.retry(10, 30 * 60, _("Still no free download slots available"))
-
- else:
- self.info['error'] = alert
- self.retry(10, 10 * 60, _("Try again later"))
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download url not found"))
-
- link = urlparse.urljoin("http://fileshark.pl", m.group(1))
-
- self.html = self.load(link)
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- seconds = int(m.group(1))
- self.logDebug("Wait %s seconds" % seconds)
- self.wait(seconds)
-
- action, inputs = self.parseHtmlForm('action=""')
-
- m = re.search(self.TOKEN_PATTERN, self.html)
- if m is None:
- self.retry(reason=_("Captcha form not found"))
-
- inputs['form[_token]'] = m.group(1)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.retry(reason=_("Captcha image not found"))
-
- tmp_load = self.load
- self.load = self._decode64 #: work-around: injects decode64 inside decryptCaptcha
-
- inputs['form[captcha]'] = self.decryptCaptcha(m.group(1), imgtype='jpeg')
- inputs['form[start]'] = ""
-
- self.load = tmp_load
-
- self.download(link, post=inputs, disposition=True)
-
-
- def _decode64(self, data, *args, **kwargs):
- return data.decode('base64')
-
-
-getInfo = create_getInfo(FileSharkPl)
diff --git a/module/plugins/hoster/FileStoreTo.py b/module/plugins/hoster/FileStoreTo.py
deleted file mode 100644
index 3262b73ad..000000000
--- a/module/plugins/hoster/FileStoreTo.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FileStoreTo(SimpleHoster):
- __name__ = "FileStoreTo"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FileStore.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- INFO_PATTERN = r'File: <span.*?>(?P<N>.+?)<.*>Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
- TEMP_OFFLINE_PATTERN = r'>Der Download ist nicht bereit !<'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.wait(10)
- self.link = self.load("http://filestore.to/ajax/download.php",
- get={'D': re.search(r'"D=(\w+)', self.html).group(1)})
-
-
-getInfo = create_getInfo(FileStoreTo)
diff --git a/module/plugins/hoster/FilebeerInfo.py b/module/plugins/hoster/FilebeerInfo.py
deleted file mode 100644
index 77ee367a7..000000000
--- a/module/plugins/hoster/FilebeerInfo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilebeerInfo(DeadHoster):
- __name__ = "FilebeerInfo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Filebeer.info plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FilebeerInfo)
diff --git a/module/plugins/hoster/FilecloudIo.py b/module/plugins/hoster/FilecloudIo.py
deleted file mode 100644
index 38d2972a1..000000000
--- a/module/plugins/hoster/FilecloudIo.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilecloudIo(SimpleHoster):
- __name__ = "FilecloudIo"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filecloud.io hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LOGIN_ACCOUNT = True
-
- NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
- SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
- OFFLINE_PATTERN = r'l10n\.(FILES__DOESNT_EXIST|REMOVED)'
- TEMP_OFFLINE_PATTERN = r'l10n\.FILES__WARNING'
-
- UKEY_PATTERN = r'\'ukey\'\s*:\'(\w+)'
- AB1_PATTERN = r'if\( __ab1 == \'(\w+)\' \)'
-
- ERROR_MSG_PATTERN = r'var __error_msg\s*=\s*l10n\.(.*?);'
-
- RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\';'
-
- LINK_FREE_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- data = {"ukey": self.info['pattern']['ID']}
-
- m = re.search(self.AB1_PATTERN, self.html)
- if m is None:
- self.error(_("__AB1"))
- data['__ab1'] = m.group(1)
-
- recaptcha = ReCaptcha(self)
-
- m = re.search(self.RECAPTCHA_PATTERN, self.html)
- captcha_key = m.group(1) if m else recaptcha.detect_key()
-
- if captcha_key is None:
- self.error(_("ReCaptcha key not found"))
-
- response, challenge = recaptcha.challenge(captcha_key)
- self.account.form_data = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field" : response}
- self.account.relogin(self.user)
- self.retry(2)
-
- json_url = "http://filecloud.io/download-request.json"
- res = self.load(json_url, post=data)
- self.logDebug(res)
- res = json_loads(res)
-
- if "error" in res and res['error']:
- self.fail(res)
-
- self.logDebug(res)
- if res['captcha']:
- data['ctype'] = "recaptcha"
-
- for _i in xrange(5):
- data['recaptcha_response'], data['recaptcha_challenge'] = recaptcha.challenge(captcha_key)
-
- json_url = "http://filecloud.io/download-request.json"
- res = self.load(json_url, post=data)
- self.logDebug(res)
- res = json_loads(res)
-
- if "retry" in res and res['retry']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("Incorrect captcha"))
-
- if res['dl']:
- self.html = self.load('http://filecloud.io/download.html')
-
- m = re.search(self.LINK_FREE_PATTERN % self.info['pattern']['ID'], self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- if "size" in self.info and self.info['size']:
- self.check_data = {"size": int(self.info['size'])}
-
- self.link = m.group(1)
- else:
- self.fail(_("Unexpected server response"))
-
-
- def handlePremium(self, pyfile):
- akey = self.account.getAccountData(self.user)['akey']
- ukey = self.info['pattern']['ID']
- self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey))
- rep = self.load("http://api.filecloud.io/api-fetch_download_url.api",
- post={"akey": akey, "ukey": ukey})
- self.logDebug("FetchDownloadUrl: " + rep)
- rep = json_loads(rep)
- if rep['status'] == 'ok':
- self.link = rep['download_url']
- else:
- self.fail(rep['message'])
-
-
-getInfo = create_getInfo(FilecloudIo)
diff --git a/module/plugins/hoster/FilefactoryCom.py b/module/plugins/hoster/FilefactoryCom.py
deleted file mode 100644
index 0a9d5e2cc..000000000
--- a/module/plugins/hoster/FilefactoryCom.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- for url in urls:
- h = getURL(url, just_header=True)
- m = re.search(r'Location: (.+)\r\n', h)
- if m and not re.match(m.group(1), FilefactoryCom.__pattern__): #: It's a direct link! Skipping
- yield (url, 0, 3, url)
- else: #: It's a standard html page
- yield parseFileInfo(FilefactoryCom, url, getURL(url))
-
-
-class FilefactoryCom(SimpleHoster):
- __name__ = "FilefactoryCom"
- __type__ = "hoster"
- __version__ = "0.54"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filefactory.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'<div id="file_name"[^>]*>\s*<h2>(?P<N>[^<]+)</h2>\s*<div id="file_info">\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+) uploaded'
- OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'"([^"]+filefactory\.com/get.+?)"'
-
- WAIT_PATTERN = r'<div id="countdown_clock" data-delay="(\d+)">'
- PREMIUM_ONLY_PATTERN = r'>Premium Account Required'
-
- COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
-
-
- def handleFree(self, pyfile):
- if "Currently only Premium Members can download files larger than" in self.html:
- self.fail(_("File too large for free download"))
- elif "All free download slots on this server are currently in use" in self.html:
- self.retry(50, 15 * 60, _("All free slots are busy"))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
-
- def checkFile(self, rules={}):
- check = self.checkDownload({'multiple': "You are currently downloading too many files at once.",
- 'error' : '<div id="errorMessage">'})
-
- if check == "multiple":
- self.logDebug("Parallel downloads detected; waiting 15 minutes")
- self.retry(wait_time=15 * 60, reason=_("Parallel downloads"))
-
- elif check == "error":
- self.error(_("Unknown error"))
-
- return super(FilefactoryCom, self).checkFile(rules)
-
-
- def handlePremium(self, pyfile):
- self.link = self.directLink(self.load(pyfile.url, just_header=True))
-
- if not self.link:
- html = self.load(pyfile.url)
- m = re.search(self.LINK_PREMIUM_PATTERN, html)
- if m:
- self.link = m.group(1)
- else:
- self.error(_("Premium download link not found"))
diff --git a/module/plugins/hoster/FilejungleCom.py b/module/plugins/hoster/FilejungleCom.py
deleted file mode 100644
index 236603c6e..000000000
--- a/module/plugins/hoster/FilejungleCom.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.FileserveCom import FileserveCom, checkFile
-from module.plugins.Plugin import chunks
-
-
-class FilejungleCom(FileserveCom):
- __name__ = "FilejungleCom"
- __type__ = "hoster"
- __version__ = "0.51"
-
- __pattern__ = r'http://(?:www\.)?filejungle\.com/f/(?P<ID>[^/]+)'
-
- __description__ = """Filejungle.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- URLS = ["http://www.filejungle.com/f/", "http://www.filejungle.com/check_links.php",
- "http://www.filejungle.com/checkReCaptcha.php"]
- LINKCHECK_TR = r'<li>\s*(<div class="col1">.*?)</li>'
- LINKCHECK_TD = r'<div class="(?:col )?col\d">(?:<.*?>|&nbsp;)*([^<]*)'
-
- LONG_WAIT_PATTERN = r'<h1>Please wait for (\d+) (\w+)\s*to download the next file\.</h1>'
-
-
-def getInfo(urls):
- for chunk in chunks(urls, 100):
- yield checkFile(FilejungleCom, chunk)
diff --git a/module/plugins/hoster/FileomCom.py b/module/plugins/hoster/FileomCom.py
deleted file mode 100644
index 306953b84..000000000
--- a/module/plugins/hoster/FileomCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://fileom.com/gycaytyzdw3g/random.bin.html
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class FileomCom(XFSHoster):
- __name__ = "FileomCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?fileom\.com/\w{12}'
-
- __description__ = """Fileom.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
- SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- LINK_PATTERN = r'var url2 = \'(.+?)\';'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
-getInfo = create_getInfo(FileomCom)
diff --git a/module/plugins/hoster/FilepostCom.py b/module/plugins/hoster/FilepostCom.py
deleted file mode 100644
index 2a9e3dc26..000000000
--- a/module/plugins/hoster/FilepostCom.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilepostCom(SimpleHoster):
- __name__ = "FilepostCom"
- __type__ = "hoster"
- __version__ = "0.33"
-
- __pattern__ = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filepost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<input type="text" id="url" value=\'<a href.*?>(?P<N>[^>]+?) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</a>\' class="inp_text"/>'
- OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>|<div class="file_info file_info_deleted">'
-
- PREMIUM_ONLY_PATTERN = r'members only. Please upgrade to premium|a premium membership is required to download this file'
- RECAPTCHA_PATTERN = r'Captcha.init\({\s*key:\s*\'(.+?)\''
- FLP_TOKEN_PATTERN = r'set_store_options\({token: \'(.+?)\''
-
-
- def handleFree(self, pyfile):
- m = re.search(self.FLP_TOKEN_PATTERN, self.html)
- if m is None:
- self.error(_("Token"))
- flp_token = m.group(1)
-
- m = re.search(self.RECAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha key"))
- captcha_key = m.group(1)
-
- # Get wait time
- get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time.time() * 10000)) + '-xml'}
- post_dict = {'action': 'set_download', 'token': flp_token, 'code': self.info['pattern']['ID']}
- wait_time = int(self.getJsonResponse(get_dict, post_dict, 'wait_time'))
-
- if wait_time > 0:
- self.wait(wait_time)
-
- post_dict = {"token": flp_token, "code": self.info['pattern']['ID'], "file_pass": ''}
-
- if 'var is_pass_exists = true;' in self.html:
- # Solve password
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + file_pass)
-
- get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
- post_dict['file_pass'] = file_pass
-
- self.link = self.getJsonResponse(get_dict, post_dict, 'link')
-
- if not self.link:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- else:
- # Solve recaptcha
- recaptcha = ReCaptcha(self)
-
- for i in xrange(5):
- get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
- if i:
- post_dict['recaptcha_response_field'], post_dict['recaptcha_challenge_field'] = recaptcha.challenge(
- captcha_key)
- self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
- captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
-
- self.link = self.getJsonResponse(get_dict, post_dict, 'link')
-
- else:
- self.fail(_("Invalid captcha"))
-
-
- def getJsonResponse(self, get_dict, post_dict, field):
- res = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
-
- self.logDebug(res)
-
- if not 'js' in res:
- self.error(_("JSON %s 1") % field)
-
- # i changed js_answer to res['js'] since js_answer is nowhere set.
- # i don't know the JSON-HTTP specs in detail, but the previous author
- # accessed res['js']['error'] as well as js_answer['error'].
- # see the two lines commented out with "# ~?".
- if 'error' in res['js']:
-
- if res['js']['error'] == 'download_delay':
- self.retry(wait_time=res['js']['params']['next_download'])
- # ~? self.retry(wait_time=js_answer['params']['next_download'])
-
- elif 'Wrong file password' in res['js']['error'] \
- or 'You entered a wrong CAPTCHA code' in res['js']['error'] \
- or 'CAPTCHA Code nicht korrekt' in res['js']['error']:
- return None
-
- elif 'CAPTCHA' in res['js']['error']:
- self.logDebug("Error response is unknown, but mentions CAPTCHA")
- return None
-
- else:
- self.fail(res['js']['error'])
-
- if not 'answer' in res['js'] or not field in res['js']['answer']:
- self.error(_("JSON %s 2") % field)
-
- return res['js']['answer'][field]
-
-
-getInfo = create_getInfo(FilepostCom)
diff --git a/module/plugins/hoster/FilepupNet.py b/module/plugins/hoster/FilepupNet.py
deleted file mode 100644
index 874fde3c8..000000000
--- a/module/plugins/hoster/FilepupNet.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.filepup.net/files/k5w4ZVoF1410184283.html
-# http://www.filepup.net/files/R4GBq9XH1410186553.html
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilepupNet(SimpleHoster):
- __name__ = "FilepupNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filepup\.net/files/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filepup.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'>(?P<N>.+?)</h1>'
- SIZE_PATTERN = r'class="fa fa-archive"></i> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>This file has been deleted'
-
- LINK_FREE_PATTERN = r'(http://www\.filepup\.net/get/.+?)\''
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link not found"))
-
- dl_link = m.group(1)
- self.download(dl_link, post={'task': "download"})
-
-
-getInfo = create_getInfo(FilepupNet)
diff --git a/module/plugins/hoster/FilerNet.py b/module/plugins/hoster/FilerNet.py
deleted file mode 100644
index 156392c79..000000000
--- a/module/plugins/hoster/FilerNet.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://filer.net/get/ivgf5ztw53et3ogd
-# http://filer.net/get/hgo14gzcng3scbvv
-
-import pycurl
-import re
-import urlparse
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilerNet(SimpleHoster):
- __name__ = "FilerNet"
- __type__ = "hoster"
- __version__ = "0.19"
-
- __pattern__ = r'https?://(?:www\.)?filer\.net/get/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filer.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'<h1 class="page-header">Free Download (?P<N>\S+) <small>(?P<S>[\w.]+) (?P<U>[\w^_]+)</small></h1>'
- OFFLINE_PATTERN = r'Nicht gefunden'
-
- WAIT_PATTERN = r'musst du <span id="time">(\d+)'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>'
-
-
- def handleFree(self, pyfile):
- inputs = self.parseHtmlForm(input_names={'token': re.compile(r'.+')})[1]
- if 'token' not in inputs:
- self.error(_("Unable to detect token"))
-
- self.html = self.load(pyfile.url, post={'token': inputs['token']}, decode=True)
-
- inputs = self.parseHtmlForm(input_names={'hash': re.compile(r'.+')})[1]
- if 'hash' not in inputs:
- self.error(_("Unable to detect hash"))
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- #@NOTE: Work-around for v0.4.9 just_header issue
- #@TODO: Check for v0.4.10
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.load(pyfile.url, post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response,
- 'hash' : inputs['hash']})
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- if 'location' in self.req.http.header.lower():
- self.link = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
- self.correctCaptcha()
- else:
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(FilerNet)
diff --git a/module/plugins/hoster/FilerioCom.py b/module/plugins/hoster/FilerioCom.py
deleted file mode 100644
index c6ebbd444..000000000
--- a/module/plugins/hoster/FilerioCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class FilerioCom(XFSHoster):
- __name__ = "FilerioCom"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
-
- __description__ = """FileRio.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- URL_REPLACEMENTS = [(r'filekeen\.com', "filerio.in")]
-
- OFFLINE_PATTERN = r'>&quot;File Not Found|File has been removed'
-
-
-getInfo = create_getInfo(FilerioCom)
diff --git a/module/plugins/hoster/FilesMailRu.py b/module/plugins/hoster/FilesMailRu.py
deleted file mode 100644
index 7bd099282..000000000
--- a/module/plugins/hoster/FilesMailRu.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-
-
-def getInfo(urls):
- result = []
- for chunk in chunks(urls, 10):
- for url in chunk:
- html = getURL(url)
- if r'<div class="errorMessage mb10">' in html:
- result.append((url, 0, 1, url))
- elif r'Page cannot be displayed' in html:
- result.append((url, 0, 1, url))
- else:
- try:
- url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
- file_name = re.search(url_pattern, html).group(0).split(', event)">')[1].split('</a>')[0]
- result.append((file_name, 0, 2, url))
- except Exception:
- pass
-
- # status 1=OFFLINE, 2=OK, 3=UNKNOWN
- # result.append((#name,#size,#status,#url))
- yield result
-
-
-class FilesMailRu(Hoster):
- __name__ = "FilesMailRu"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __pattern__ = r'http://(?:www\.)?files\.mail\.ru/.+'
-
- __description__ = """Files.mail.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("oZiRiz", "ich@oziriz.de")]
-
-
- def setup(self):
- self.multiDL = bool(self.account)
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url)
- self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
-
- #marks the file as "offline" when the pattern was found on the html-page'''
- if r'<div class="errorMessage mb10">' in self.html:
- self.offline()
-
- elif r'Page cannot be displayed' in self.html:
- self.offline()
-
- #the filename that will be showed in the list (e.g. test.part1.rar)'''
- pyfile.name = self.getFileName()
-
- #prepare and download'''
- if not self.account:
- self.prepare()
- self.download(self.getFileUrl())
- self.myPostProcess()
- else:
- self.download(self.getFileUrl())
- self.myPostProcess()
-
-
- def prepare(self):
- """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected"""
- self.setWait(10)
- self.wait()
- return True
-
-
- def getFileUrl(self):
- """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html"""
- return re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[0]
-
-
- def getFileName(self):
- """gives you the Name for each file. Also extracted from the HTML-Page"""
- return re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0]
-
-
- def myPostProcess(self):
- # searches the file for HTMl-Code. Sometimes the Redirect
- # doesn't work (maybe a curl Problem) and you get only a small
- # HTML file and the Download is marked as "finished"
- # then the download will be restarted. It's only bad for these
- # who want download a HTML-File (it's one in a million ;-) )
- #
- # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB
- # so i set it to check every download because sometimes there are downloads
- # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file
- # (Loading 100MB in to ram is not an option)
- check = self.checkDownload({"html": "<meta name="}, read_size=50000)
- if check == "html":
- self.logInfo(_(
- "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." %
- self.pyfile.name))
- self.retry()
diff --git a/module/plugins/hoster/FileserveCom.py b/module/plugins/hoster/FileserveCom.py
deleted file mode 100644
index 4bca2eb59..000000000
--- a/module/plugins/hoster/FileserveCom.py
+++ /dev/null
@@ -1,216 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-from module.utils import parseFileSize
-
-
-def checkFile(plugin, urls):
- html = getURL(plugin.URLS[1], post={"urls": "\n".join(urls)}, decode=True)
-
- file_info = []
- for li in re.finditer(plugin.LINKCHECK_TR, html, re.S):
- try:
- cols = re.findall(plugin.LINKCHECK_TD, li.group(1))
- if cols:
- file_info.append((
- cols[1] if cols[1] != '--' else cols[0],
- parseFileSize(cols[2]) if cols[2] != '--' else 0,
- 2 if cols[3].startswith('Available') else 1,
- cols[0]))
- except Exception, e:
- continue
-
- return file_info
-
-
-class FileserveCom(Hoster):
- __name__ = "FileserveCom"
- __type__ = "hoster"
- __version__ = "0.54"
-
- __pattern__ = r'http://(?:www\.)?fileserve\.com/file/(?P<ID>[^/]+)'
-
- __description__ = """Fileserve.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Paul King", None),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- URLS = ["http://www.fileserve.com/file/", "http://www.fileserve.com/link-checker.php",
- "http://www.fileserve.com/checkReCaptcha.php"]
- LINKCHECK_TR = r'<tr>\s*(<td>http://www\.fileserve\.com/file/.*?)</tr>'
- LINKCHECK_TD = r'<td>(?:<.*?>|&nbsp;)*([^<]*)'
-
- CAPTCHA_KEY_PATTERN = r'var reCAPTCHA_publickey=\'(.+?)\''
- LONG_WAIT_PATTERN = r'<li class="title">You need to wait (\d+) (\w+) to start another download\.</li>'
- LINK_EXPIRED_PATTERN = r'Your download link has expired'
- DAILY_LIMIT_PATTERN = r'Your daily download limit has been reached'
- NOT_LOGGED_IN_PATTERN = r'<form (name="loginDialogBoxForm"|id="login_form")|<li><a href="/login\.php">Login</a></li>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
- self.file_id = re.match(self.__pattern__, self.pyfile.url).group('ID')
- self.url = "%s%s" % (self.URLS[0], self.file_id)
-
- self.logDebug("File ID: %s URL: %s" % (self.file_id, self.url))
-
-
- def process(self, pyfile):
- pyfile.name, pyfile.size, status, self.url = checkFile(self, [self.url])[0]
- if status != 2:
- self.offline()
- self.logDebug("File Name: %s Size: %d" % (pyfile.name, pyfile.size))
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
-
- def handleFree(self):
- self.html = self.load(self.url)
- action = self.load(self.url, post={"checkDownload": "check"}, decode=True)
- action = json_loads(action)
- self.logDebug(action)
-
- if "fail" in action:
- if action['fail'] == "timeLimit":
- self.html = self.load(self.url, post={"checkDownload": "showError", "errorType": "timeLimit"},
- decode=True)
-
- self.doLongWait(re.search(self.LONG_WAIT_PATTERN, self.html))
-
- elif action['fail'] == "parallelDownload":
- self.logWarning(_("Parallel download error, now waiting 60s"))
- self.retry(wait_time=60, reason=_("parallelDownload"))
-
- else:
- self.fail(_("Download check returned: %s") % action['fail'])
-
- elif "success" in action:
- if action['success'] == "showCaptcha":
- self.doCaptcha()
- self.doTimmer()
- elif action['success'] == "showTimmer":
- self.doTimmer()
-
- else:
- self.error(_("Unknown server response"))
-
- # show download link
- res = self.load(self.url, post={"downloadLink": "show"}, decode=True)
- self.logDebug("Show downloadLink response: %s" % res)
- if "fail" in res:
- self.error(_("Couldn't retrieve download url"))
-
- # this may either download our file or forward us to an error page
- self.download(self.url, post={"download": "normal"})
- self.logDebug(self.req.http.lastEffectiveURL)
-
- check = self.checkDownload({"expired": self.LINK_EXPIRED_PATTERN,
- "wait" : re.compile(self.LONG_WAIT_PATTERN),
- "limit" : self.DAILY_LIMIT_PATTERN})
-
- if check == "expired":
- self.logDebug("Download link was expired")
- self.retry()
-
- elif check == "wait":
- self.doLongWait(self.lastCheck)
-
- elif check == "limit":
- self.logWarning(_("Download limited reached for today"))
- self.setWait(secondsToMidnight(gmt=2), True)
- self.wait()
- self.retry()
-
- self.thread.m.reconnecting.wait(3) # Ease issue with later downloads appearing to be in parallel
-
-
- def doTimmer(self):
- res = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
- self.logDebug("Wait response: %s" % res[:80])
-
- if "fail" in res:
- self.fail(_("Failed getting wait time"))
-
- if self.__name__ == "FilejungleCom":
- m = re.search(r'"waitTime":(\d+)', res)
- if m is None:
- self.fail(_("Cannot get wait time"))
- wait_time = int(m.group(1))
- else:
- wait_time = int(res) + 3
-
- self.setWait(wait_time)
- self.wait()
-
-
- def doCaptcha(self):
- captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(captcha_key)
- res = json_loads(self.load(self.URLS[2],
- post={'recaptcha_challenge_field' : challenge,
- 'recaptcha_response_field' : response,
- 'recaptcha_shortencode_field': self.file_id}))
- if not res['success']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("Invalid captcha"))
-
-
- def doLongWait(self, m):
- wait_time = (int(m.group(1)) * {'seconds': 1, 'minutes': 60, 'hours': 3600}[m.group(2)]) if m else 12 * 60
- self.setWait(wait_time, True)
- self.wait()
- self.retry()
-
-
- def handlePremium(self):
- premium_url = None
- if self.__name__ == "FileserveCom":
- #try api download
- res = self.load("http://app.fileserve.com/api/download/premium/",
- post={"username": self.user,
- "password": self.account.getAccountData(self.user)['password'],
- "shorten": self.file_id},
- decode=True)
- if res:
- res = json_loads(res)
- if res['error_code'] == "302":
- premium_url = res['next']
- elif res['error_code'] in ["305", "500"]:
- self.tempOffline()
- elif res['error_code'] in ["403", "605"]:
- self.resetAccount()
- elif res['error_code'] in ["606", "607", "608"]:
- self.offline()
- else:
- self.logError(res['error_code'], res['error_message'])
-
- self.download(premium_url or self.pyfile.url)
-
- if not premium_url and self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)}):
- self.account.relogin(self.user)
- self.retry(reason=_("Not logged in"))
-
-
-def getInfo(urls):
- for chunk in chunks(urls, 100):
- yield checkFile(FileserveCom, chunk)
diff --git a/module/plugins/hoster/FileshareInUa.py b/module/plugins/hoster/FileshareInUa.py
deleted file mode 100644
index c636375f5..000000000
--- a/module/plugins/hoster/FileshareInUa.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FileshareInUa(DeadHoster):
- __name__ = "FileshareInUa"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?fileshare\.in\.ua/\w{7}'
- __config__ = []
-
- __description__ = """Fileshare.in.ua hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fwannmacher", "felipe@warhammerproject.com")]
-
-
-getInfo = create_getInfo(FileshareInUa)
diff --git a/module/plugins/hoster/FilesonicCom.py b/module/plugins/hoster/FilesonicCom.py
deleted file mode 100644
index 16b4dee14..000000000
--- a/module/plugins/hoster/FilesonicCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilesonicCom(DeadHoster):
- __name__ = "FilesonicCom"
- __type__ = "hoster"
- __version__ = "0.35"
-
- __pattern__ = r'http://(?:www\.)?filesonic\.com/file/\w+'
- __config__ = []
-
- __description__ = """Filesonic.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("paulking", None)]
-
-
-getInfo = create_getInfo(FilesonicCom)
diff --git a/module/plugins/hoster/FilezyNet.py b/module/plugins/hoster/FilezyNet.py
deleted file mode 100644
index 2a6ba2722..000000000
--- a/module/plugins/hoster/FilezyNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilezyNet(DeadHoster):
- __name__ = "FilezyNet"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?filezy\.net/\w{12}'
- __config__ = []
-
- __description__ = """Filezy.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
-getInfo = create_getInfo(FilezyNet)
diff --git a/module/plugins/hoster/FiredriveCom.py b/module/plugins/hoster/FiredriveCom.py
deleted file mode 100644
index 612b9ceca..000000000
--- a/module/plugins/hoster/FiredriveCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FiredriveCom(DeadHoster):
- __name__ = "FiredriveCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Firedrive.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(FiredriveCom)
diff --git a/module/plugins/hoster/FlyFilesNet.py b/module/plugins/hoster/FlyFilesNet.py
deleted file mode 100644
index 689eb3c66..000000000
--- a/module/plugins/hoster/FlyFilesNet.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-class FlyFilesNet(SimpleHoster):
- __name__ = "FlyFilesNet"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?flyfiles\.net/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FlyFiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
- SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
- NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
-
-
- def process(self, pyfile):
- name = re.search(self.NAME_PATTERN, pyfile.url).group(1)
- pyfile.name = urllib.unquote_plus(name)
-
- session = re.search(self.SESSION_PATTERN, pyfile.url).group(1)
-
- url = "http://flyfiles.net"
-
- # get download URL
- parsed_url = getURL(url, post={"getDownLink": session})
- self.logDebug("Parsed URL: %s" % parsed_url)
-
- if parsed_url == '#downlink|' or parsed_url == "#downlink|#":
- self.logWarning(_("Could not get the download URL. Please wait 10 minutes"))
- self.wait(10 * 60, True)
- self.retry()
-
- self.link = parsed_url.replace('#downlink|', '')
diff --git a/module/plugins/hoster/FourSharedCom.py b/module/plugins/hoster/FourSharedCom.py
deleted file mode 100644
index 79eb1fb83..000000000
--- a/module/plugins/hoster/FourSharedCom.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FourSharedCom(SimpleHoster):
- __name__ = "FourSharedCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """4Shared.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
- SIZE_PATTERN = r'<span title="Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)">'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
-
- NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
- SIZE_REPLACEMENTS = [(",", "")]
-
- DIRECT_LINK = False
- LOGIN_ACCOUNT = True
-
- LINK_FREE_PATTERN = r'name="d3link" value="(.*?)"'
- LINK_BTN_PATTERN = r'id="btnLink" href="(.*?)"'
-
- ID_PATTERN = r'name="d3fid" value="(.*?)"'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_BTN_PATTERN, self.html)
- if m:
- link = m.group(1)
- else:
- link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', pyfile.url)
-
- self.html = self.load(link)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link"))
-
- self.link = m.group(1)
-
- try:
- m = re.search(self.ID_PATTERN, self.html)
- res = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
- self.logDebug(res)
- except Exception:
- pass
-
- self.wait(20)
-
-
-getInfo = create_getInfo(FourSharedCom)
diff --git a/module/plugins/hoster/FreakshareCom.py b/module/plugins/hoster/FreakshareCom.py
deleted file mode 100644
index 6a595e75b..000000000
--- a/module/plugins/hoster/FreakshareCom.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-
-
-class FreakshareCom(Hoster):
- __name__ = "FreakshareCom"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
-
- __description__ = """Freakshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sitacuisses", "sitacuisses@yahoo.de"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Toilal", "toilal.dev@gmail.com")]
-
-
- def setup(self):
- self.multiDL = False
- self.req_opts = []
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- pyfile.url = pyfile.url.replace("freakshare.net/", "freakshare.com/")
-
- if self.account:
- self.html = self.load(pyfile.url, cookies=False)
- pyfile.name = self.get_file_name()
- self.download(pyfile.url)
-
- else:
- self.prepare()
- self.get_file_url()
-
- self.download(pyfile.url, post=self.req_opts)
-
- check = self.checkDownload({"bad" : "bad try",
- "paralell" : "> Sorry, you cant download more then 1 files at time. <",
- "empty" : "Warning: Unknown: Filename cannot be empty",
- "wrong_captcha" : "Wrong Captcha!",
- "downloadserver": "No Downloadserver. Please try again later!"})
-
- if check == "bad":
- self.fail(_("Bad Try"))
-
- elif check == "paralell":
- self.setWait(300, True)
- self.wait()
- self.retry()
-
- elif check == "empty":
- self.fail(_("File not downloadable"))
-
- elif check == "wrong_captcha":
- self.invalidCaptcha()
- self.retry()
-
- elif check == "downloadserver":
- self.retry(5, 15 * 60, _("No Download server"))
-
-
- def prepare(self):
- pyfile = self.pyfile
-
- self.download_html()
-
- if not self.file_exists():
- self.offline()
-
- self.setWait(self.get_waiting_time())
-
- pyfile.name = self.get_file_name()
- pyfile.size = self.get_file_size()
-
- self.wait()
-
- return True
-
-
- def download_html(self):
- self.load("http://freakshare.com/index.php", {"language": "EN"}) # Set english language in server session
- self.html = self.load(self.pyfile.url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
- if not self.wantReconnect:
- self.req_opts = self.get_download_options() # get the Post options for the Request
- #file_url = self.pyfile.url
- #return file_url
- else:
- self.offline()
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- if not self.wantReconnect:
- m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
- if m:
- file_name = m.group(1)
- else:
- file_name = self.pyfile.url
-
- return file_name
- else:
- return self.pyfile.url
-
-
- def get_file_size(self):
- size = 0
- if not self.html:
- self.download_html()
-
- if not self.wantReconnect:
- m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
- if m:
- units = float(m.group(1).replace(",", ""))
- pow = {'KB': 1, 'MB': 2, 'GB': 3}[m.group(2)]
- size = int(units * 1024 ** pow)
-
- return size
-
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- if "Your Traffic is used up for today" in self.html:
- self.wantReconnect = True
- return secondsToMidnight(gmt=2)
-
- timestring = re.search('\s*var\s(?:downloadWait|time)\s=\s(\d*)[\d.]*;', self.html)
- if timestring:
- return int(timestring.group(1))
- else:
- return 60
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"This file does not exist!", self.html):
- return False
- else:
- return True
-
-
- def get_download_options(self):
- re_envelope = re.search(r".*?value=\"Free\sDownload\".*?\n*?(.*?<.*?>\n*)*?\n*\s*?</form>",
- self.html).group(0) # get the whole request
- to_sort = re.findall(r"<input\stype=\"hidden\"\svalue=\"(.*?)\"\sname=\"(.*?)\"\s\/>", re_envelope)
- request_options = dict((n, v) for (v, n) in to_sort)
-
- herewego = self.load(self.pyfile.url, None, request_options) # the actual download-Page
-
- to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
- request_options = dict((n, v) for (v, n) in to_sort)
-
- challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=(\w+)", herewego)
-
- if challenge:
- re_captcha = ReCaptcha(self)
- (request_options['recaptcha_challenge_field'],
- request_options['recaptcha_response_field']) = re_captcha.challenge(challenge.group(1))
-
- return request_options
diff --git a/module/plugins/hoster/FreeWayMe.py b/module/plugins/hoster/FreeWayMe.py
deleted file mode 100644
index 1cca24c3b..000000000
--- a/module/plugins/hoster/FreeWayMe.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class FreeWayMe(MultiHoster):
- __name__ = "FreeWayMe"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https://(?:www\.)?free-way\.me/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FreeWayMe multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def setup(self):
- self.resumeDownload = False
- self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- def handlePremium(self, pyfile):
- user, data = self.account.selectAccount()
-
- for _i in xrange(5):
- # try it five times
- header = self.load("https://www.free-way.me/load.php",
- get={'multiget': 7,
- 'url' : pyfile.url,
- 'user' : user,
- 'pw' : self.account.getAccountData(user)['password'],
- 'json' : ""},
- just_header=True)
-
- if 'location' in header:
- headers = self.load(header['location'], just_header=True)
- if headers['code'] == 500:
- # error on 2nd stage
- self.logError(_("Error [stage2]"))
- else:
- # seems to work..
- self.download(header['location'])
- break
- else:
- # error page first stage
- self.logError(_("Error [stage1]"))
-
- #@TODO: handle errors
-
-
-getInfo = create_getInfo(FreeWayMe)
diff --git a/module/plugins/hoster/FreevideoCz.py b/module/plugins/hoster/FreevideoCz.py
deleted file mode 100644
index f47c07c7e..000000000
--- a/module/plugins/hoster/FreevideoCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FreevideoCz(DeadHoster):
- __name__ = "FreevideoCz"
- __type__ = "hoster"
- __version__ = "0.30"
-
- __pattern__ = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
- __config__ = []
-
- __description__ = """Freevideo.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FreevideoCz) \ No newline at end of file
diff --git a/module/plugins/hoster/FshareVn.py b/module/plugins/hoster/FshareVn.py
deleted file mode 100644
index 50128db10..000000000
--- a/module/plugins/hoster/FshareVn.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- for url in urls:
- html = getURL("http://www.fshare.vn/check_link.php",
- post={'action': "check_link", 'arrlinks': url},
- decode=True)
-
- yield parseFileInfo(FshareVn, url, html)
-
-
-def doubleDecode(m):
- return m.group(1).decode('raw_unicode_escape')
-
-
-class FshareVn(SimpleHoster):
- __name__ = "FshareVn"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?fshare\.vn/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FshareVn hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)<\\/p>'
- OFFLINE_PATTERN = r'<div class=\\"f_left file_w\\"|<\\/p>\\t\\t\\t\\t\\r\\n\\t\\t<p><\\/p>\\t\\t\\r\\n\\t\\t<p>0 KB<\\/p>'
-
- NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
-
- LINK_FREE_PATTERN = r'action="(http://download.*?)[#"]'
- WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
-
-
- def preload(self):
- self.html = self.load("http://www.fshare.vn/check_link.php",
- post={'action': "check_link", 'arrlinks': pyfile.url},
- decode=True)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def handleFree(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- self.checkErrors()
-
- action, inputs = self.parseHtmlForm('frm_download')
- url = urlparse.urljoin(pyfile.url, action)
-
- if not inputs:
- self.error(_("No FORM"))
-
- elif 'link_file_pwd_dl' in inputs:
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + password)
- inputs['link_file_pwd_dl'] = password
- self.html = self.load(url, post=inputs, decode=True)
-
- if 'name="link_file_pwd_dl"' in self.html:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- else:
- self.html = self.load(url, post=inputs, decode=True)
-
- self.checkErrors()
-
- m = re.search(r'var count = (\d+)', self.html)
- self.setWait(int(m.group(1)) if m else 30)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
- self.wait()
-
-
- def checkErrors(self):
- if '/error.php?' in self.req.lastEffectiveURL or u"Liên kết bạn chọn khÃŽng tồn" in self.html:
- self.offline()
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logInfo(_("Wait until %s ICT") % m.group(1))
- wait_until = time.mktime.time(time.strptime.time(m.group(1), "%d/%m/%Y %H:%M"))
- self.wait(wait_until - time.mktime.time(time.gmtime.time()) - 7 * 60 * 60, True)
- self.retry()
- elif '<ul class="message-error">' in self.html:
- msg = "Unknown error occured or wait time not parsed"
- self.logError(msg)
- self.retry(30, 2 * 60, msg)
-
- self.info.pop('error', None)
diff --git a/module/plugins/hoster/Ftp.py b/module/plugins/hoster/Ftp.py
deleted file mode 100644
index 5258a1c27..000000000
--- a/module/plugins/hoster/Ftp.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import urllib
-import urlparse
-
-from module.plugins.Hoster import Hoster
-
-
-class Ftp(Hoster):
- __name__ = "Ftp"
- __type__ = "hoster"
- __version__ = "0.51"
-
- __pattern__ = r'(?:ftps?|sftp)://([\w.-]+(:[\w.-]+)?@)?[\w.-]+(:\d+)?/.+'
-
- __description__ = """Download from ftp directory"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.com"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
-
- def process(self, pyfile):
- parsed_url = urlparse.urlparse(pyfile.url)
- netloc = parsed_url.netloc
-
- pyfile.name = parsed_url.path.rpartition('/')[2]
- try:
- pyfile.name = urllib.unquote(str(pyfile.name)).decode('utf8')
- except Exception:
- pass
-
- if not "@" in netloc:
- servers = [x['login'] for x in self.account.getAllAccounts()] if self.account else []
-
- if netloc in servers:
- self.logDebug("Logging on to %s" % netloc)
- self.req.addAuth(self.account.getAccountInfo(netloc)['password'])
- else:
- pwd = self.getPassword()
- if ':' in pwd:
- self.req.addAuth(pwd)
-
- self.req.http.c.setopt(pycurl.NOBODY, 1)
-
- try:
- res = self.load(pyfile.url)
- except pycurl.error, e:
- self.fail(_("Error %d: %s") % e.args)
-
- self.req.http.c.setopt(pycurl.NOBODY, 0)
- self.logDebug(self.req.http.header)
-
- m = re.search(r"Content-Length:\s*(\d+)", res)
- if m:
- pyfile.size = int(m.group(1))
- self.download(pyfile.url)
- else:
- #Naive ftp directory listing
- if re.search(r'^25\d.*?"', self.req.http.header, re.M):
- pyfile.url = pyfile.url.rstrip('/')
- pkgname = "/".join(pyfile.package().name, urlparse.urlparse(pyfile.url).path.rpartition('/')[2])
- pyfile.url += '/'
- self.req.http.c.setopt(48, 1) # CURLOPT_DIRLISTONLY
- res = self.load(pyfile.url, decode=False)
- links = [pyfile.url + urllib.quote(x) for x in res.splitlines()]
- self.logDebug("LINKS", links)
- self.core.api.addPackage(pkgname, links)
- else:
- self.fail(_("Unexpected server response"))
diff --git a/module/plugins/hoster/GamefrontCom.py b/module/plugins/hoster/GamefrontCom.py
deleted file mode 100644
index c68866f87..000000000
--- a/module/plugins/hoster/GamefrontCom.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class GamefrontCom(Hoster):
- __name__ = "GamefrontCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?gamefront\.com/files/\w+'
-
- __description__ = """Gamefront.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fwannmacher", "felipe@warhammerproject.com")]
-
-
- PATTERN_FILENAME = r'<title>(.*?) | Game Front'
- PATTERN_FILESIZE = r'<dt>File Size:</dt>[\n\s]*<dd>(.*?)</dd>'
- PATTERN_OFFLINE = r'This file doesn\'t exist, or has been removed.'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.html = self.load(pyfile.url, decode=True)
-
- if not self._checkOnline():
- self.offline()
-
- pyfile.name = self._getName()
-
- link = self._getLink()
-
- if not link.startswith('http://'):
- link = "http://www.gamefront.com/" + link
-
- self.download(link)
-
-
- def _checkOnline(self):
- if re.search(self.PATTERN_OFFLINE, self.html):
- return False
- else:
- return True
-
-
- def _getName(self):
- name = re.search(self.PATTERN_FILENAME, self.html)
- if name is None:
- self.fail(_("Plugin broken")
-
- return name.group(1)
-
-
- def _getLink(self):
- self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=\w+)",
- self.html).group(1))
- return re.search("<a href=\"(http://media\d+\.gamefront.com/.*)\">click here</a>", self.html2).group(1).replace("&amp;", "&")
-
-
-def getInfo(urls):
- result = []
-
- for url in urls:
- html = getURL(url)
-
- if re.search(GamefrontCom.PATTERN_OFFLINE, html):
- result.append((url, 0, 1, url))
- else:
- name = re.search(GamefrontCom.PATTERN_FILENAME, html)
- if name is None:
- result.append((url, 0, 1, url))
- else:
- name = name.group(1)
- size = re.search(GamefrontCom.PATTERN_FILESIZE, html)
- size = parseFileSize(size.group(1))
-
- result.append((name, size, 3, url))
-
- yield result
diff --git a/module/plugins/hoster/GigapetaCom.py b/module/plugins/hoster/GigapetaCom.py
deleted file mode 100644
index e4d0601fc..000000000
--- a/module/plugins/hoster/GigapetaCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class GigapetaCom(SimpleHoster):
- __name__ = "GigapetaCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """GigaPeta.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
- SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
- OFFLINE_PATTERN = r'<div id="page_error">'
-
- COOKIES = [("gigapeta.com", "lang", "us")]
-
-
- def handleFree(self, pyfile):
- captcha_key = str(random.randint(1, 100000000))
- captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- for _i in xrange(5):
- self.checkErrors()
-
- captcha = self.decryptCaptcha(captcha_url)
- self.html = self.load(pyfile.url, post={
- "captcha_key": captcha_key,
- "captcha": captcha,
- "download": "Download"})
-
- m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
- if m:
- self.link = m.group(1).rstrip() #@TODO: Remove .rstrip() in 0.4.10
- break
- elif "Entered figures don&#96;t coincide with the picture" in self.html:
- self.invalidCaptcha()
- else:
- self.fail(_("No valid captcha code entered"))
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-
- def checkErrors(self):
- if "All threads for IP" in self.html:
- self.logDebug("Your IP is already downloading a file")
- self.wait(5 * 60, True)
- self.retry()
-
- self.info.pop('error', None)
-
-
-getInfo = create_getInfo(GigapetaCom)
diff --git a/module/plugins/hoster/GooIm.py b/module/plugins/hoster/GooIm.py
deleted file mode 100644
index 4b27e6cc8..000000000
--- a/module/plugins/hoster/GooIm.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class GooIm(SimpleHoster):
- __name__ = "GooIm"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?goo\.im/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Goo.im hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'You will be redirected to .*(?P<N>[^/ ]+) in'
- OFFLINE_PATTERN = r'The file you requested was not found'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.wait(10)
- self.link = pyfile.url
-
-
-getInfo = create_getInfo(GooIm)
diff --git a/module/plugins/hoster/GoogledriveCom.py b/module/plugins/hoster/GoogledriveCom.py
deleted file mode 100644
index 66f36e6c0..000000000
--- a/module/plugins/hoster/GoogledriveCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://drive.google.com/file/d/0B6RNTe4ygItBQm15RnJiTmMyckU/view?pli=1
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import html_unescape
-
-
-class GoogledriveCom(SimpleHoster):
- __name__ = "GoogledriveCom"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'https?://(?:www\.)?drive\.google\.com/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Drive.google.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'"og:title" content="(?P<N>.*?)">'
- OFFLINE_PATTERN = r'align="center"><p class="errorMessage"'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- try:
- link1 = re.search(r'"(https://docs.google.com/uc\?id.*?export=download)",',
- self.html.decode('unicode-escape')).group(1)
-
- except AttributeError:
- self.error(_("Hop #1 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link1)
-
- self.html = self.load(link1).decode('unicode-escape')
-
- try:
- link2 = html_unescape(re.search(r'href="(/uc\?export=download.*?)">',
- self.html).group(1))
-
- except AttributeError:
- self.error(_("Hop #2 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link2)
-
- link3 = self.load("https://docs.google.com" + link2, just_header=True)
- self.logDebug("DL-Link: %s" % link3['location'])
-
- self.link = link3['location']
-
-
-getInfo = create_getInfo(GoogledriveCom)
diff --git a/module/plugins/hoster/HellshareCz.py b/module/plugins/hoster/HellshareCz.py
deleted file mode 100644
index ac0043b37..000000000
--- a/module/plugins/hoster/HellshareCz.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class HellshareCz(SimpleHoster):
- __name__ = "HellshareCz"
- __type__ = "hoster"
- __version__ = "0.85"
-
- __pattern__ = r'http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Hellshare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- CHECK_TRAFFIC = True
- LOGIN_ACCOUNT = True
-
- NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
- SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
- OFFLINE_PATTERN = r'<h1>File not found.</h1>'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = bool(self.account)
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(HellshareCz)
diff --git a/module/plugins/hoster/HellspyCz.py b/module/plugins/hoster/HellspyCz.py
deleted file mode 100644
index 49fb9e829..000000000
--- a/module/plugins/hoster/HellspyCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class HellspyCz(DeadHoster):
- __name__ = "HellspyCz"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj\.pl)(/\S+/\d+)'
- __config__ = []
-
- __description__ = """HellSpy.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(HellspyCz)
diff --git a/module/plugins/hoster/HostujeNet.py b/module/plugins/hoster/HostujeNet.py
deleted file mode 100644
index ec91e50b9..000000000
--- a/module/plugins/hoster/HostujeNet.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class HostujeNet(SimpleHoster):
- __name__ = "HostujeNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?hostuje\.net/\w+'
-
- __description__ = """Hostuje.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GammaC0de", None)]
-
-
- NAME_PATTERN = r'<input type="hidden" name="name" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'<b>Rozmiar:</b> (?P<S>[\d.,]+) (?P<U>[\w^_]+)<br>'
- OFFLINE_PATTERN = ur'Podany plik nie został odnaleziony\.\.\.'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(r'<script src="([\w^_]+.php)"></script>', self.html)
- if m:
- jscript = self.load("http://hostuje.net/" + m.group(1))
- m = re.search(r"\('(\w+\.php\?i=\w+)'\);", jscript)
- if m:
- self.load("http://hostuje.net/" + m.group(1))
- else:
- self.error(_("unexpected javascript format"))
- else:
- self.error(_("script not found"))
-
- action, inputs = self.parseHtmlForm(pyfile.url.replace(".", "\.").replace( "?", "\?"))
- if not action:
- self.error(_("form not found"))
-
- self.download(action, post=inputs)
-
-
-getInfo = create_getInfo(HostujeNet)
diff --git a/module/plugins/hoster/HotfileCom.py b/module/plugins/hoster/HotfileCom.py
deleted file mode 100644
index 6c6a4fa89..000000000
--- a/module/plugins/hoster/HotfileCom.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class HotfileCom(DeadHoster):
- __name__ = "HotfileCom"
- __type__ = "hoster"
- __version__ = "0.37"
-
- __pattern__ = r'https?://(?:www\.)?hotfile\.com/dl/\d+/\w+'
- __config__ = []
-
- __description__ = """Hotfile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sitacuisses", "sitacuisses@yhoo.de"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("JoKoT3", "jokot3@gmail.com")]
-
-
-getInfo = create_getInfo(HotfileCom)
diff --git a/module/plugins/hoster/HugefilesNet.py b/module/plugins/hoster/HugefilesNet.py
deleted file mode 100644
index 3fdcca1ba..000000000
--- a/module/plugins/hoster/HugefilesNet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class HugefilesNet(XFSHoster):
- __name__ = "HugefilesNet"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?hugefiles\.net/\w{12}'
-
- __description__ = """Hugefiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- SIZE_PATTERN = r'File Size:</span>\s*<span.*?>(?P<S>[^<]+)</span></div>'
-
- FORM_INPUTS_MAP = {'ctype': re.compile(r'\d+')}
-
-
-getInfo = create_getInfo(HugefilesNet)
diff --git a/module/plugins/hoster/HundredEightyUploadCom.py b/module/plugins/hoster/HundredEightyUploadCom.py
deleted file mode 100644
index 2a35a008f..000000000
--- a/module/plugins/hoster/HundredEightyUploadCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class HundredEightyUploadCom(XFSHoster):
- __name__ = "HundredEightyUploadCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?180upload\.com/\w{12}'
-
- __description__ = """180upload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- OFFLINE_PATTERN = r'>File Not Found'
-
-
-getInfo = create_getInfo(HundredEightyUploadCom)
diff --git a/module/plugins/hoster/IFileWs.py b/module/plugins/hoster/IFileWs.py
deleted file mode 100644
index 7d2a76a60..000000000
--- a/module/plugins/hoster/IFileWs.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IFileWs(DeadHoster):
- __name__ = "IFileWs"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?ifile\.ws/\w{12}'
- __config__ = []
-
- __description__ = """Ifile.ws hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
-getInfo = create_getInfo(IFileWs)
diff --git a/module/plugins/hoster/IcyFilesCom.py b/module/plugins/hoster/IcyFilesCom.py
deleted file mode 100644
index ca0f6441b..000000000
--- a/module/plugins/hoster/IcyFilesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IcyFilesCom(DeadHoster):
- __name__ = "IcyFilesCom"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?icyfiles\.com/(.+)'
- __config__ = []
-
- __description__ = """IcyFiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
-getInfo = create_getInfo(IcyFilesCom)
diff --git a/module/plugins/hoster/IfileIt.py b/module/plugins/hoster/IfileIt.py
deleted file mode 100644
index f13065f86..000000000
--- a/module/plugins/hoster/IfileIt.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IfileIt(DeadHoster):
- __name__ = "IfileIt"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'^unmatchable$'
- __config__ = []
-
- __description__ = """Ifile.it"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(IfileIt)
diff --git a/module/plugins/hoster/IfolderRu.py b/module/plugins/hoster/IfolderRu.py
deleted file mode 100644
index 0f09731e4..000000000
--- a/module/plugins/hoster/IfolderRu.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class IfolderRu(SimpleHoster):
- __name__ = "IfolderRu"
- __type__ = "hoster"
- __version__ = "0.39"
-
- __pattern__ = r'http://(?:www)?(files\.)?(ifolder\.ru|metalarea\.org|rusfolder\.(com|net|ru))/(files/)?(?P<ID>\d+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Ifolder.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
-
- NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
- SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
- OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>.*?</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
-
- SESSION_ID_PATTERN = r'<input type="hidden" name="session" value="(.+?)"'
- INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "(.+?)";\}'
- HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\''
-
- LINK_FREE_PATTERN = r'<a href="(.+?)" class="downloadbutton_files"'
-
- WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = bool(self.account)
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- url = "http://rusfolder.com/%s" % self.info['pattern']['ID']
- self.html = self.load("http://rusfolder.com/%s" % self.info['pattern']['ID'], decode=True)
- self.getFileInfo()
-
- session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
-
- captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
- for _i in xrange(5):
- action, inputs = self.parseHtmlForm('id="download-step-one-form"')
- inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
- inputs['action'] = '1'
- self.logDebug(inputs)
-
- self.html = self.load(url, decode=True, post=inputs)
- if self.WRONG_CAPTCHA_PATTERN in self.html:
- self.invalidCaptcha()
- else:
- break
- else:
- self.fail(_("Invalid captcha"))
-
- self.link = re.search(self.LINK_FREE_PATTERN, self.html).group(1)
-
-
-getInfo = create_getInfo(IfolderRu)
-
diff --git a/module/plugins/hoster/JumbofilesCom.py b/module/plugins/hoster/JumbofilesCom.py
deleted file mode 100644
index a9bf14c6d..000000000
--- a/module/plugins/hoster/JumbofilesCom.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class JumbofilesCom(SimpleHoster):
- __name__ = "JumbofilesCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?jumbofiles\.com/(?P<ID>\w{12})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """JumboFiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
- INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
- LINK_FREE_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- post_data = {"id": self.info['pattern']['ID'], "op": "download3", "rand": ""}
- html = self.load(self.pyfile.url, post=post_data, decode=True)
- self.link = re.search(self.LINK_FREE_PATTERN, html).group(1)
-
-
-getInfo = create_getInfo(JumbofilesCom)
diff --git a/module/plugins/hoster/JunocloudMe.py b/module/plugins/hoster/JunocloudMe.py
deleted file mode 100644
index 415d5e2d0..000000000
--- a/module/plugins/hoster/JunocloudMe.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class JunocloudMe(XFSHoster):
- __name__ = "JunocloudMe"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:\w+\.)?junocloud\.me/\w{12}'
-
- __description__ = """Junocloud.me hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")]
-
- OFFLINE_PATTERN = r'>No such file with this filename<'
- TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
-
-
-getInfo = create_getInfo(JunocloudMe)
diff --git a/module/plugins/hoster/Keep2ShareCc.py b/module/plugins/hoster/Keep2ShareCc.py
deleted file mode 100644
index fdae65096..000000000
--- a/module/plugins/hoster/Keep2ShareCc.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class Keep2ShareCc(SimpleHoster):
- __name__ = "Keep2ShareCc"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Keep2Share.cc hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://keep2s.cc/file/\g<ID>")]
-
- NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
- SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
-
- OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
- TEMP_OFFLINE_PATTERN = r'Downloading blocked due to'
-
- LINK_FREE_PATTERN = r'"(.+?url.html\?file=.+?)"|window\.location\.href = \'(.+?)\';'
- LINK_PREMIUM_PATTERN = r'window\.location\.href = \'(.+?)\';'
-
- CAPTCHA_PATTERN = r'src="(/file/captcha\.html.+?)"'
-
- WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
- TEMP_ERROR_PATTERN = r'>\s*(Download count files exceed|Traffic limit exceed|Free account does not allow to download more than one file at the same time)'
- ERROR_PATTERN = r'>\s*(Free user can\'t download large files|You no can access to this file|This download available only for premium users|This is private file)'
-
-
- def checkErrors(self):
- m = re.search(self.TEMP_ERROR_PATTERN, self.html)
- if m:
- self.info['error'] = m.group(1)
- self.wantReconnect = True
- self.retry(wait_time=30 * 60, reason=m.group(0))
-
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = m.group(1)
- self.error(errmsg)
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logDebug("Hoster told us to wait for %s" % m.group(1))
-
- # string to time convert courtesy of https://stackoverflow.com/questions/10663720
- ftr = [3600, 60, 1]
- wait_time = sum(a * b for a, b in zip(ftr, map(int, m.group(1).split(':'))))
-
- self.wantReconnect = True
- self.retry(wait_time=wait_time, reason="Please wait to download this file")
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- self.fid = re.search(r'<input type="hidden" name="slow_id" value="(.+?)">', self.html).group(1)
- self.html = self.load(pyfile.url, post={'yt0': '', 'slow_id': self.fid})
-
- # self.logDebug(self.fid)
- # self.logDebug(pyfile.url)
-
- self.checkErrors()
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.handleCaptcha()
- self.wait(31)
- self.html = self.load(pyfile.url)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
-
- def handleCaptcha(self):
- post_data = {'free' : 1,
- 'freeDownloadRequest': 1,
- 'uniqueId' : self.fid,
- 'yt0' : ''}
-
- m = re.search(r'id="(captcha\-form)"', self.html)
- self.logDebug("captcha-form found %s" % m)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- self.logDebug("CAPTCHA_PATTERN found %s" % m)
- if m:
- captcha_url = urlparse.urljoin("http://keep2s.cc/", m.group(1))
- post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url)
- else:
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
- post_data.update({'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- self.html = self.load(self.pyfile.url, post=post_data)
-
- if 'verification code is incorrect' not in self.html:
- self.correctCaptcha()
- else:
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(Keep2ShareCc)
diff --git a/module/plugins/hoster/KickloadCom.py b/module/plugins/hoster/KickloadCom.py
deleted file mode 100644
index df43b0fca..000000000
--- a/module/plugins/hoster/KickloadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class KickloadCom(DeadHoster):
- __name__ = "KickloadCom"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?kickload\.com/get/.+'
- __config__ = []
-
- __description__ = """Kickload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(KickloadCom)
diff --git a/module/plugins/hoster/KingfilesNet.py b/module/plugins/hoster/KingfilesNet.py
deleted file mode 100644
index 99f309d00..000000000
--- a/module/plugins/hoster/KingfilesNet.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class KingfilesNet(SimpleHoster):
- __name__ = "KingfilesNet"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Kingfiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'name="fname" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'>Size: .+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>(File Not Found</b><br><br>|File Not Found</h2>)'
-
- RAND_ID_PATTERN = r'type=\"hidden\" name=\"rand\" value=\"(.+)\">'
-
- LINK_FREE_PATTERN = r'var download_url = \'(.+)\';'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- # Click the free user button
- post_data = {'op' : "download1",
- 'usr_login' : "",
- 'id' : self.info['pattern']['ID'],
- 'fname' : pyfile.name,
- 'referer' : "",
- 'method_free': "+"}
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- solvemedia = SolveMedia(self)
- response, challenge = solvemedia.challenge()
-
- # Make the downloadlink appear and load the file
- m = re.search(self.RAND_ID_PATTERN, self.html)
- if m is None:
- self.error(_("Random key not found"))
-
- rand = m.group(1)
- self.logDebug("rand = ", rand)
-
- post_data = {'op' : "download2",
- 'id' : self.info['pattern']['ID'],
- 'rand' : rand,
- 'referer' : pyfile.url,
- 'method_free' : "+",
- 'method_premium' : "",
- 'adcopy_response' : response,
- 'adcopy_challenge': challenge,
- 'down_direct' : "1"}
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download url not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(KingfilesNet)
diff --git a/module/plugins/hoster/LemUploadsCom.py b/module/plugins/hoster/LemUploadsCom.py
deleted file mode 100644
index c7aae3bf9..000000000
--- a/module/plugins/hoster/LemUploadsCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class LemUploadsCom(DeadHoster):
- __name__ = "LemUploadsCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?lemuploads\.com/\w{12}'
- __config__ = []
-
- __description__ = """LemUploads.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(LemUploadsCom)
diff --git a/module/plugins/hoster/LetitbitNet.py b/module/plugins/hoster/LetitbitNet.py
deleted file mode 100644
index 40d792e11..000000000
--- a/module/plugins/hoster/LetitbitNet.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# API Documentation:
-# http://api.letitbit.net/reg/static/api.pdf
-#
-# Test links:
-# http://letitbit.net/download/07874.0b5709a7d3beee2408bb1f2eefce/random.bin.html
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads, json_dumps
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, secondsToMidnight
-
-
-def api_response(url):
- json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
- api_rep = getURL("http://api.letitbit.net/json",
- post={'r': json_dumps(json_data)})
- return json_loads(api_rep)
-
-
-def getInfo(urls):
- for url in urls:
- api_rep = api_response(url)
- if api_rep['status'] == 'OK':
- info = api_rep['data'][0]
- yield (info['name'], info['size'], 2, url)
- else:
- yield (url, 0, 1, url)
-
-
-class LetitbitNet(SimpleHoster):
- __name__ = "LetitbitNet"
- __type__ = "hoster"
- __version__ = "0.30"
-
- __pattern__ = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Letitbit.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("z00nx", "z00nx0@gmail.com")]
-
-
- URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
-
- SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
- CAPTCHA_CONTROL_FIELD = r'recaptcha_control_field\s=\s\'(.+?)\''
-
-
- def setup(self):
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('id="ifree_form"')
- if not action:
- self.error(_("ifree_form"))
-
- pyfile.size = float(inputs['sssize'])
- self.logDebug(action, inputs)
- inputs['desc'] = ""
-
- self.html = self.load(urlparse.urljoin("http://letitbit.net/", action), post=inputs)
-
- m = re.search(self.SECONDS_PATTERN, self.html)
- seconds = int(m.group(1)) if m else 60
-
- self.logDebug("Seconds found", seconds)
-
- m = re.search(self.CAPTCHA_CONTROL_FIELD, self.html)
- recaptcha_control_field = m.group(1)
-
- self.logDebug("ReCaptcha control field found", recaptcha_control_field)
-
- self.wait(seconds)
-
- res = self.load("http://letitbit.net/ajax/download3.php", post=" ")
- if res != '1':
- self.error(_("Unknown response - ajax_check_url"))
-
- self.logDebug(res)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- post_data = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field": response,
- "recaptcha_control_field": recaptcha_control_field}
-
- self.logDebug("Post data to send", post_data)
-
- res = self.load("http://letitbit.net/ajax/check_recaptcha.php", post=post_data)
-
- self.logDebug(res)
-
- if not res:
- self.invalidCaptcha()
-
- if res == "error_free_download_blocked":
- self.logWarning(_("Daily limit reached"))
- self.wait(secondsToMidnight(gmt=2), True)
-
- if res == "error_wrong_captcha":
- self.invalidCaptcha()
- self.retry()
-
- elif res.startswith('['):
- urls = json_loads(res)
-
- elif res.startswith('http://'):
- urls = [res]
-
- else:
- self.error(_("Unknown response - captcha check"))
-
- self.link = urls[0]
-
-
- def handlePremium(self, pyfile):
- api_key = self.user
- premium_key = self.account.getAccountData(self.user)['password']
-
- json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": pyfile.url}]]
- api_rep = self.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
- self.logDebug("API Data: " + api_rep)
- api_rep = json_loads(api_rep)
-
- if api_rep['status'] == 'FAIL':
- self.fail(api_rep['data'])
-
- self.link = api_rep['data'][0][0]
diff --git a/module/plugins/hoster/LinksnappyCom.py b/module/plugins/hoster/LinksnappyCom.py
deleted file mode 100644
index 9c3af4ae3..000000000
--- a/module/plugins/hoster/LinksnappyCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class LinksnappyCom(MultiHoster):
- __name__ = "LinksnappyCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:[^/]+\.)?linksnappy\.com'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Linksnappy.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- SINGLE_CHUNK_HOSTERS = ["easybytez.com"]
-
-
- def handlePremium(self, pyfile):
- host = self._get_host(pyfile.url)
- json_params = json_dumps({'link' : pyfile.url,
- 'type' : host,
- 'username': self.user,
- 'password': self.account.getAccountData(self.user)['password']})
-
- r = self.load("http://gen.linksnappy.com/genAPI.php",
- post={'genLinks': json_params})
-
- self.logDebug("JSON data: " + r)
-
- j = json_loads(r)['links'][0]
-
- if j['error']:
- self.error(_("Error converting the link"))
-
- pyfile.name = j['filename']
- self.link = j['generated']
-
- if host in self.SINGLE_CHUNK_HOSTERS:
- self.chunkLimit = 1
- else:
- self.setup()
-
-
- @staticmethod
- def _get_host(url):
- host = urlparse.urlsplit(url).netloc
- return re.search(r'[\w-]+\.\w+$', host).group(0)
-
-
-getInfo = create_getInfo(LinksnappyCom)
diff --git a/module/plugins/hoster/LoadTo.py b/module/plugins/hoster/LoadTo.py
deleted file mode 100644
index 2b4202051..000000000
--- a/module/plugins/hoster/LoadTo.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.load.to/JWydcofUY6/random.bin
-# http://www.load.to/oeSmrfkXE/random100.bin
-
-import re
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LoadTo(SimpleHoster):
- __name__ = "LoadTo"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?load\.to/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """ Load.to hoster plugin """
- __license__ = "GPLv3"
- __authors__ = [("halfman", "Pulpan3@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
- SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>Can\'t find file'
-
- LINK_FREE_PATTERN = r'<form method="post" action="(.+?)"'
- WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
-
- URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- # Search for Download URL
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
-
- # Set Timer - may be obsolete
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
- # Load.to is using solvemedia captchas since ~july 2014:
- solvemedia = SolveMedia(self)
- captcha_key = solvemedia.detect_key()
-
- if captcha_key:
- response, challenge = solvemedia.challenge(captcha_key)
- self.download(self.link,
- post={'adcopy_challenge': challenge,
- 'adcopy_response' : response,
- 'returnUrl' : pyfile.url})
-
-
-getInfo = create_getInfo(LoadTo)
diff --git a/module/plugins/hoster/LolabitsEs.py b/module/plugins/hoster/LolabitsEs.py
deleted file mode 100644
index 61df5f0bb..000000000
--- a/module/plugins/hoster/LolabitsEs.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*
-
-import HTMLParser
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LolabitsEs(SimpleHoster):
- __name__ = "LolabitsEs"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?lolabits\.es/.+'
-
- __description__ = """Lolabits.es hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'Descargar: <b>(?P<N>.+?)<'
- SIZE_PATTERN = r'class="fileSize">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'Un usuario con este nombre no existe'
-
- FILEID_PATTERN = r'name="FileId" value="(\d+)"'
- TOKEN_PATTERN = r'name="__RequestVerificationToken" type="hidden" value="(.+?)"'
- LINK_PATTERN = r'"redirectUrl":"(.+?)"'
-
-
- def setup(self):
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- fileid = re.search(self.FILEID_PATTERN, self.html).group(1)
- self.logDebug("FileID: " + fileid)
-
- token = re.search(self.TOKEN_PATTERN, self.html).group(1)
- self.logDebug("Token: " + token)
-
- self.html = self.load("http://lolabits.es/action/License/Download",
- post={'fileId' : fileid,
- '__RequestVerificationToken' : token}).decode('unicode-escape')
-
- self.link = HTMLParser.HTMLParser().unescape(re.search(self.LINK_PATTERN, self.html).group(1))
-
-
-getInfo = create_getInfo(LolabitsEs)
diff --git a/module/plugins/hoster/LomafileCom.py b/module/plugins/hoster/LomafileCom.py
deleted file mode 100644
index 4dd092f45..000000000
--- a/module/plugins/hoster/LomafileCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class LomafileCom(DeadHoster):
- __name__ = "LomafileCom"
- __type__ = "hoster"
- __version__ = "0.52"
-
- __pattern__ = r'http://lomafile\.com/\w{12}'
- __config__ = []
-
- __description__ = """Lomafile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
-getInfo = create_getInfo(LomafileCom)
diff --git a/module/plugins/hoster/LuckyShareNet.py b/module/plugins/hoster/LuckyShareNet.py
deleted file mode 100644
index 26af8153f..000000000
--- a/module/plugins/hoster/LuckyShareNet.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from bottle import json_loads
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LuckyShareNet(SimpleHoster):
- __name__ = "LuckyShareNet"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """LuckyShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- INFO_PATTERN = r'<h1 class=\'file_name\'>(?P<N>\S+)</h1>\s*<span class=\'file_size\'>Filesize: (?P<S>[\d.,]+)(?P<U>[\w^_]+)</span>'
- OFFLINE_PATTERN = r'There is no such file available'
-
-
- def parseJson(self, rep):
- if 'AJAX Error' in rep:
- html = self.load(self.pyfile.url, decode=True)
- m = re.search(r"waitingtime = (\d+);", html)
- if m:
- seconds = int(m.group(1))
- self.logDebug("You have to wait %d seconds between free downloads" % seconds)
- self.retry(wait_time=seconds)
- else:
- self.error(_("Unable to detect wait time between free downloads"))
- elif 'Hash expired' in rep:
- self.retry(reason=_("Hash expired"))
- return json_loads(rep)
-
-
- # TODO: There should be a filesize limit for free downloads
- # TODO: Some files could not be downloaded in free mode
- def handleFree(self, pyfile):
- rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + self.info['pattern']['ID'], decode=True)
-
- self.logDebug("JSON: " + rep)
-
- json = self.parseJson(rep)
- self.wait(json['time'])
-
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge()
- rep = self.load(r"http://luckyshare.net/download/verify/challenge/%s/response/%s/hash/%s" %
- (challenge, response, json['hash']), decode=True)
- self.logDebug("JSON: " + rep)
- if 'link' in rep:
- json.update(self.parseJson(rep))
- self.correctCaptcha()
- break
- elif 'Verification failed' in rep:
- self.invalidCaptcha()
- else:
- self.error(_("Unable to get downlaod link"))
-
- if not json['link']:
- self.fail(_("No Download url retrieved/all captcha attempts failed"))
-
- self.link = json['link']
-
-
-getInfo = create_getInfo(LuckyShareNet)
diff --git a/module/plugins/hoster/MediafireCom.py b/module/plugins/hoster/MediafireCom.py
deleted file mode 100644
index c022918ae..000000000
--- a/module/plugins/hoster/MediafireCom.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MediafireCom(SimpleHoster):
- __name__ = "MediafireCom"
- __type__ = "hoster"
- __version__ = "0.86"
-
- __pattern__ = r'https?://(?:www\.)?mediafire\.com/(file/|view/\??|download(\.php\?|/)|\?)\w{15}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Mediafire.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>.+?)"/>'
- SIZE_PATTERN = r'<li>File size: <span>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- INFO_PATTERN = r'oFileSharePopup\.ald\(\'.*?\',\'(?P<N>.+?)\',\'(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\',\'\',\'(?P<H>.+?)\'\)'
- OFFLINE_PATTERN = r'class="error_msg_title"'
-
- LINK_FREE_PATTERN = r'kNO = "(.+?)"'
-
- PASSWORD_PATTERN = r'<form name="form_password"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- solvemedia = SolveMedia(self)
- captcha_key = solvemedia.detect_key()
-
- if captcha_key:
- response, challenge = solvemedia.challenge(captcha_key)
- self.html = self.load(pyfile.url,
- post={'adcopy_challenge': challenge,
- 'adcopy_response' : response},
- decode=True)
-
- if self.PASSWORD_PATTERN in self.html:
- password = self.getPassword()
-
- if not password:
- self.fail(_("No password found"))
- else:
- self.logInfo(_("Password protected link, trying: ") + password)
- self.html = self.load(self.link, post={'downloadp': password})
-
- if self.PASSWORD_PATTERN in self.html:
- self.fail(_("Incorrect password"))
-
- return super(MediafireCom, self).handleFree(pyfile)
-
-
-getInfo = create_getInfo(MediafireCom)
diff --git a/module/plugins/hoster/MegaCoNz.py b/module/plugins/hoster/MegaCoNz.py
deleted file mode 100644
index aa7755af4..000000000
--- a/module/plugins/hoster/MegaCoNz.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import array
-import os
-# import pycurl
-import random
-import re
-
-from base64 import standard_b64decode
-
-from Crypto.Cipher import AES
-from Crypto.Util import Counter
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.Hoster import Hoster
-from module.utils import decode, fs_decode, fs_encode
-
-
-############################ General errors ###################################
-# EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred
-# EARGS (-2): You have passed invalid arguments to this command
-# EAGAIN (-3): (always at the request level) A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff
-# ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)
-#
-############################ Upload errors ####################################
-# EFAILED (-5): The upload failed. Please restart it from scratch
-# ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL
-# ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary
-# EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one
-#
-############################ Stream/System errors #############################
-# ENOENT (-9): Object (typically, node or user) not found
-# ECIRCULAR (-10): Circular linkage attempted
-# EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
-# EEXIST (-12): Trying to create an object that already exists
-# EINCOMPLETE (-13): Trying to access an incomplete resource
-# EKEY (-14): A decryption operation failed (never returned by the API)
-# ESID (-15): Invalid or expired user session, please relogin
-# EBLOCKED (-16): User blocked
-# EOVERQUOTA (-17): Request over quota
-# ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
-# ETOOMANYCONNECTIONS (-19): Too many connections on this resource
-# EWRITE (-20): Write failed
-# EREAD (-21): Read failed
-# EAPPKEY (-22): Invalid application key; request not processed
-
-
-class MegaCoNz(Hoster):
- __name__ = "MegaCoNz"
- __type__ = "hoster"
- __version__ = "0.26"
-
- __pattern__ = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#(?P<TYPE>N|)!(?P<ID>[\w^_]+)!(?P<KEY>[\w,-]+)'
-
- __description__ = """Mega.co.nz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "ranan@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_URL = "https://eu.api.mega.co.nz/cs"
- FILE_SUFFIX = ".crypted"
-
-
- def b64_decode(self, data):
- data = data.replace("-", "+").replace("_", "/")
- return standard_b64decode(data + '=' * (-len(data) % 4))
-
-
- def getCipherKey(self, key):
- """ Construct the cipher key from the given data """
- a = array.array("I", self.b64_decode(key))
-
- k = array.array("I", (a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]))
- iv = a[4:6] + array.array("I", (0, 0))
- meta_mac = a[6:8]
-
- return k, iv, meta_mac
-
-
- def api_response(self, **kwargs):
- """ Dispatch a call to the api, see https://mega.co.nz/#developers """
-
- # generate a session id, no idea where to obtain elsewhere
- uid = random.random.randint(10 << 9, 10 ** 10)
-
- res = self.load(self.API_URL, get={'id': uid}, post=json_dumps([kwargs]))
- self.logDebug("Api Response: " + res)
- return json_loads(res)
-
-
- def decryptAttr(self, data, key):
- k, iv, meta_mac = self.getCipherKey(key)
- cbc = AES.new(k, AES.MODE_CBC, "\0" * 16)
- attr = decode(cbc.decrypt(self.b64_decode(data)))
-
- self.logDebug("Decrypted Attr: %s" % attr)
- if not attr.startswith("MEGA"):
- self.fail(_("Decryption failed"))
-
- # Data is padded, 0-bytes must be stripped
- return json_loads(re.search(r'{.+?}', attr).group(0))
-
-
- def decryptFile(self, key):
- """ Decrypts the file at lastDownload` """
-
- # upper 64 bit of counter start
- n = self.b64_decode(key)[16:24]
-
- # convert counter to long and shift bytes
- k, iv, meta_mac = self.getCipherKey(key)
- ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
- cipher = AES.new(k, AES.MODE_CTR, counter=ctr)
-
- self.pyfile.setStatus("decrypting")
- self.pyfile.setProgress(0)
-
- file_crypted = fs_encode(self.lastDownload)
- file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
-
- try:
- f = open(file_crypted, "rb")
- df = open(file_decrypted, "wb")
-
- except IOError, e:
- self.fail(e)
-
- chunk_size = 2 ** 15 # buffer size, 32k
- # file_mac = [0, 0, 0, 0] # calculate CBC-MAC for checksum
-
- chunks = os.path.getsize(file_crypted) / chunk_size + 1
- for i in xrange(chunks):
- buf = f.read(chunk_size)
- if not buf:
- break
-
- chunk = cipher.decrypt(buf)
- df.write(chunk)
-
- self.pyfile.setProgress(int((100.0 / chunks) * i))
-
- # chunk_mac = [iv[0], iv[1], iv[0], iv[1]]
- # for i in xrange(0, chunk_size, 16):
- # block = chunk[i:i+16]
- # if len(block) % 16:
- # block += '=' * (16 - (len(block) % 16))
- # block = array.array("I", block)
-
- # chunk_mac = [chunk_mac[0] ^ a_[0], chunk_mac[1] ^ block[1], chunk_mac[2] ^ block[2], chunk_mac[3] ^ block[3]]
- # chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k)
-
- # file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1], file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]]
- # file_mac = aes_cbc_encrypt_a32(file_mac, k)
-
- self.pyfile.setProgress(100)
-
- f.close()
- df.close()
-
- # if file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] != meta_mac:
- # os.remove(file_decrypted)
- # self.fail(_("Checksum mismatch"))
-
- os.remove(file_crypted)
- self.lastDownload = fs_decode(file_decrypted)
-
-
- def checkError(self, code):
- ecode = abs(code)
-
- if ecode in (9, 16, 21):
- self.offline()
-
- elif ecode in (3, 13, 17, 18, 19):
- self.tempOffline()
-
- elif ecode in (1, 4, 6, 10, 15, 21):
- self.retry(5, 30, _("Error code: [%s]") % -ecode)
-
- else:
- self.fail(_("Error code: [%s]") % -ecode)
-
-
- def process(self, pyfile):
- pattern = re.match(self.__pattern__, pyfile.url).groupdict()
- id = pattern['ID']
- key = pattern['KEY']
- public = pattern['TYPE'] == ''
-
- self.logDebug("ID: %s" % id, "Key: %s" % key, "Type: %s" % ("public" if public else "node"))
-
- # g is for requesting a download url
- # this is similar to the calls in the mega js app, documentation is very bad
- if public:
- mega = self.api_response(a="g", g=1, p=id, ssl=1)[0]
- else:
- mega = self.api_response(a="g", g=1, n=id, ssl=1)[0]
-
- if isinstance(mega, int):
- self.checkError(mega)
- elif "e" in mega:
- self.checkError(mega['e'])
-
- attr = self.decryptAttr(mega['at'], key)
-
- pyfile.name = attr['n'] + self.FILE_SUFFIX
- pyfile.size = mega['s']
-
- # self.req.http.c.setopt(pycurl.SSL_CIPHER_LIST, "RC4-MD5:DEFAULT")
-
- self.download(mega['g'])
-
- self.decryptFile(key)
-
- # Everything is finished and final name can be set
- pyfile.name = attr['n']
diff --git a/module/plugins/hoster/MegaDebridEu.py b/module/plugins/hoster/MegaDebridEu.py
deleted file mode 100644
index 9c8cc2a90..000000000
--- a/module/plugins/hoster/MegaDebridEu.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class MegaDebridEu(MultiHoster):
- __name__ = "MegaDebridEu"
- __type__ = "hoster"
- __version__ = "0.47"
-
- __pattern__ = r'http://((?:www\d+\.|s\d+\.)?mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """mega-debrid.eu multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
-
-
- API_URL = "https://www.mega-debrid.eu/api.php"
-
-
- def api_load(self):
- """
- Connexion to the mega-debrid API
- Return True if succeed
- """
- user, data = self.account.selectAccount()
- jsonResponse = self.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- res = json_loads(jsonResponse)
-
- if res['response_code'] == "ok":
- self.token = res['token']
- return True
- else:
- return False
-
-
- def handlePremium(self, pyfile):
- """
- Debrid a link
- Return The debrided link if succeed or original link if fail
- """
- if not self.api_load():
- self.error("Unable to connect to remote API")
-
- jsonResponse = self.load(self.API_URL,
- get={'action': 'getLink', 'token': self.token},
- post={'link': pyfile.url})
-
- res = json_loads(jsonResponse)
- if res['response_code'] == "ok":
- self.link = res['debridLink'][1:-1]
-
-
-getInfo = create_getInfo(MegaDebridEu)
diff --git a/module/plugins/hoster/MegaFilesSe.py b/module/plugins/hoster/MegaFilesSe.py
deleted file mode 100644
index 795b74a78..000000000
--- a/module/plugins/hoster/MegaFilesSe.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegaFilesSe(DeadHoster):
- __name__ = "MegaFilesSe"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?megafiles\.se/\w{12}'
- __config__ = []
-
- __description__ = """MegaFiles.se hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(MegaFilesSe)
diff --git a/module/plugins/hoster/MegaRapidCz.py b/module/plugins/hoster/MegaRapidCz.py
deleted file mode 100644
index 59fb8251e..000000000
--- a/module/plugins/hoster/MegaRapidCz.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getRequest
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- h = getRequest()
- h.c.setopt(pycurl.HTTPHEADER,
- ["Accept: text/html",
- "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"])
-
- for url in urls:
- html = h.load(url, decode=True)
- yield parseFileInfo(MegaRapidCz, url, html)
-
-
-class MegaRapidCz(SimpleHoster):
- __name__ = "MegaRapidCz"
- __type__ = "hoster"
- __version__ = "0.56"
-
- __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MegaRapid.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("MikyWoW", "mikywow@seznam.cz"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h1.*?><span.*?>(?:<a.*?>)?(?P<N>[^<]+)'
- SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong></td>'
- OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
-
- CHECK_TRAFFIC = True
-
- LINK_PREMIUM_PATTERN = r'<a href="(.+?)" title="Stahnout">([^<]+)</a>'
-
- ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahování je přístupné pouze přihlášenÜm uÅŸivatelům'
- ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahování zdarma je moÅŸné jen přes náš'
-
-
- def setup(self):
- self.chunkLimit = 1
-
-
- def handlePremium(self, pyfile):
- m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- else:
- if re.search(self.ERR_LOGIN_PATTERN, self.html):
- self.relogin(self.user)
- self.retry(wait_time=60, reason=_("User login failed"))
- elif re.search(self.ERR_CREDIT_PATTERN, self.html):
- self.fail(_("Not enough credit left"))
- else:
- self.fail(_("Download link not found"))
diff --git a/module/plugins/hoster/MegaRapidoNet.py b/module/plugins/hoster/MegaRapidoNet.py
deleted file mode 100644
index a3b4c72ba..000000000
--- a/module/plugins/hoster/MegaRapidoNet.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-def random_with_N_digits(n):
- rand = "0."
- not_zero = 0
- for i in range(1, n + 1):
- r = random.randint(0, 9)
- if(r > 0):
- not_zero += 1
- rand += str(r)
-
- if not_zero > 0:
- return rand
- else:
- return random_with_N_digits(n)
-
-
-class MegaRapidoNet(MultiHoster):
- __name__ = "MegaRapidoNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?\w+\.megarapido\.net/\?file=\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MegaRapido.net multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- LINK_PREMIUM_PATTERN = r'<\s*?a[^>]*?title\s*?=\s*?["\'].*?download["\'][^>]*?href=["\']([^"\']+)'
-
- ERROR_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']?alert-message error.*?>([^<]*)'
-
-
- def handlePremium(self, pyfile):
- self.html = self.load("http://megarapido.net/gerar.php",
- post={'rand' :random_with_N_digits(16),
- 'urllist' : pyfile.url,
- 'links' : pyfile.url,
- 'exibir' : "normal",
- 'usar' : "premium",
- 'user' : self.account.getAccountInfo(self.user).get('sid', None),
- 'autoreset': ""})
-
- if "desloga e loga novamente para gerar seus links" in self.html.lower():
- self.error("You have logged in at another place")
-
- return super(MegaRapidoNet, self).handlePremium(pyfile)
diff --git a/module/plugins/hoster/MegacrypterCom.py b/module/plugins/hoster/MegacrypterCom.py
deleted file mode 100644
index 10a2eb025..000000000
--- a/module/plugins/hoster/MegacrypterCom.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads, json_dumps
-
-from module.plugins.hoster.MegaCoNz import MegaCoNz
-
-
-class MegacrypterCom(MegaCoNz):
- __name__ = "MegacrypterCom"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'https?://\w{0,10}\.?megacrypter\.com/[\w!-]+'
-
- __description__ = """Megacrypter.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GonzaloSR", "gonzalo@gonzalosr.com")]
-
-
- API_URL = "http://megacrypter.com/api"
- FILE_SUFFIX = ".crypted"
-
-
- def api_response(self, **kwargs):
- """ Dispatch a call to the api, see megacrypter.com/api_doc """
- self.logDebug("JSON request: " + json_dumps(kwargs))
- res = self.load(self.API_URL, post=json_dumps(kwargs))
- self.logDebug("API Response: " + res)
- return json_loads(res)
-
-
- def process(self, pyfile):
- # match is guaranteed because plugin was chosen to handle url
- node = re.match(self.__pattern__, pyfile.url).group(0)
-
- # get Mega.co.nz link info
- info = self.api_response(link=node, m="info")
-
- # get crypted file URL
- dl = self.api_response(link=node, m="dl")
-
- # TODO: map error codes, implement password protection
- # if info['pass'] is True:
- # crypted_file_key, md5_file_key = info['key'].split("#")
-
- key = self.b64_decode(info['key'])
-
- pyfile.name = info['name'] + self.FILE_SUFFIX
-
- self.download(dl['url'])
-
- self.decryptFile(key)
-
- # Everything is finished and final name can be set
- pyfile.name = info['name']
diff --git a/module/plugins/hoster/MegareleaseOrg.py b/module/plugins/hoster/MegareleaseOrg.py
deleted file mode 100644
index 1460ac868..000000000
--- a/module/plugins/hoster/MegareleaseOrg.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegareleaseOrg(DeadHoster):
- __name__ = "MegareleaseOrg"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?megarelease\.org/\w{12}'
- __config__ = []
-
- __description__ = """Megarelease.org hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("derek3x", "derek3x@vmail.me"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(MegareleaseOrg)
diff --git a/module/plugins/hoster/MegasharesCom.py b/module/plugins/hoster/MegasharesCom.py
deleted file mode 100644
index ed2363fe3..000000000
--- a/module/plugins/hoster/MegasharesCom.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MegasharesCom(SimpleHoster):
- __name__ = "MegasharesCom"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Megashares.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>.+?)">'
- SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted|Invalid link)'
-
- LINK_PATTERN = r'<div id="show_download_button_%d".*?>\s*<a href="(.+?)">'
-
- PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <.*?>(\w+).*?You have.*?<.*?>.*?([\d.]+) (\w+)'
- PASSPORT_RENEW_PATTERN = r'(\d+):<strong>(\d+)</strong>:<strong>(\d+)</strong>'
- REACTIVATE_NUM_PATTERN = r'<input[^>]*id="random_num" value="(\d+)" />'
- REACTIVATE_PASSPORT_PATTERN = r'<input[^>]*id="passport_num" value="(\w+)" />'
- REQUEST_URI_PATTERN = r'var request_uri = "(.+?)";'
- NO_SLOTS_PATTERN = r'<dd class="red">All download slots for this link are currently filled'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
-
- def handlePremium(self, pyfile):
- self.handleDownload(True)
-
-
- def handleFree(self, pyfile):
- if self.NO_SLOTS_PATTERN in self.html:
- self.retry(wait_time=5 * 60)
-
- m = re.search(self.REACTIVATE_PASSPORT_PATTERN, self.html)
- if m:
- passport_num = m.group(1)
- request_uri = re.search(self.REQUEST_URI_PATTERN, self.html).group(1)
-
- for _i in xrange(5):
- random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
-
- verifyinput = self.decryptCaptcha("http://d01.megashares.com/index.php",
- get={'secgfx': "gfx", 'random_num': random_num})
-
- self.logInfo(_("Reactivating passport %s: %s %s") % (passport_num, random_num, verifyinput))
-
- res = self.load("http://d01.megashares.com%s" % request_uri,
- get={'rs' : "check_passport_renewal",
- 'rsargs[]': verifyinput,
- 'rsargs[]': random_num,
- 'rsargs[]': passport_num,
- 'rsargs[]': "replace_sec_pprenewal",
- 'rsrnd[]' : str(int(time.time() * 1000))})
-
- if 'Thank you for reactivating your passport.' in res:
- self.correctCaptcha()
- self.retry()
- else:
- self.invalidCaptcha()
- else:
- self.fail(_("Failed to reactivate passport"))
-
- m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
- if m:
- time = [int(x) for x in m.groups()]
- renew = time[0] + (time[1] * 60) + (time[2] * 60)
- self.logDebug("Waiting %d seconds for a new passport" % renew)
- self.retry(wait_time=renew, reason=_("Passport renewal"))
-
- # Check traffic left on passport
- m = re.search(self.PASSPORT_LEFT_PATTERN, self.html, re.M | re.S)
- if m is None:
- self.fail(_("Passport not found"))
-
- self.logInfo(_("Download passport: %s") % m.group(1))
- data_left = float(m.group(2)) * 1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[m.group(3)]
- self.logInfo(_("Data left: %s %s (%d MB needed)") % (m.group(2), m.group(3), self.pyfile.size / 1048576))
-
- if not data_left:
- self.retry(wait_time=600, reason=_("Passport renewal"))
-
- self.handleDownload(False)
-
-
- def handleDownload(self, premium=False):
- # Find download link;
- m = re.search(self.LINK_PATTERN % (1 if premium else 2), self.html)
- msg = _('%s download URL' % ('Premium' if premium else 'Free'))
- if m is None:
- self.error(msg)
-
- self.link = m.group(1)
- self.logDebug("%s: %s" % (msg, self.link))
-
-
-getInfo = create_getInfo(MegasharesCom)
diff --git a/module/plugins/hoster/MegauploadCom.py b/module/plugins/hoster/MegauploadCom.py
deleted file mode 100644
index 7fe305834..000000000
--- a/module/plugins/hoster/MegauploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegauploadCom(DeadHoster):
- __name__ = "MegauploadCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?megaupload\.com/\?.*&?(d|v)=\w+'
- __config__ = []
-
- __description__ = """Megaupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
-getInfo = create_getInfo(MegauploadCom)
diff --git a/module/plugins/hoster/MegavideoCom.py b/module/plugins/hoster/MegavideoCom.py
deleted file mode 100644
index 0b4523e03..000000000
--- a/module/plugins/hoster/MegavideoCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegavideoCom(DeadHoster):
- __name__ = "MegavideoCom"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?megavideo\.com/\?.*&?(d|v)=\w+'
- __config__ = []
-
- __description__ = """Megavideo.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(MegavideoCom)
diff --git a/module/plugins/hoster/MovReelCom.py b/module/plugins/hoster/MovReelCom.py
deleted file mode 100644
index 2fe5184ae..000000000
--- a/module/plugins/hoster/MovReelCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class MovReelCom(XFSHoster):
- __name__ = "MovReelCom"
- __type__ = "hoster"
- __version__ = "1.24"
-
- __pattern__ = r'http://(?:www\.)?movreel\.com/\w{12}'
-
- __description__ = """MovReel.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("JorisV83", "jorisv83-pyload@yahoo.com")]
-
-
- LINK_PATTERN = r'<a href="(.+?)">Download Link'
-
-
-getInfo = create_getInfo(MovReelCom)
diff --git a/module/plugins/hoster/MultihostersCom.py b/module/plugins/hoster/MultihostersCom.py
deleted file mode 100644
index bcd7c6237..000000000
--- a/module/plugins/hoster/MultihostersCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.ZeveraCom import ZeveraCom
-
-
-class MultihostersCom(ZeveraCom):
- __name__ = "MultihostersCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)multihosters\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
-
- __description__ = """Multihosters.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
diff --git a/module/plugins/hoster/MultishareCz.py b/module/plugins/hoster/MultishareCz.py
deleted file mode 100644
index bbb77f525..000000000
--- a/module/plugins/hoster/MultishareCz.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MultishareCz(SimpleHoster):
- __name__ = "MultishareCz"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MultiShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- SIZE_REPLACEMENTS = [('&nbsp;', '')]
-
- CHECK_TRAFFIC = True
- MULTI_HOSTER = True
-
- INFO_PATTERN = ur'(?:<li>Název|Soubor): <strong>(?P<N>[^<]+)</strong><(?:/li><li|br)>Velikost: <strong>(?P<S>[^<]+)</strong>'
- OFFLINE_PATTERN = ur'<h1>Stáhnout soubor</h1><p><strong>PoşadovanÜ soubor neexistuje.</strong></p>'
-
-
- def handleFree(self, pyfile):
- self.download("http://www.multishare.cz/html/download_free.php", get={'ID': self.info['pattern']['ID']})
-
-
- def handlePremium(self, pyfile):
- self.download("http://www.multishare.cz/html/download_premium.php", get={'ID': self.info['pattern']['ID']})
-
-
- def handleMulti(self, pyfile):
- self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": pyfile.url}, decode=True)
-
- self.checkInfo()
-
- if not self.checkTrafficLeft():
- self.fail(_("Not enough credit left to download file"))
-
- self.download("http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random.random() * 10000 * random.random()),
- get={'u_ID' : self.acc_info['u_ID'],
- 'u_hash': self.acc_info['u_hash'],
- 'link' : pyfile.url},
- disposition=True)
-
-
-getInfo = create_getInfo(MultishareCz)
diff --git a/module/plugins/hoster/MyfastfileCom.py b/module/plugins/hoster/MyfastfileCom.py
deleted file mode 100644
index 662638843..000000000
--- a/module/plugins/hoster/MyfastfileCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class MyfastfileCom(MultiHoster):
- __name__ = "MyfastfileCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Myfastfile.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def setup(self):
- self.chunkLimit = -1
-
-
- def handlePremium(self, pyfile):
- self.html = self.load('http://myfastfile.com/api.php',
- get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
- 'link': pyfile.url})
- self.logDebug("JSON data: " + self.html)
-
- self.html = json_loads(self.html)
- if self.html['status'] != 'ok':
- self.fail(_("Unable to unrestrict link"))
-
- self.link = self.html['link']
-
-
-getInfo = create_getInfo(MyfastfileCom)
diff --git a/module/plugins/hoster/MystoreTo.py b/module/plugins/hoster/MystoreTo.py
deleted file mode 100644
index 531368134..000000000
--- a/module/plugins/hoster/MystoreTo.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test link:
-# http://mystore.to/dl/mxcA50jKfP
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MystoreTo(SimpleHoster):
- __name__ = "MystoreTo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?mystore\.to/dl/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Mystore.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.+?)<'
- SIZE_PATTERN = r'FILESIZE: (?P<S>[\d\.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>file not found<'
-
-
- def setup(self):
- self.chunkLimit = 1
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- try:
- fid = re.search(r'wert="(.+?)"', self.html).group(1)
-
- except AttributeError:
- self.error(_("File-ID not found"))
-
- self.link = self.load("http://mystore.to/api/download",
- post={'FID': fid})
-
-
-getInfo = create_getInfo(MystoreTo)
diff --git a/module/plugins/hoster/MyvideoDe.py b/module/plugins/hoster/MyvideoDe.py
deleted file mode 100644
index 8af4a9a61..000000000
--- a/module/plugins/hoster/MyvideoDe.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.unescape import unescape
-
-
-class MyvideoDe(Hoster):
- __name__ = "MyvideoDe"
- __type__ = "hoster"
- __version__ = "0.90"
-
- __pattern__ = r'http://(?:www\.)?myvideo\.de/watch/'
-
- __description__ = """Myvideo.de hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.download_html()
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- def download_html(self):
- self.html = self.load(self.pyfile.url)
-
-
- def get_file_url(self):
- videoId = re.search(r"addVariable\('_videoid','(.*)'\);p.addParam\('quality'", self.html).group(1)
- videoServer = re.search("rel='image_src' href='(.*)thumbs/.*' />", self.html).group(1)
- file_url = videoServer + videoId + ".flv"
- return file_url
-
-
- def get_file_name(self):
- file_name_pattern = r'<h1 class=\'globalHd\'>(.*)</h1>'
- return unescape(re.search(file_name_pattern, self.html).group(1).replace("/", "") + '.flv')
-
-
- def file_exists(self):
- self.download_html()
- self.load(str(self.pyfile.url), cookies=False, just_header=True)
- if self.req.lastEffectiveURL == "http://www.myvideo.de/":
- return False
- return True
diff --git a/module/plugins/hoster/NahrajCz.py b/module/plugins/hoster/NahrajCz.py
deleted file mode 100644
index 5f5003e22..000000000
--- a/module/plugins/hoster/NahrajCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class NahrajCz(DeadHoster):
- __name__ = "NahrajCz"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?nahraj\.cz/content/download/.+'
- __config__ = []
-
- __description__ = """Nahraj.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(NahrajCz)
diff --git a/module/plugins/hoster/NarodRu.py b/module/plugins/hoster/NarodRu.py
deleted file mode 100644
index b7380add0..000000000
--- a/module/plugins/hoster/NarodRu.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class NarodRu(SimpleHoster):
- __name__ = "NarodRu"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Narod.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
- SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
- OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
-
- SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
- URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
- (r"/start/\d+\.\w+-narod\.yandex\.ru/(\d{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
-
- CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
- LINK_FREE_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
-
-
- def handleFree(self, pyfile):
- for _i in xrange(5):
- self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random.random() * 777))
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha"))
-
- post_data = {"action": "sendcapcha"}
- captcha_url, post_data['key'] = m.groups()
- post_data['rep'] = self.decryptCaptcha(captcha_url)
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = 'http://narod.ru' + m.group(1)
- self.correctCaptcha()
- break
-
- elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
- self.invalidCaptcha()
-
- else:
- self.error(_("Download link"))
-
- else:
- self.fail(_("No valid captcha code entered"))
-
-
-getInfo = create_getInfo(NarodRu)
diff --git a/module/plugins/hoster/NetloadIn.py b/module/plugins/hoster/NetloadIn.py
deleted file mode 100644
index cab7bd88f..000000000
--- a/module/plugins/hoster/NetloadIn.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-def getInfo(urls):
- ## returns list of tupels (name, size (in bytes), status (see FileDatabase), url)
-
- apiurl = "http://api.netload.in/info.php"
- id_regex = re.compile(NetloadIn.__pattern__)
- urls_per_query = 80
-
- for chunk in chunks(urls, urls_per_query):
- ids = ""
- for url in chunk:
- match = id_regex.search(url)
- if match:
- ids = ids + match.group('ID') + ";"
-
- api = getURL(apiurl,
- get={'auth' : "Zf9SnQh9WiReEsb18akjvQGqT0I830e8",
- 'bz' : 1,
- 'md5' : 1,
- 'file_id': ids},
- decode=True)
-
- if api is None or len(api) < 10:
- self.logDebug("Prefetch failed")
- return
-
- if api.find("unknown_auth") >= 0:
- self.logDebug("Outdated auth code")
- return
-
- result = []
-
- for i, r in enumerate(api.splitlines()):
- try:
- tmp = r.split(";")
-
- try:
- size = int(tmp[2])
- except Exception:
- size = 0
-
- result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i] ))
-
- except Exception:
- self.logDebug("Error while processing response: %s" % r)
-
- yield result
-
-
-class NetloadIn(Hoster):
- __name__ = "NetloadIn"
- __type__ = "hoster"
- __version__ = "0.49"
-
- __pattern__ = r'https?://(?:www\.)?netload\.in/(?P<PATH>datei|index\.php\?id=10&file_id=)(?P<ID>\w+)'
-
- __description__ = """Netload.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("RaNaN", "ranan@pyload.org"),
- ("Gregy", "gregy@gregy.cz")]
-
-
- RECAPTCHA_KEY = "6LcLJMQSAAAAAJzquPUPKNovIhbK6LpSqCjYrsR1"
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
-
-
- def process(self, pyfile):
- self.url = pyfile.url
-
- self.prepare()
-
- pyfile.setStatus("downloading")
-
- self.proceed(self.url)
-
-
- def prepare(self):
- self.api_load()
-
- if self.api_data and self.api_data['filename']:
- self.pyfile.name = self.api_data['filename']
-
- if self.premium:
- self.logDebug("Use Premium Account")
-
- settings = self.load("http://www.netload.in/index.php", get={'id': 2, 'lang': "en"})
-
- if '<option value="2" selected="selected">Direkter Download' in settings:
- self.logDebug("Using direct download")
- return True
- else:
- self.logDebug("Direct downloads not enabled. Parsing html for a download URL")
-
- if self.download_html():
- return True
- else:
- self.fail(_("Failed"))
- return False
-
-
- def api_load(self, n=0):
- url = self.url
- id_regex = re.compile(self.__pattern__)
- match = id_regex.search(url)
-
- if match:
- #normalize url
- self.url = 'http://www.netload.in/datei%s.htm' % match.group('ID')
- self.logDebug("URL: %s" % self.url)
- else:
- self.api_data = False
- return
-
- apiurl = "http://api.netload.in/info.php"
- html = self.load(apiurl, cookies=False,
- get={"file_id": match.group('ID'), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
- "md5": "1"}, decode=True).strip()
- if not html and n <= 3:
- self.setWait(2)
- self.wait()
- self.api_load(n + 1)
- return
-
- self.logDebug("APIDATA: " + html)
-
- self.api_data = {}
-
- if html and ";" in html and html not in ("unknown file_data", "unknown_server_data", "No input file specified."):
- lines = html.split(";")
- self.api_data['exists'] = True
- self.api_data['fileid'] = lines[0]
- self.api_data['filename'] = lines[1]
- self.api_data['size'] = lines[2]
- self.api_data['status'] = lines[3]
-
- if self.api_data['status'] == "online":
- self.api_data['checksum'] = lines[4].strip()
- else:
- self.api_data = False # check manually since api data is useless sometimes
-
- if lines[0] == lines[1] and lines[2] == "0": # useless api data
- self.api_data = False
- else:
- self.api_data = False
-
-
- def final_wait(self, page):
- wait_time = self.get_wait_time.time(page)
-
- self.setWait(wait_time)
-
- self.logDebug("Final wait %d seconds" % wait_time)
-
- self.wait()
-
- self.url = self.get_file_url(page)
-
-
- def check_free_wait(self, page):
- if ">An access request has been made from IP address <" in page:
- self.wantReconnect = True
- self.setWait(self.get_wait_time.time(page) or 30)
- self.wait()
- return True
- else:
- return False
-
-
- def download_html(self):
- page = self.load(self.url, decode=True)
-
- if "/share/templates/download_hddcrash.tpl" in page:
- self.logError(_("Netload HDD Crash"))
- self.fail(_("File temporarily not available"))
-
- if not self.api_data:
- self.logDebug("API Data may be useless, get details from html page")
-
- if "* The file was deleted" in page:
- self.offline()
-
- name = re.search(r'class="dl_first_filename">([^<]+)', page, re.M)
- # the found filename is not truncated
- if name:
- name = name.group(1).strip()
- if not name.endswith(".."):
- self.pyfile.name = name
-
- captchawaited = False
-
- for i in xrange(5):
- if not page:
- page = self.load(self.url)
- t = time.time() + 30
-
- if "/share/templates/download_hddcrash.tpl" in page:
- self.logError(_("Netload HDD Crash"))
- self.fail(_("File temporarily not available"))
-
- self.logDebug("Try number %d " % i)
-
- if ">Your download is being prepared.<" in page:
- self.logDebug("We will prepare your download")
- self.final_wait(page)
- return True
-
- self.logDebug("Trying to find captcha")
-
- try:
- url_captcha_html = re.search(r'(index.php\?id=10&amp;.*&amp;captcha=1)', page).group(1).replace("amp;", "")
-
- except Exception, e:
- self.logDebug("Exception during Captcha regex: %s" % e.message)
- page = None
-
- else:
- url_captcha_html = urlparse.urljoin("http://netload.in/", url_captcha_html)
- break
-
- self.html = self.load(url_captcha_html)
-
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- response_page = self.load("http://www.netload.in/index.php?id=10",
- post={'captcha_check' : '1',
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response,
- 'file_id' : self.api_data['fileid'],
- 'Download_Next' : ''})
- if "Orange_Link" in response_page:
- break
-
- if self.check_free_wait(response_page):
- self.logDebug("Had to wait for next free slot, trying again")
- return self.download_html()
-
- else:
- download_url = self.get_file_url(response_page)
- self.logDebug("Download URL after get_file: " + download_url)
- if not download_url.startswith("http://"):
- self.error(_("Download url: %s") % download_url)
- self.wait()
-
- self.url = download_url
- return True
-
-
- def get_file_url(self, page):
- try:
- file_url_pattern = r'<a class="Orange_Link" href="(http://.+)".?>Or click here'
- attempt = re.search(file_url_pattern, page)
- if attempt:
- return attempt.group(1)
- else:
- self.logDebug("Backup try for final link")
- file_url_pattern = r'<a href="(.+)" class="Orange_Link">Click here'
- attempt = re.search(file_url_pattern, page)
- return "http://netload.in/" + attempt.group(1)
-
- except Exception, e:
- self.logDebug("Getting final link failed", e.message)
- return None
-
-
- def get_wait_time.time(self, page):
- return int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
-
-
- def proceed(self, url):
- self.download(url, disposition=True)
-
- check = self.checkDownload({'empty' : re.compile(r'^$'),
- 'offline': re.compile("The file was deleted")})
- if check == "empty":
- self.logInfo(_("Downloaded File was empty"))
- self.retry()
-
- elif check == "offline":
- self.offline()
diff --git a/module/plugins/hoster/NitroflareCom.py b/module/plugins/hoster/NitroflareCom.py
deleted file mode 100644
index 40bf0749f..000000000
--- a/module/plugins/hoster/NitroflareCom.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-class NitroflareCom(SimpleHoster):
- __name__ = "NitroflareCom"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'https?://(?:www\.)?nitroflare\.com/view/(?P<ID>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Nitroflare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sahil", "sahilshekhawat01@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Stickell", "l.stickell@yahoo.it")]
-
- # URL_REPLACEMENTS = [("http://", "https://")]
-
- INFO_PATTERN = r'title="(?P<N>.+?)".+>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>File doesn\'t exist'
-
- LINK_FREE_PATTERN = r'(https?://[\w\-]+\.nitroflare\.com/.+?)"'
-
- RECAPTCHA_KEY = "6Lenx_USAAAAAF5L1pmTWvWcH73dipAEzNnmNLgy"
-
- PREMIUM_ONLY_PATTERN = r'This file is available with Premium only'
- WAIT_PATTERN = r'You have to wait .+?<'
- ERROR_PATTERN = r'downloading is not possible'
-
-
- def checkErrors(self):
- if not self.html:
- return
-
- if not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
- self.fail(_("Link require a premium account to be handled"))
-
- elif hasattr(self, 'WAIT_PATTERN'):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec)', m.group(0), re.I))
- self.wait(wait_time, wait_time > 300)
- return
-
- elif hasattr(self, 'ERROR_PATTERN'):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = m.group(1)
- self.error(errmsg)
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- # used here to load the cookies which will be required later
- self.load(pyfile.url, post={'goToFreePage': ""})
-
- self.load("http://nitroflare.com/ajax/setCookie.php", post={'fileId': self.info['pattern']['ID']})
- self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
- post={'method': "startTimer", 'fileId': self.info['pattern']['ID']})
-
- self.checkErrors()
-
- try:
- js_file = self.load("http://nitroflare.com/js/downloadFree.js?v=1.0.1")
- var_time = re.search("var time = (\\d+);", js_file)
- wait_time = int(var_time.groups()[0])
-
- except Exception:
- wait_time = 60
-
- self.wait(wait_time)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
- post={'method' : "fetchDownload",
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- if "The captcha wasn't entered correctly" in self.html:
- self.logWarning("The captcha wasn't entered correctly")
- return
-
- if "You have to fill the captcha" in self.html:
- self.logWarning("Captcha unfilled")
- return
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- else:
- self.logError("Unable to detect direct link")
diff --git a/module/plugins/hoster/NoPremiumPl.py b/module/plugins/hoster/NoPremiumPl.py
deleted file mode 100644
index c3313c525..000000000
--- a/module/plugins/hoster/NoPremiumPl.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class NoPremiumPl(MultiHoster):
- __name__ = "NoPremiumPl"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://direct\.nopremium\.pl.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NoPremium.pl multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
-
- API_URL = "http://crypt.nopremium.pl"
-
- API_QUERY = {'site' : "nopremium",
- 'output' : "json",
- 'username': "",
- 'password': "",
- 'url' : ""}
-
- ERROR_CODES = {0 : "[%s] Incorrect login credentials",
- 1 : "[%s] Not enough transfer to download - top-up your account",
- 2 : "[%s] Incorrect / dead link",
- 3 : "[%s] Error connecting to hosting, try again later",
- 9 : "[%s] Premium account has expired",
- 15: "[%s] Hosting no longer supported",
- 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
-
-
- def prepare(self):
- super(NoPremiumPl, self).prepare()
-
- data = self.account.getAccountData(self.user)
-
- self.usr = data['usr']
- self.pwd = data['pwd']
-
-
- def runFileQuery(self, url, mode=None):
- query = self.API_QUERY.copy()
-
- query["username"] = self.usr
- query["password"] = self.pwd
- query["url"] = url
-
- if mode == "fileinfo":
- query['check'] = 2
- query['loc'] = 1
-
- self.logDebug(query)
-
- return self.load(self.API_URL, post=query)
-
-
- def handleFree(self, pyfile):
- try:
- data = self.runFileQuery(pyfile.url, 'fileinfo')
-
- except Exception:
- self.logDebug("runFileQuery error")
- self.tempOffline()
-
- try:
- parsed = json_loads(data)
-
- except Exception:
- self.logDebug("loads error")
- self.tempOffline()
-
- self.logDebug(parsed)
-
- if "errno" in parsed.keys():
- if parsed["errno"] in self.ERROR_CODES:
- # error code in known
- self.fail(self.ERROR_CODES[parsed["errno"]] % self.__name__)
- else:
- # error code isn't yet added to plugin
- self.fail(
- parsed["errstring"]
- or _("Unknown error (code: %s)") % parsed["errno"]
- )
-
- if "sdownload" in parsed:
- if parsed["sdownload"] == "1":
- self.fail(
- _("Download from %s is possible only using NoPremium.pl website \
- directly") % parsed["hosting"])
-
- pyfile.name = parsed["filename"]
- pyfile.size = parsed["filesize"]
-
- try:
- self.link = self.runFileQuery(pyfile.url, 'filedownload')
-
- except Exception:
- self.logDebug("runFileQuery error #2")
- self.tempOffline()
diff --git a/module/plugins/hoster/NosuploadCom.py b/module/plugins/hoster/NosuploadCom.py
deleted file mode 100644
index 241ee3a29..000000000
--- a/module/plugins/hoster/NosuploadCom.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class NosuploadCom(XFSHoster):
- __name__ = "NosuploadCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
-
- __description__ = """Nosupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)</p>'
- LINK_PATTERN = r'<a class="select" href="(http://.+?)">Download</a>'
-
- WAIT_PATTERN = r'Please wait.*?>(\d+)</span>'
-
-
- def getDownloadLink(self):
- # stage1: press the "Free Download" button
- data = self.getPostParameters()
- self.html = self.load(self.pyfile.url, post=data, decode=True)
-
- # stage2: wait some time and press the "Download File" button
- data = self.getPostParameters()
- wait_time = re.search(self.WAIT_PATTERN, self.html, re.M | re.S).group(1)
- self.logDebug("Hoster told us to wait %s seconds" % wait_time)
- self.wait(wait_time)
- self.html = self.load(self.pyfile.url, post=data, decode=True)
-
- # stage3: get the download link
- return re.search(self.LINK_PATTERN, self.html, re.S).group(1)
-
-
-getInfo = create_getInfo(NosuploadCom)
diff --git a/module/plugins/hoster/NovafileCom.py b/module/plugins/hoster/NovafileCom.py
deleted file mode 100644
index b00f71635..000000000
--- a/module/plugins/hoster/NovafileCom.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://novafile.com/vfun4z6o2cit
-# http://novafile.com/s6zrr5wemuz4
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class NovafileCom(XFSHoster):
- __name__ = "NovafileCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?novafile\.com/\w{12}'
-
- __description__ = """Novafile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- ERROR_PATTERN = r'class="alert.+?alert-separate".*?>\s*(?:<p>)?(.*?)\s*</'
- WAIT_PATTERN = r'<p>Please wait <span id="count".*?>(\d+)</span> seconds</p>'
-
- LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
-
-
-getInfo = create_getInfo(NovafileCom)
diff --git a/module/plugins/hoster/NowDownloadSx.py b/module/plugins/hoster/NowDownloadSx.py
deleted file mode 100644
index a1cf9baf7..000000000
--- a/module/plugins/hoster/NowDownloadSx.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import fixup
-
-
-class NowDownloadSx(SimpleHoster):
- __name__ = "NowDownloadSx"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?(nowdownload\.[a-zA-Z]{2,}/(dl/|download\.php.+?id=|mobile/(#/files/|.+?id=))|likeupload\.org/)\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NowDownload.sx hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[\d.,]+) (?P<U>[\w^_]+) </h4>'
- OFFLINE_PATTERN = r'>This file does not exist'
-
- TOKEN_PATTERN = r'"(/api/token\.php\?token=\w+)"'
- CONTINUE_PATTERN = r'"(/dl2/\w+/\w+)"'
- WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
- LINK_FREE_PATTERN = r'(http://s\d+\.coolcdn\.info/nowdownload/.+?)["\']'
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<.*?>', '')]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- tokenlink = re.search(self.TOKEN_PATTERN, self.html)
- continuelink = re.search(self.CONTINUE_PATTERN, self.html)
- if tokenlink is None or continuelink is None:
- self.error()
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait = int(m.group(1))
- else:
- wait = 60
-
- baseurl = "http://www.nowdownload.at"
- self.html = self.load(baseurl + str(tokenlink.group(1)))
- self.wait(wait)
-
- self.html = self.load(baseurl + str(continuelink.group(1)))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(NowDownloadSx)
diff --git a/module/plugins/hoster/NowVideoSx.py b/module/plugins/hoster/NowVideoSx.py
deleted file mode 100644
index 477379597..000000000
--- a/module/plugins/hoster/NowVideoSx.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class NowVideoSx(SimpleHoster):
- __name__ = "NowVideoSx"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?nowvideo\.[a-zA-Z]{2,}/(video/|mobile/(#/videos/|.+?id=))(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NowVideo.sx hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.nowvideo.sx/video/\g<ID>')]
-
- NAME_PATTERN = r'<h4>(?P<N>.+?)<'
- OFFLINE_PATTERN = r'>This file no longer exists'
-
- LINK_FREE_PATTERN = r'<source src="(.+?)"'
- LINK_PREMIUM_PATTERN = r'<div id="content_player" >\s*<a href="(.+?)"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://www.nowvideo.sx/mobile/video.php", get={'id': self.info['pattern']['ID']})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(NowVideoSx)
diff --git a/module/plugins/hoster/OboomCom.py b/module/plugins/hoster/OboomCom.py
deleted file mode 100644
index 725763345..000000000
--- a/module/plugins/hoster/OboomCom.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# https://www.oboom.com/B7CYZIEB/10Mio.dat
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class OboomCom(Hoster):
- __name__ = "OboomCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>\w{8})'
-
- __description__ = """oboom.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stanley", "stanley.foerster@gmail.com")]
-
-
- RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
-
-
- def setup(self):
- self.chunkLimit = 1
- self.multiDL = self.resumeDownload = self.premium
-
-
- def process(self, pyfile):
- self.pyfile.url.replace(".com/#id=", ".com/#")
- self.pyfile.url.replace(".com/#/", ".com/#")
- self.getFileId(self.pyfile.url)
- self.getSessionToken()
- self.getFileInfo(self.sessionToken, self.fileId)
- self.pyfile.name = self.fileName
- self.pyfile.size = self.fileSize
- if not self.premium:
- self.solveCaptcha()
- self.getDownloadTicket()
- self.download("https://%s/1.0/dlh" % self.downloadDomain, get={"ticket": self.downloadTicket, "http_errors": 0})
-
-
- def loadUrl(self, url, get=None):
- if get is None:
- get = dict()
- return json_loads(self.load(url, get, decode=True))
-
-
- def getFileId(self, url):
- self.fileId = re.match(OboomCom.__pattern__, url).group('ID')
-
-
- def getSessionToken(self):
- if self.premium:
- accountInfo = self.account.getAccountInfo(self.user, True)
- if "session" in accountInfo:
- self.sessionToken = accountInfo['session']
- else:
- self.fail(_("Could not retrieve premium session"))
- else:
- apiUrl = "https://www.oboom.com/1.0/guestsession"
- result = self.loadUrl(apiUrl)
- if result[0] == 200:
- self.sessionToken = result[1]
- else:
- self.fail(_("Could not retrieve token for guest session. Error code: %s") % result[0])
-
-
- def solveCaptcha(self):
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
- apiUrl = "https://www.oboom.com/1.0/download/ticket"
- params = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field": response,
- "download_id": self.fileId,
- "token": self.sessionToken}
- result = self.loadUrl(apiUrl, params)
-
- if result[0] == 200:
- self.downloadToken = result[1]
- self.downloadAuth = result[2]
- self.correctCaptcha()
- self.setWait(30)
- self.wait()
- break
-
- elif result[0] == 400:
- if result[1] == "incorrect-captcha-sol":
- self.invalidCaptcha()
- elif result[1] == "captcha-timeout":
- self.invalidCaptcha()
- elif result[1] == "forbidden":
- self.retry(5, 15 * 60, _("Service unavailable"))
-
- elif result[0] == 403:
- if result[1] == -1: # another download is running
- self.setWait(15 * 60)
- else:
- self.setWait(result[1], True)
- self.wait()
- self.retry(5)
- else:
- self.invalidCaptcha()
- self.fail(_("Received invalid captcha 5 times"))
-
-
- def getFileInfo(self, token, fileId):
- apiUrl = "https://api.oboom.com/1.0/info"
- params = {"token": token, "items": fileId, "http_errors": 0}
-
- result = self.loadUrl(apiUrl, params)
- if result[0] == 200:
- item = result[1][0]
- if item['state'] == "online":
- self.fileSize = item['size']
- self.fileName = item['name']
- else:
- self.offline()
- else:
- self.fail(_("Could not retrieve file info. Error code %s: %s") % (result[0], result[1]))
-
-
- def getDownloadTicket(self):
- apiUrl = "https://api.oboom.com/1/dl"
- params = {"item": self.fileId, "http_errors": 0}
- if self.premium:
- params['token'] = self.sessionToken
- else:
- params['token'] = self.downloadToken
- params['auth'] = self.downloadAuth
-
- result = self.loadUrl(apiUrl, params)
- if result[0] == 200:
- self.downloadDomain = result[1]
- self.downloadTicket = result[2]
- elif result[0] == 421:
- self.retry(wait_time=result[2] + 60, reason=_("Connection limit exceeded"))
- else:
- self.fail(_("Could not retrieve download ticket. Error code: %s") % result[0])
diff --git a/module/plugins/hoster/OneFichierCom.py b/module/plugins/hoster/OneFichierCom.py
deleted file mode 100644
index 8a5fa9cae..000000000
--- a/module/plugins/hoster/OneFichierCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class OneFichierCom(SimpleHoster):
- __name__ = "OneFichierCom"
- __type__ = "hoster"
- __version__ = "0.83"
-
- __pattern__ = r'https?://(?:www\.)?(?:(?P<ID1>\w+)\.)?(?P<HOST>1fichier\.com|alterupload\.com|cjoint\.net|d(es)?fichiers\.com|dl4free\.com|megadl\.fr|mesfichiers\.org|piecejointe\.net|pjointe\.com|tenvoi\.com)(?:/\?(?P<ID2>\w+))?'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """1fichier.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("the-razer", "daniel_ AT gmx DOT net"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("imclem", None),
- ("stickell", "l.stickell@yahoo.it"),
- ("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Ludovic Lehmann", "ludo.lehmann@gmail.com")]
-
-
- NAME_PATTERN = r'>FileName :</td>\s*<td.*>(?P<N>.+?)<'
- SIZE_PATTERN = r'>Size :</td>\s*<td.*>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'File not found !\s*<'
-
- COOKIES = [("1fichier.com", "LG", "en")]
-
- WAIT_PATTERN = r'>You must wait \d+ minutes'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- id = self.info['pattern']['ID1'] or self.info['pattern']['ID2']
- url, inputs = self.parseHtmlForm('action="https://1fichier.com/\?%s' % id)
-
- if not url:
- self.fail(_("Download link not found"))
-
- if "pass" in inputs:
- inputs['pass'] = self.getPassword()
-
- inputs['submit'] = "Download"
-
- self.download(url, post=inputs)
-
-
- def handlePremium(self, pyfile):
- self.download(pyfile.url, post={'dl': "Download", 'did': 0})
-
-
-getInfo = create_getInfo(OneFichierCom)
diff --git a/module/plugins/hoster/OronCom.py b/module/plugins/hoster/OronCom.py
deleted file mode 100644
index 7002d26ab..000000000
--- a/module/plugins/hoster/OronCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class OronCom(DeadHoster):
- __name__ = "OronCom"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'https?://(?:www\.)?oron\.com/\w{12}'
- __config__ = []
-
- __description__ = """Oron.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("chrox", "chrox@pyload.org"),
- ("DHMH", "DHMH@pyload.org")]
-
-
-getInfo = create_getInfo(OronCom)
diff --git a/module/plugins/hoster/OverLoadMe.py b/module/plugins/hoster/OverLoadMe.py
deleted file mode 100644
index d06baa0f5..000000000
--- a/module/plugins/hoster/OverLoadMe.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class OverLoadMe(MultiHoster):
- __name__ = "OverLoadMe"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https?://.*overload\.me/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Over-Load.me multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def setup(self):
- self.chunkLimit = 5
-
-
- def handlePremium(self, pyfile):
- https = "https" if self.getConfig('ssl') else "http"
- data = self.account.getAccountData(self.user)
- page = self.load(https + "://api.over-load.me/getdownload.php",
- get={'auth': data['password'],
- 'link': pyfile.url})
-
- data = json_loads(page)
-
- self.logDebug(data)
-
- if data['error'] == 1:
- self.logWarning(data['msg'])
- self.tempOffline()
- else:
- if pyfile.name and pyfile.name.endswith('.tmp') and data['filename']:
- pyfile.name = data['filename']
- pyfile.size = parseFileSize(data['filesize'])
-
- http_repl = ["http://", "https://"]
- self.link = data['downloadlink'].replace(*http_repl if self.getConfig('ssl') else *http_repl[::-1])
-
-
-getInfo = create_getInfo(OverLoadMe)
diff --git a/module/plugins/hoster/PandaplaNet.py b/module/plugins/hoster/PandaplaNet.py
deleted file mode 100644
index a5716e4d4..000000000
--- a/module/plugins/hoster/PandaplaNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PandaplaNet(DeadHoster):
- __name__ = "PandaplaNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?pandapla\.net/\w{12}'
- __config__ = []
-
- __description__ = """Pandapla.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(PandaplaNet)
diff --git a/module/plugins/hoster/PornhostCom.py b/module/plugins/hoster/PornhostCom.py
deleted file mode 100644
index 0c3b84a9d..000000000
--- a/module/plugins/hoster/PornhostCom.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhostCom(Hoster):
- __name__ = "PornhostCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?pornhost\.com/(\d+/\d+\.html|\d+)'
-
- __description__ = """Pornhost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de")]
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- # Old interface
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- url = re.search(r'download this file</label>.*?<a href="(.*?)"', self.html)
- if url is None:
- url = re.search(r'"(http://dl\d+\.pornhost\.com/files/.*?/.*?/.*?/.*?/.*?/.*?\..*?)"', self.html)
- if url is None:
- url = re.search(r'width: 894px; height: 675px">.*?<img src="(.*?)"', self.html)
- if url is None:
- url = re.search(r'"http://file\d+\.pornhost\.com/\d+/.*?"',
- self.html) # TODO: fix this one since it doesn't match
-
- return url.group(1).strip()
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- name = re.search(r'<title>pornhost\.com - free file hosting with a twist - gallery(.*?)</title>', self.html)
- if name is None:
- name = re.search(r'id="url" value="http://www\.pornhost\.com/(.*?)/"', self.html)
- if name is None:
- name = re.search(r'<title>pornhost\.com - free file hosting with a twist -(.*?)</title>', self.html)
- if name is None:
- name = re.search(r'"http://file\d+\.pornhost\.com/.*?/(.*?)"', self.html)
-
- name = name.group(1).strip() + ".flv"
-
- return name
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r'gallery not found|You will be redirected to', self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PornhubCom.py b/module/plugins/hoster/PornhubCom.py
deleted file mode 100644
index 16ce36ea9..000000000
--- a/module/plugins/hoster/PornhubCom.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhubCom(Hoster):
- __name__ = "PornhubCom"
- __type__ = "hoster"
- __version__ = "0.50"
-
- __pattern__ = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=\w+'
-
- __description__ = """Pornhub.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de")]
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- url = "http://www.pornhub.com//gateway.php"
- video_id = self.pyfile.url.split('=')[-1]
- # thanks to jD team for this one v
- post_data = "\x00\x03\x00\x00\x00\x01\x00\x0c\x70\x6c\x61\x79\x65\x72\x43\x6f\x6e\x66\x69\x67\x00\x02\x2f\x31\x00\x00\x00\x44\x0a\x00\x00\x00\x03\x02\x00"
- post_data += chr(len(video_id))
- post_data += video_id
- post_data += "\x02\x00\x02\x2d\x31\x02\x00\x20"
- post_data += "add299463d4410c6d1b1c418868225f7"
-
- content = self.load(url, post=str(post_data))
-
- new_content = ""
- for x in content:
- if ord(x) < 32 or ord(x) > 176:
- new_content += '#'
- else:
- new_content += x
-
- content = new_content
-
- return re.search(r'flv_url.*(http.*?)##post_roll', content).group(1)
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- m = re.search(r'<title.+?>([^<]+) - ', self.html)
- if m:
- name = m.group(1)
- else:
- matches = re.findall('<h1>(.*?)</h1>', self.html)
- if len(matches) > 1:
- name = matches[1]
- else:
- name = matches[0]
-
- return name + '.flv'
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r'This video is no longer in our database or is in conversion', self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PotloadCom.py b/module/plugins/hoster/PotloadCom.py
deleted file mode 100644
index 0eed158a4..000000000
--- a/module/plugins/hoster/PotloadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PotloadCom(DeadHoster):
- __name__ = "PotloadCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?potload\.com/\w{12}'
- __config__ = []
-
- __description__ = """Potload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(PotloadCom)
diff --git a/module/plugins/hoster/PremiumTo.py b/module/plugins/hoster/PremiumTo.py
deleted file mode 100644
index ab5016673..000000000
--- a/module/plugins/hoster/PremiumTo.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import fs_encode
-
-
-class PremiumTo(MultiHoster):
- __name__ = "PremiumTo"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Premium.to multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CHECK_TRAFFIC = True
-
-
- def handlePremium(self, pyfile):
- #raise timeout to 2min
- self.download("http://premium.to/api/getfile.php",
- get={'username': self.account.username,
- 'password': self.account.password,
- 'link' : pyfile.url},
- disposition=True)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'nopremium': "No premium account available"}):
- self.retry(60, 5 * 60, "No premium account available")
-
- err = ''
- if self.req.http.code == '420':
- # Custom error code send - fail
- file = fs_encode(self.lastDownload)
- with open(file, "rb") as f:
- err = f.read(256).strip()
- os.remove(file)
-
- if err:
- self.fail(err)
-
- return super(PremiumTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(PremiumTo)
diff --git a/module/plugins/hoster/PremiumizeMe.py b/module/plugins/hoster/PremiumizeMe.py
deleted file mode 100644
index 3227fb001..000000000
--- a/module/plugins/hoster/PremiumizeMe.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class PremiumizeMe(MultiHoster):
- __name__ = "PremiumizeMe"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Premiumize.me multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
-
-
- def handlePremium(self, pyfile):
- # In some cases hostsers do not supply us with a filename at download, so we
- # are going to set a fall back filename (e.g. for freakshare or xfileshare)
- pyfile.name = pyfile.name.split('/').pop() # Remove everthing before last slash
-
- # Correction for automatic assigned filename: Removing html at end if needed
- suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
- temp = pyfile.name.split('.')
- if temp.pop() in suffix_to_remove:
- pyfile.name = ".".join(temp)
-
- # Get account data
- user, data = self.account.selectAccount()
-
- # Get rewritten link using the premiumize.me api v1 (see https://secure.premiumize.me/?show=api)
- data = json_loads(self.load("https://api.premiumize.me/pm-api/v1.php",
- get={'method' : "directdownloadlink",
- 'params[login]': user,
- 'params[pass]' : data['password'],
- 'params[link]' : pyfile.url}))
-
- # Check status and decide what to do
- status = data['status']
-
- if status == 200:
- self.link = data['result']['location']
- return
-
- elif status == 400:
- self.fail(_("Invalid link"))
-
- elif status == 404:
- self.offline()
-
- elif status >= 500:
- self.tempOffline()
-
- else:
- self.fail(data['statusmessage'])
-
-
-getInfo = create_getInfo(PremiumizeMe)
diff --git a/module/plugins/hoster/PromptfileCom.py b/module/plugins/hoster/PromptfileCom.py
deleted file mode 100644
index 3815a1a24..000000000
--- a/module/plugins/hoster/PromptfileCom.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class PromptfileCom(SimpleHoster):
- __name__ = "PromptfileCom"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'https?://(?:www\.)?promptfile\.com/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Promptfile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- INFO_PATTERN = r'<span style=".+?" title=".+?">(?P<N>.*?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</span>'
- OFFLINE_PATTERN = r'<span style=".+?" title="File Not Found">File Not Found</span>'
-
- CHASH_PATTERN = r'<input type="hidden" name="chash" value="(.+?)" />'
- LINK_FREE_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>'
-
-
- def handleFree(self, pyfile):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.error(_("CHASH_PATTERN not found"))
- chash = m.group(1)
- self.logDebug("Read chash %s" % chash)
- # continue to stage2
- self.html = self.load(pyfile.url, decode=True, post={'chash': chash})
-
- # STAGE 2: get the direct link
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(PromptfileCom)
diff --git a/module/plugins/hoster/PrzeklejPl.py b/module/plugins/hoster/PrzeklejPl.py
deleted file mode 100644
index 68fd6ca6d..000000000
--- a/module/plugins/hoster/PrzeklejPl.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PrzeklejPl(DeadHoster):
- __name__ = "PrzeklejPl"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?przeklej\.pl/plik/.+'
- __config__ = []
-
- __description__ = """Przeklej.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(PrzeklejPl)
diff --git a/module/plugins/hoster/PutdriveCom.py b/module/plugins/hoster/PutdriveCom.py
deleted file mode 100644
index 7f4b7b6cc..000000000
--- a/module/plugins/hoster/PutdriveCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.ZeveraCom import ZeveraCom
-
-
-class PutdriveCom(ZeveraCom):
- __name__ = "PutdriveCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)putdrive\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
-
- __description__ = """Multihosters.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/module/plugins/hoster/QuickshareCz.py b/module/plugins/hoster/QuickshareCz.py
deleted file mode 100644
index 1e0750b88..000000000
--- a/module/plugins/hoster/QuickshareCz.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class QuickshareCz(SimpleHoster):
- __name__ = "QuickshareCz"
- __type__ = "hoster"
- __version__ = "0.56"
-
- __pattern__ = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Quickshare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
- SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</td>'
- OFFLINE_PATTERN = r'<script type="text/javascript">location\.href=\'/chyba\';</script>'
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- # parse js variables
- self.jsvars = dict((x, y.strip("'")) for x, y in re.findall(r"var (\w+) = ([\d.]+|'.+?')", self.html))
- self.logDebug(self.jsvars)
- pyfile.name = self.jsvars['ID3']
-
- # determine download type - free or premium
- if self.premium:
- if 'UU_prihlasen' in self.jsvars:
- if self.jsvars['UU_prihlasen'] == '0':
- self.logWarning(_("User not logged in"))
- self.relogin(self.user)
- self.retry()
- elif float(self.jsvars['UU_kredit']) < float(self.jsvars['kredit_odecet']):
- self.logWarning(_("Not enough credit left"))
- self.premium = False
-
- if self.premium:
- self.handlePremium(pyfile)
- else:
- self.handleFree(pyfile)
-
- if self.checkDownload({"error": re.compile(r"\AChyba!")}, max_size=100):
- self.fail(_("File not m or plugin defect"))
-
-
- def handleFree(self, pyfile):
- # get download url
- download_url = '%s/download.php' % self.jsvars['server']
- data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID3", "ID4"))
- self.logDebug("FREE URL1:" + download_url, data)
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.load(download_url, post=data)
- self.header = self.req.http.header
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- m = re.search(r'Location\s*:\s*(.+)', self.header, re.I)
- if m is None:
- self.fail(_("File not found"))
-
- self.link = m.group(1).rstrip() #@TODO: Remove .rstrip() in 0.4.10
- self.logDebug("FREE URL2:" + self.link)
-
- # check errors
- m = re.search(r'/chyba/(\d+)', self.link)
- if m:
- if m.group(1) == '1':
- self.retry(60, 2 * 60, "This IP is already downloading")
- elif m.group(1) == '2':
- self.retry(60, 60, "No free slots available")
- else:
- self.fail(_("Error %d") % m.group(1))
-
-
- def handlePremium(self, pyfile):
- download_url = '%s/download_premium.php' % self.jsvars['server']
- data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID4", "ID5"))
- self.download(download_url, get=data)
-
-
-getInfo = create_getInfo(QuickshareCz)
diff --git a/module/plugins/hoster/RPNetBiz.py b/module/plugins/hoster/RPNetBiz.py
deleted file mode 100644
index 710faf25c..000000000
--- a/module/plugins/hoster/RPNetBiz.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class RPNetBiz(MultiHoster):
- __name__ = "RPNetBiz"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'https?://.+rpnet\.biz'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """RPNet.biz multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def setup(self):
- self.chunkLimit = -1
-
-
- def handlePremium(self, pyfile):
- user, data = self.account.selectAccount()
-
- # Get the download link
- res = self.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user,
- "password": data['password'],
- "action" : "generate",
- "links" : pyfile.url})
-
- self.logDebug("JSON data: %s" % res)
- link_status = json_loads(res)['links'][0] # get the first link... since we only queried one
-
- # Check if we only have an id as a HDD link
- if 'id' in link_status:
- self.logDebug("Need to wait at least 30 seconds before requery")
- self.setWait(30) # wait for 30 seconds
- self.wait()
- # Lets query the server again asking for the status on the link,
- # we need to keep doing this until we reach 100
- max_tries = 30
- my_try = 0
- while (my_try <= max_tries):
- self.logDebug("Try: %d ; Max Tries: %d" % (my_try, max_tries))
- res = self.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user,
- "password": data['password'],
- "action": "downloadInformation",
- "id": link_status['id']})
- self.logDebug("JSON data hdd query: %s" % res)
- download_status = json_loads(res)['download']
-
- if download_status['status'] == '100':
- link_status['generated'] = download_status['rpnet_link']
- self.logDebug("Successfully downloaded to rpnet HDD: %s" % link_status['generated'])
- break
- else:
- self.logDebug("At %s%% for the file download" % download_status['status'])
-
- self.setWait(30)
- self.wait()
- my_try += 1
-
- if my_try > max_tries: # We went over the limit!
- self.fail(_("Waited for about 15 minutes for download to finish but failed"))
-
- if 'generated' in link_status:
- self.link = link_status['generated']
- return
- elif 'error' in link_status:
- self.fail(link_status['error'])
- else:
- self.fail(_("Something went wrong, not supposed to enter here"))
-
-
-getInfo = create_getInfo(RPNetBiz)
diff --git a/module/plugins/hoster/RapideoPl.py b/module/plugins/hoster/RapideoPl.py
deleted file mode 100644
index 50804e8cd..000000000
--- a/module/plugins/hoster/RapideoPl.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class RapideoPl(MultiHoster):
- __name__ = "RapideoPl"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapideo.pl multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
-
- API_URL = "http://enc.rapideo.pl"
-
- API_QUERY = {'site' : "newrd",
- 'output' : "json",
- 'username': "",
- 'password': "",
- 'url' : ""}
-
- ERROR_CODES = {0 : "[%s] Incorrect login credentials",
- 1 : "[%s] Not enough transfer to download - top-up your account",
- 2 : "[%s] Incorrect / dead link",
- 3 : "[%s] Error connecting to hosting, try again later",
- 9 : "[%s] Premium account has expired",
- 15: "[%s] Hosting no longer supported",
- 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
-
-
- def prepare(self):
- super(RapideoPl, self).prepare()
-
- data = self.account.getAccountData(self.user)
-
- self.usr = data['usr']
- self.pwd = data['pwd']
-
-
- def runFileQuery(self, url, mode=None):
- query = self.API_QUERY.copy()
-
- query["username"] = self.usr
- query["password"] = self.pwd
- query["url"] = url
-
- if mode == "fileinfo":
- query['check'] = 2
- query['loc'] = 1
-
- self.logDebug(query)
-
- return self.load(self.API_URL, post=query)
-
-
- def handleFree(self, pyfile):
- try:
- data = self.runFileQuery(pyfile.url, 'fileinfo')
-
- except Exception:
- self.logDebug("RunFileQuery error")
- self.tempOffline()
-
- try:
- parsed = json_loads(data)
-
- except Exception:
- self.logDebug("Loads error")
- self.tempOffline()
-
- self.logDebug(parsed)
-
- if "errno" in parsed.keys():
- if parsed["errno"] in self.ERROR_CODES:
- # error code in known
- self.fail(self.ERROR_CODES[parsed["errno"]] % self.__name__)
- else:
- # error code isn't yet added to plugin
- self.fail(
- parsed["errstring"]
- or _("Unknown error (code: %s)") % parsed["errno"]
- )
-
- if "sdownload" in parsed:
- if parsed["sdownload"] == "1":
- self.fail(
- _("Download from %s is possible only using Rapideo.pl website \
- directly") % parsed["hosting"])
-
- pyfile.name = parsed["filename"]
- pyfile.size = parsed["filesize"]
-
- try:
- self.link = self.runFileQuery(pyfile.url, 'filedownload')
-
- except Exception:
- self.logDebug("runFileQuery error #2")
- self.tempOffline()
diff --git a/module/plugins/hoster/RapidfileshareNet.py b/module/plugins/hoster/RapidfileshareNet.py
deleted file mode 100644
index 0bbaed57f..000000000
--- a/module/plugins/hoster/RapidfileshareNet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class RapidfileshareNet(XFSHoster):
- __name__ = "RapidfileshareNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?rapidfileshare\.net/\w{12}'
-
- __description__ = """Rapidfileshare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'>http://www.rapidfileshare.net/\w+?</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</font>'
-
- OFFLINE_PATTERN = r'>No such file with this filename'
- TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
-
-
-getInfo = create_getInfo(RapidfileshareNet)
diff --git a/module/plugins/hoster/RapidgatorNet.py b/module/plugins/hoster/RapidgatorNet.py
deleted file mode 100644
index ae74e8a63..000000000
--- a/module/plugins/hoster/RapidgatorNet.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.common.json_layer import json_loads
-from module.network.HTTPRequest import BadHeader
-from module.plugins.internal.CaptchaService import AdsCaptcha, ReCaptcha, SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RapidgatorNet(SimpleHoster):
- __name__ = "RapidgatorNet"
- __type__ = "hoster"
- __version__ = "0.33"
-
- __pattern__ = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapidgator.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("chrox", None),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_URL = "http://rapidgator.net/api/file"
-
- COOKIES = [("rapidgator.net", "lang", "en")]
-
- NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
- SIZE_PATTERN = r'File size:\s*<strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong>'
- OFFLINE_PATTERN = r'>(File not found|Error 404)'
-
- JSVARS_PATTERN = r'\s+var\s*(startTimerUrl|getDownloadUrl|captchaUrl|fid|secs)\s*=\s*\'?(.*?)\'?;'
-
- PREMIUM_ONLY_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
- ERROR_PATTERN = r'You have reached your (?:daily|hourly) downloads limit'
- WAIT_PATTERN = r'(Delay between downloads must be not less than|Try again in).+'
-
- LINK_FREE_PATTERN = r'return \'(http://\w+.rapidgator.net/.*)\';'
-
- RECAPTCHA_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
- ADSCAPTCHA_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']+)'
- SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
-
-
- def setup(self):
- if self.account:
- self.sid = self.account.getAccountInfo(self.user).get('sid', None)
- else:
- self.sid = None
-
- if self.sid:
- self.premium = True
-
- self.resumeDownload = self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- def api_response(self, cmd):
- try:
- json = self.load('%s/%s' % (self.API_URL, cmd),
- get={'sid': self.sid,
- 'url': self.pyfile.url}, decode=True)
- self.logDebug("API:%s" % cmd, json, "SID: %s" % self.sid)
- json = json_loads(json)
- status = json['response_status']
- msg = json['response_details']
-
- except BadHeader, e:
- self.logError("API: %s" % cmd, e, "SID: %s" % self.sid)
- status = e.code
- msg = e
-
- if status == 200:
- return json['response']
-
- elif status == 423:
- self.account.empty(self.user)
- self.retry()
-
- else:
- self.account.relogin(self.user)
- self.retry(wait_time=60)
-
-
- def handlePremium(self, pyfile):
- self.api_data = self.api_response('info')
- self.api_data['md5'] = self.api_data['hash']
-
- pyfile.name = self.api_data['filename']
- pyfile.size = self.api_data['size']
-
- self.link = self.api_response('download')['url']
-
-
- def handleFree(self, pyfile):
- jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
- self.logDebug(jsvars)
-
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- url = "http://rapidgator.net%s?fid=%s" % (
- jsvars.get('startTimerUrl', '/download/AjaxStartTimer'), jsvars['fid'])
- jsvars.update(self.getJsonResponse(url))
-
- self.wait(jsvars.get('secs', 45), False)
-
- url = "http://rapidgator.net%s?sid=%s" % (
- jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
- jsvars.update(self.getJsonResponse(url))
-
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
-
- url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
- self.html = self.load(url)
-
- for _i in xrange(5):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- break
- else:
- captcha = self.handleCaptcha()
-
- if not captcha:
- self.error(_("Captcha pattern not found"))
-
- response, challenge = captcha.challenge()
-
- self.html = self.load(url, post={'DownloadCaptchaForm[captcha]': "",
- 'adcopy_challenge' : challenge,
- 'adcopy_response' : response})
-
- if "The verification code is incorrect" in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- else:
- self.error(_("Download link"))
-
-
- def handleCaptcha(self):
- for klass in (AdsCaptcha, ReCaptcha, SolveMedia):
- inst = klass(self)
- if inst.detect_key():
- return inst
-
-
- def getJsonResponse(self, url):
- res = self.load(url, decode=True)
- if not res.startswith('{'):
- self.retry()
- self.logDebug(url, res)
- return json_loads(res)
-
-
-getInfo = create_getInfo(RapidgatorNet)
diff --git a/module/plugins/hoster/RapiduNet.py b/module/plugins/hoster/RapiduNet.py
deleted file mode 100644
index fcccbbebc..000000000
--- a/module/plugins/hoster/RapiduNet.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RapiduNet(SimpleHoster):
- __name__ = "RapiduNet"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapidu.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", "")]
-
-
- COOKIES = [("rapidu.net", "rapidu_lang", "en")]
-
- INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>'
- OFFLINE_PATTERN = r'<h1>404'
-
- ERROR_PATTERN = r'<div class="error">'
-
- RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
-
- def handleFree(self, pyfile):
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
- get={'a': "getLoadTimeToDownload"},
- post={'_go': ""},
- decode=True)
-
- if str(jsvars['timeToDownload']) is "stop":
- t = (24 * 60 * 60) - (int(time.time()) % (24 * 60 * 60)) + time.altzone
-
- self.logInfo("You've reach your daily download transfer")
-
- self.retry(10, 10 if t < 1 else None, _("Try tomorrow again")) #@NOTE: check t in case of not synchronised clock
-
- else:
- self.wait(int(jsvars['timeToDownload']) - int(time.time()))
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
- get={'a': "getCheckCaptcha"},
- post={'_go' : "",
- 'captcha1': challenge,
- 'captcha2': response,
- 'fileId' : self.info['pattern']['ID']},
- decode=True)
-
- if jsvars['message'] == 'success':
- self.link = jsvars['url']
-
-
- def getJsonResponse(self, *args, **kwargs):
- res = self.load(*args, **kwargs)
- if not res.startswith('{'):
- self.retry()
-
- self.logDebug(res)
-
- return json_loads(res)
-
-
-getInfo = create_getInfo(RapiduNet)
diff --git a/module/plugins/hoster/RarefileNet.py b/module/plugins/hoster/RarefileNet.py
deleted file mode 100644
index a45e4ed4d..000000000
--- a/module/plugins/hoster/RarefileNet.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class RarefileNet(XFSHoster):
- __name__ = "RarefileNet"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?rarefile\.net/\w{12}'
-
- __description__ = """Rarefile.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<a href="(.+?)">\1</a>'
-
-
-getInfo = create_getInfo(RarefileNet)
diff --git a/module/plugins/hoster/RealdebridCom.py b/module/plugins/hoster/RealdebridCom.py
deleted file mode 100644
index 4500fcefc..000000000
--- a/module/plugins/hoster/RealdebridCom.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class RealdebridCom(MultiHoster):
- __name__ = "RealdebridCom"
- __type__ = "hoster"
- __version__ = "0.67"
-
- __pattern__ = r'https?://((?:www\.|s\d+\.)?real-debrid\.com/dl/|[\w^_]\.rdb\.so/d/)[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Real-Debrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def setup(self):
- self.chunkLimit = 3
-
-
- def handlePremium(self, pyfile):
- data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php",
- get={'lang' : "en",
- 'link' : pyfile.url,
- 'password': self.getPassword(),
- 'time' : int(time.time() * 1000)}))
-
- self.logDebug("Returned Data: %s" % data)
-
- if data['error'] != 0:
- if data['message'] == "Your file is unavailable on the hoster.":
- self.offline()
- else:
- self.logWarning(data['message'])
- self.tempOffline()
- else:
- if pyfile.name and pyfile.name.endswith('.tmp') and data['file_name']:
- pyfile.name = data['file_name']
- pyfile.size = parseFileSize(data['file_size'])
- self.link = data['generated_links'][0][-1]
-
- if self.getConfig('ssl'):
- self.link = self.link.replace("http://", "https://")
- else:
- self.link = self.link.replace("https://", "http://")
-
-
-getInfo = create_getInfo(RealdebridCom)
diff --git a/module/plugins/hoster/RedtubeCom.py b/module/plugins/hoster/RedtubeCom.py
deleted file mode 100644
index 9051c498a..000000000
--- a/module/plugins/hoster/RedtubeCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.unescape import unescape
-
-
-class RedtubeCom(Hoster):
- __name__ = "RedtubeCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?redtube\.com/\d+'
-
- __description__ = """Redtube.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de")]
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- file_url = unescape(re.search(r'hashlink=(http.*?)"', self.html).group(1))
-
- return file_url
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search('<title>(.*?)- RedTube - Free Porn Videos</title>', self.html).group(1).strip() + ".flv"
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r'This video has been removed.', self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/RemixshareCom.py b/module/plugins/hoster/RemixshareCom.py
deleted file mode 100644
index d60101aed..000000000
--- a/module/plugins/hoster/RemixshareCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://remixshare.com/download/z8uli
-#
-# Note:
-# The remixshare.com website is very very slow, so
-# if your download not starts because of pycurl timeouts:
-# Adjust timeouts in /usr/share/pyload/module/network/HTTPRequest.py
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RemixshareCom(SimpleHoster):
- __name__ = "RemixshareCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://remixshare\.com/(download|dl)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Remixshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de" ),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("sraedler" , "simon.raedler@yahoo.de")]
-
-
- INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>[\w^_]+)\)<'
- HASHSUM_PATTERN = r'>(?P<T>MD5): (?P<H>\w+)'
- OFFLINE_PATTERN = r'<h1>Ooops!'
-
- LINK_PATTERN = r'var uri = "(.+?)"'
- TOKEN_PATTERN = r'var acc = (\d+)'
-
- WAIT_PATTERN = r'var XYZ = "(\d+)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- b = re.search(self.LINK_PATTERN, self.html)
- if not b:
- self.error(_("File url"))
-
- c = re.search(self.TOKEN_PATTERN, self.html)
- if not c:
- self.error(_("File token"))
-
- self.link = b.group(1) + "/zzz/" + c.group(1)
-
-
-getInfo = create_getInfo(RemixshareCom)
diff --git a/module/plugins/hoster/RgHostNet.py b/module/plugins/hoster/RgHostNet.py
deleted file mode 100644
index 1a45def44..000000000
--- a/module/plugins/hoster/RgHostNet.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RgHostNet(SimpleHoster):
- __name__ = "RgHostNet"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?rghost\.(net|ru)/[\d-]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """RgHost.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
- INFO_PATTERN = r'data-share42-text="(?P<N>.+?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- HASHSUM_PATTERN = r'<dt>(?P<T>\w+)</dt>\s*<dd>(?P<H>\w+)'
- OFFLINE_PATTERN = r'>(File is deleted|page not found)'
-
- LINK_FREE_PATTERN = r'<a href="(.+?)" class="btn large'
-
-
-getInfo = create_getInfo(RgHostNet)
diff --git a/module/plugins/hoster/SafesharingEu.py b/module/plugins/hoster/SafesharingEu.py
deleted file mode 100644
index 08612e413..000000000
--- a/module/plugins/hoster/SafesharingEu.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class SafesharingEu(XFSHoster):
- __name__ = "SafesharingEu"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?safesharing\.eu/\w{12}'
-
- __description__ = """Safesharing.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)'
-
-
-getInfo = create_getInfo(SafesharingEu)
diff --git a/module/plugins/hoster/SecureUploadEu.py b/module/plugins/hoster/SecureUploadEu.py
deleted file mode 100644
index 6bfbce328..000000000
--- a/module/plugins/hoster/SecureUploadEu.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class SecureUploadEu(XFSHoster):
- __name__ = "SecureUploadEu"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?secureupload\.eu/\w{12}'
-
- __description__ = """SecureUpload.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
- INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
-
-
-getInfo = create_getInfo(SecureUploadEu)
diff --git a/module/plugins/hoster/SendspaceCom.py b/module/plugins/hoster/SendspaceCom.py
deleted file mode 100644
index 0ab20949d..000000000
--- a/module/plugins/hoster/SendspaceCom.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SendspaceCom(SimpleHoster):
- __name__ = "SendspaceCom"
- __type__ = "hoster"
- __version__ = "0.17"
-
- __pattern__ = r'https?://(?:www\.)?sendspace\.com/file/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Sendspace.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
- SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)\s*</div>'
- OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
-
- LINK_FREE_PATTERN = r'<a id="download_button" href="(.+?)"'
-
- CAPTCHA_PATTERN = r'<td><img src="(/captchas/captcha\.php?captcha=(.+?))"></td>'
- USER_CAPTCHA_PATTERN = r'<td><img src="/captchas/captcha\.php?user=(.+?))"></td>'
-
-
- def handleFree(self, pyfile):
- params = {}
- for _i in xrange(3):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- if 'captcha_hash' in params:
- self.correctCaptcha()
- self.link = m.group(1)
- break
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- if 'captcha_hash' in params:
- self.invalidCaptcha()
- captcha_url1 = "http://www.sendspace.com/" + m.group(1)
- m = re.search(self.USER_CAPTCHA_PATTERN, self.html)
- captcha_url2 = "http://www.sendspace.com/" + m.group(1)
- params = {'captcha_hash': m.group(2),
- 'captcha_submit': 'Verify',
- 'captcha_answer': self.decryptCaptcha(captcha_url1) + " " + self.decryptCaptcha(captcha_url2)}
- else:
- params = {'download': "Regular Download"}
-
- self.logDebug(params)
- self.html = self.load(pyfile.url, post=params)
- else:
- self.fail(_("Download link not found"))
-
-
-getInfo = create_getInfo(SendspaceCom)
diff --git a/module/plugins/hoster/Share4WebCom.py b/module/plugins/hoster/Share4WebCom.py
deleted file mode 100644
index 7a276c1fe..000000000
--- a/module/plugins/hoster/Share4WebCom.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.UnibytesCom import UnibytesCom
-from module.plugins.internal.SimpleHoster import create_getInfo
-
-
-class Share4WebCom(UnibytesCom):
- __name__ = "Share4WebCom"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https?://(?:www\.)?share4web\.com/get/\w+'
-
- __description__ = """Share4web.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "share4web.com"
-
-
-getInfo = create_getInfo(Share4WebCom)
diff --git a/module/plugins/hoster/Share76Com.py b/module/plugins/hoster/Share76Com.py
deleted file mode 100644
index 01bbc5ac2..000000000
--- a/module/plugins/hoster/Share76Com.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class Share76Com(DeadHoster):
- __name__ = "Share76Com"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?share76\.com/\w{12}'
- __config__ = []
-
- __description__ = """Share76.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
-getInfo = create_getInfo(Share76Com)
diff --git a/module/plugins/hoster/ShareFilesCo.py b/module/plugins/hoster/ShareFilesCo.py
deleted file mode 100644
index 2fd1fc7cf..000000000
--- a/module/plugins/hoster/ShareFilesCo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ShareFilesCo(DeadHoster):
- __name__ = "ShareFilesCo"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?sharefiles\.co/\w{12}'
- __config__ = []
-
- __description__ = """Sharefiles.co hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(ShareFilesCo)
diff --git a/module/plugins/hoster/SharebeesCom.py b/module/plugins/hoster/SharebeesCom.py
deleted file mode 100644
index 347ae97e6..000000000
--- a/module/plugins/hoster/SharebeesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SharebeesCom(DeadHoster):
- __name__ = "SharebeesCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?sharebees\.com/\w{12}'
- __config__ = []
-
- __description__ = """ShareBees hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(SharebeesCom)
diff --git a/module/plugins/hoster/ShareonlineBiz.py b/module/plugins/hoster/ShareonlineBiz.py
deleted file mode 100644
index 505222fce..000000000
--- a/module/plugins/hoster/ShareonlineBiz.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urllib
-import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ShareonlineBiz(SimpleHoster):
- __name__ = "ShareonlineBiz"
- __type__ = "hoster"
- __version__ = "0.49"
-
- __pattern__ = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Shareonline.biz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://www.share-online.biz/dl/\g<ID>")]
-
- CHECK_TRAFFIC = True
-
- RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX"
-
- ERROR_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = {'name': urlparse.urlparse(urllib.unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': url}
-
- if url:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict()
-
- field = getURL("http://api.share-online.biz/linkcheck.php",
- get={'md5': "1"},
- post={'links': info['pattern']['ID']},
- decode=True).split(";")
-
- if field[1] == "OK":
- info['fileid'] = field[0]
- info['status'] = 2
- info['name'] = field[2]
- info['size'] = field[3] #: in bytes
- info['md5'] = field[4].strip().lower().replace("\n\n", "") #: md5
-
- elif field[1] in ("DELETED", "NOT FOUND"):
- info['status'] = 1
-
- return info
-
-
- def setup(self):
- self.resumeDownload = self.premium
- self.multiDL = False
-
-
- def handleCaptcha(self):
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- m = re.search(r'var wait=(\d+);', self.html)
- self.setWait(int(m.group(1)) if m else 30)
-
- res = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time.time() * 1000)),
- post={'dl_free' : "1",
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
- if not res == '0':
- self.correctCaptcha()
- return res
- else:
- self.invalidCaptcha()
- else:
- self.invalidCaptcha()
- self.fail(_("No valid captcha solution received"))
-
-
- def handleFree(self, pyfile):
- self.wait(3)
-
- self.html = self.load("%s/free/" % pyfile.url,
- post={'dl_free': "1", 'choice': "free"},
- decode=True)
-
- self.checkErrors()
-
- res = self.handleCaptcha()
- self.link = res.decode('base64')
-
- if not self.link.startswith("http://"):
- self.error(_("Wrong download url"))
-
- self.wait()
-
-
- def checkFile(self, rules={}):
- check = self.checkDownload({'cookie': re.compile(r'<div id="dl_failure"'),
- 'fail' : re.compile(r"<title>Share-Online")})
-
- if check == "cookie":
- self.invalidCaptcha()
- self.retry(5, 60, _("Cookie failure"))
-
- elif check == "fail":
- self.invalidCaptcha()
- self.retry(5, 5 * 60, _("Download failed"))
-
- return super(ShareonlineBiz, self).checkFile(rules)
-
-
- def handlePremium(self, pyfile): #: should be working better loading (account) api internally
- html = self.load("http://api.share-online.biz/account.php",
- get={'username': self.user,
- 'password': self.account.getAccountData(self.user)['password'],
- 'act' : "download",
- 'lid' : self.info['fileid']})
-
- self.api_data = dlinfo = {}
-
- for line in html.splitlines():
- key, value = line.split(": ")
- dlinfo[key.lower()] = value
-
- self.logDebug(dlinfo)
-
- if not dlinfo['status'] == "online":
- self.offline()
- else:
- pyfile.name = dlinfo['name']
- pyfile.size = int(dlinfo['size'])
-
- self.link = dlinfo['url']
-
- if self.link == "server_under_maintenance":
- self.tempOffline()
- else:
- self.multiDL = True
-
-
- def checkErrors(self):
- m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
- if m is None:
- self.info.pop('error', None)
- return
-
- errmsg = m.group(1).lower()
-
- try:
- self.logError(errmsg, re.search(self.ERROR_PATTERN, self.html).group(1))
- except Exception:
- self.logError("Unknown error occurred", errmsg)
-
- if errmsg is "invalid":
- self.fail(_("File not available"))
-
- elif errmsg in ("freelimit", "size", "proxy"):
- self.fail(_("Premium account needed"))
-
- elif errmsg in ("expired", "server"):
- self.retry(wait_time=600, reason=errmsg)
-
- elif 'slot' in errmsg:
- self.wantReconnect = True
- self.retry(24, 3600, errmsg)
-
- else:
- self.wantReconnect = True
- self.retry(wait_time=60, reason=errmsg)
-
-
-getInfo = create_getInfo(ShareonlineBiz)
diff --git a/module/plugins/hoster/ShareplaceCom.py b/module/plugins/hoster/ShareplaceCom.py
deleted file mode 100644
index babd0d1d4..000000000
--- a/module/plugins/hoster/ShareplaceCom.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.Hoster import Hoster
-
-
-class ShareplaceCom(Hoster):
- __name__ = "ShareplaceCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?shareplace\.(com|org)/\?\w+'
-
- __description__ = """Shareplace.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("ACCakut", None)]
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.prepare()
- self.download(self.get_file_url())
-
-
- def prepare(self):
- if not self.file_exists():
- self.offline()
-
- self.pyfile.name = self.get_file_name()
-
- wait_time = self.get_waiting_time()
- self.setWait(wait_time)
- self.wait()
-
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- #var zzipitime = 15;
- m = re.search(r'var zzipitime = (\d+);', self.html)
- if m:
- sec = int(m.group(1))
- else:
- sec = 0
-
- return sec
-
-
- def download_html(self):
- url = re.sub("shareplace.com\/\?", "shareplace.com//index1.php/?a=", self.pyfile.url)
- self.html = self.load(url, decode=True)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- url = re.search(r"var beer = '(.*?)';", self.html)
- if url:
- url = url.group(1)
- url = urllib.unquote(
- url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
- "teletubbies", ""))
- self.logDebug("URL: %s" % url)
- return url
- else:
- self.error(_("Absolute filepath not found"))
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search("<title>\s*(.*?)\s*</title>", self.html).group(1)
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r"HTTP Status 404", self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/SharingmatrixCom.py b/module/plugins/hoster/SharingmatrixCom.py
deleted file mode 100644
index 925c6af8d..000000000
--- a/module/plugins/hoster/SharingmatrixCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SharingmatrixCom(DeadHoster):
- __name__ = "SharingmatrixCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sharingmatrix\.com/file/\w+'
- __config__ = []
-
- __description__ = """Sharingmatrix.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("paulking", None)]
-
-
-getInfo = create_getInfo(SharingmatrixCom)
diff --git a/module/plugins/hoster/ShragleCom.py b/module/plugins/hoster/ShragleCom.py
deleted file mode 100644
index 9a4a259d8..000000000
--- a/module/plugins/hoster/ShragleCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ShragleCom(DeadHoster):
- __name__ = "ShragleCom"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?(cloudnator|shragle)\.com/files/(?P<ID>.+?)/'
- __config__ = []
-
- __description__ = """Cloudnator.com (Shragle.com) hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(ShragleCom)
diff --git a/module/plugins/hoster/SimplyPremiumCom.py b/module/plugins/hoster/SimplyPremiumCom.py
deleted file mode 100644
index b3f52bc8c..000000000
--- a/module/plugins/hoster/SimplyPremiumCom.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-
-
-class SimplyPremiumCom(MultiHoster):
- __name__ = "SimplyPremiumCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://.+simply-premium\.com'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Simply-Premium.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def setup(self):
- self.chunkLimit = 16
-
-
- def checkErrors(self):
- if '<valid>0</valid>' in self.html or (
- "You are not allowed to download from this host" in self.html and self.premium):
- self.account.relogin(self.user)
- self.retry()
-
- elif "NOTFOUND" in self.html:
- self.offline()
-
- elif "downloadlimit" in self.html:
- self.logWarning(_("Reached maximum connctions"))
- self.retry(5, 60, _("Reached maximum connctions"))
-
- elif "trafficlimit" in self.html:
- self.logWarning(_("Reached daily limit for this host"))
- self.retry(wait_time=secondsToMidnight(gmt=2), reason="Daily limit for this host reached")
-
- elif "hostererror" in self.html:
- self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
- self.retry(5, 60, _("Hoster is temporarily unavailable"))
-
-
- def handlePremium(self, pyfile):
- for i in xrange(5):
- self.html = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': self.pyfile.url})
-
- if self.html:
- self.logDebug("JSON data: " + self.html)
- break
- else:
- self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
- self.retry(5, 60, _("Unable to get API data"))
-
- self.checkErrors()
-
- try:
- self.pyfile.name = re.search(r'<name>([^<]+)</name>', self.html).group(1)
-
- except AttributeError:
- self.pyfile.name = ""
-
- try:
- self.pyfile.size = re.search(r'<size>(\d+)</size>', self.html).group(1)
-
- except AttributeError:
- self.pyfile.size = 0
-
- try:
- self.link = re.search(r'<download>([^<]+)</download>', self.html).group(1)
-
- except AttributeError:
- self.link = 'http://www.simply-premium.com/premium.php?link=' + self.pyfile.url
-
-
-getInfo = create_getInfo(SimplyPremiumCom)
diff --git a/module/plugins/hoster/SimplydebridCom.py b/module/plugins/hoster/SimplydebridCom.py
deleted file mode 100644
index aae71d983..000000000
--- a/module/plugins/hoster/SimplydebridCom.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo, replace_patterns
-
-
-class SimplydebridCom(MultiHoster):
- __name__ = "SimplydebridCom"
- __type__ = "hoster"
- __version__ = "0.17"
-
- __pattern__ = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Simply-debrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def handlePremium(self, pyfile):
- #fix the links for simply-debrid.com!
- self.link = replace_patterns(pyfile.url, [("clz.to", "cloudzer.net/file")
- ("http://share-online", "http://www.share-online")
- ("ul.to", "uploaded.net/file")
- ("uploaded.com", "uploaded.net")
- ("filerio.com", "filerio.in")
- ("lumfile.com", "lumfile.se")])
-
- if 'fileparadox' in self.link:
- self.link = self.link.replace("http://", "https://")
-
- self.html = self.load("http://simply-debrid.com/api.php", get={'dl': self.link})
- if 'tiger Link' in self.html or 'Invalid Link' in self.html or ('API' in self.html and 'ERROR' in self.html):
- self.error(_("Unable to unrestrict link"))
-
- self.link = self.html
-
- self.wait(5)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({"error": "No address associated with hostname"}):
- self.retry(24, 3 * 60, _("Bad file downloaded"))
-
- return super(SimplydebridCom, self).checkFile(rules)
-
-
-getInfo = create_getInfo(SimplydebridCom)
diff --git a/module/plugins/hoster/SmoozedCom.py b/module/plugins/hoster/SmoozedCom.py
deleted file mode 100644
index 5d453cf57..000000000
--- a/module/plugins/hoster/SmoozedCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class SmoozedCom(MultiHoster):
- __name__ = "SmoozedCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Smoozed.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def handlePremium(self, pyfile):
- # In some cases hostsers do not supply us with a filename at download, so we
- # are going to set a fall back filename (e.g. for freakshare or xfileshare)
- pyfile.name = pyfile.name.split('/').pop() # Remove everthing before last slash
-
- # Correction for automatic assigned filename: Removing html at end if needed
- suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
- temp = pyfile.name.split('.')
-
- if temp.pop() in suffix_to_remove:
- pyfile.name = ".".join(temp)
-
- # Check the link
- get_data = {'session_key': self.account.getAccountInfo(self.user)['session'],
- 'url' : pyfile.url}
-
- data = json_loads(self.load("http://www2.smoozed.com/api/check", get=get_data))
-
- if data["state"] != "ok":
- self.fail(data["message"])
-
- if data["data"].get("state", "ok") != "ok":
- if data["data"] == "Offline":
- self.offline()
- else:
- self.fail(data["data"]["message"])
-
- pyfile.name = data["data"]["name"]
- pyfile.size = int(data["data"]["size"])
-
- # Start the download
- header = self.load("http://www2.smoozed.com/api/download", get=get_data, just_header=True)
-
- if not "location" in header:
- self.fail(_("Unable to initialize download"))
- else:
- self.link = header["location"][-1] if isinstance(header["location"], list) else header["location"]
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'error': '{"state":"error"}',
- 'retry': '{"state":"retry"}'}):
- self.fail(_("Error response received"))
-
- return super(SmoozedCom, self).checkFile(rules)
diff --git a/module/plugins/hoster/SockshareCom.py b/module/plugins/hoster/SockshareCom.py
deleted file mode 100644
index 574c66d5b..000000000
--- a/module/plugins/hoster/SockshareCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SockshareCom(DeadHoster):
- __name__ = "SockshareCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Sockshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(SockshareCom)
diff --git a/module/plugins/hoster/SolidfilesCom.py b/module/plugins/hoster/SolidfilesCom.py
deleted file mode 100644
index d359577d6..000000000
--- a/module/plugins/hoster/SolidfilesCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.solidfiles.com/d/609cdb4b1b
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SolidfilesCom(SimpleHoster):
- __name__ = "SolidfilesCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?solidfiles\.com\/d/\w+'
-
- __description__ = """Solidfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sraedler", "simon.raedler@yahoo.de")]
-
-
- NAME_PATTERN = r'<h1 title="(?P<N>.+?)"'
- SIZE_PATTERN = r'<p class="meta">(?P<S>[\d.,]+) (?P<U>[\w_^]+)'
- OFFLINE_PATTERN = r'<h1>404'
-
- LINK_FREE_PATTERN = r'id="ddl-text" href="(.+?)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(SolidfilesCom)
diff --git a/module/plugins/hoster/SoundcloudCom.py b/module/plugins/hoster/SoundcloudCom.py
deleted file mode 100644
index 8dff4f42a..000000000
--- a/module/plugins/hoster/SoundcloudCom.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class SoundcloudCom(SimpleHoster):
- __name__ = "SoundcloudCom"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https?://(?:www\.)?soundcloud\.com/[\w-]+/[\w-]+'
- __config__ = [("use_premium", "bool" , "Use premium account if available", True ),
- ("quality" , "Lower;Higher", "Quality" , "Higher")]
-
- __description__ = """SoundCloud.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'title" content="(?P<N>.+?)"'
- OFFLINE_PATTERN = r'<title>"SoundCloud - Hear the world’s sounds"</title>'
-
-
- def handleFree(self, pyfile):
- try:
- song_id = re.search(r'sounds:(\d+)"', self.html).group(1)
-
- except Exception:
- self.error(_("Could not find song id"))
-
- try:
- client_id = re.search(r'"clientID":"(.+?)"', self.html).group(1)
-
- except Exception:
- client_id = "b45b1aa10f1ac2941910a7f0d10f8e28"
-
- # url to retrieve the actual song url
- streams = json_loads(self.load("https://api.soundcloud.com/tracks/%s/streams" % song_id,
- get={'client_id': client_id}))
-
- regex = re.compile(r'[^\d]')
- http_streams = sorted([(key, value) for key, value in streams.iteritems() if key.startswith('http_')],
- key=lambda t: regex.sub(t[0], ''),
- reverse=True)
-
- self.logDebug("Streams found: %s" % (http_streams or "None"))
-
- if http_streams:
- stream_name, self.link = http_streams[0 if self.getConfig('quality') == "Higher" else -1]
- pyfile.name += '.' + stream_name.split('_')[1].lower()
-
-
-getInfo = create_getInfo(SoundcloudCom)
diff --git a/module/plugins/hoster/SpeedLoadOrg.py b/module/plugins/hoster/SpeedLoadOrg.py
deleted file mode 100644
index 08a87e455..000000000
--- a/module/plugins/hoster/SpeedLoadOrg.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SpeedLoadOrg(DeadHoster):
- __name__ = "SpeedLoadOrg"
- __type__ = "hoster"
- __version__ = "1.02"
-
- __pattern__ = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Speedload.org hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/module/plugins/hoster/SpeedfileCz.py b/module/plugins/hoster/SpeedfileCz.py
deleted file mode 100644
index d3d627505..000000000
--- a/module/plugins/hoster/SpeedfileCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SpeedfileCz(DeadHoster):
- __name__ = "SpeedFileCz"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __pattern__ = r'http://(?:www\.)?speedfile\.cz/.+'
- __config__ = []
-
- __description__ = """Speedfile.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(SpeedfileCz)
diff --git a/module/plugins/hoster/SpeedyshareCom.py b/module/plugins/hoster/SpeedyshareCom.py
deleted file mode 100644
index 91788b2c8..000000000
--- a/module/plugins/hoster/SpeedyshareCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SpeedyshareCom(SimpleHoster):
- __name__ = "SpeedyshareCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Speedyshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
- SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
-
- OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
-
- LINK_FREE_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload\.png alt=\'Slow Download\' border=0'
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(SpeedyshareCom)
diff --git a/module/plugins/hoster/StorageTo.py b/module/plugins/hoster/StorageTo.py
deleted file mode 100644
index f9bba7822..000000000
--- a/module/plugins/hoster/StorageTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class StorageTo(DeadHoster):
- __name__ = "StorageTo"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?storage\.to/get/.+'
- __config__ = []
-
- __description__ = """Storage.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(StorageTo)
diff --git a/module/plugins/hoster/StreamCz.py b/module/plugins/hoster/StreamCz.py
deleted file mode 100644
index 97bed8109..000000000
--- a/module/plugins/hoster/StreamCz.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-
-
-def getInfo(urls):
- result = []
-
- for url in urls:
-
- html = getURL(url)
- if re.search(StreamCz.OFFLINE_PATTERN, html):
- # File offline
- result.append((url, 0, 1, url))
- else:
- result.append((url, 0, 2, url))
- yield result
-
-
-class StreamCz(Hoster):
- __name__ = "StreamCz"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+'
-
- __description__ = """Stream.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<link rel="video_src" href="http://www\.stream\.cz/\w+/(\d+)-(.+?)" />'
- OFFLINE_PATTERN = r'<h1 class="commonTitle">Str.nku nebylo mo.n. nal.zt \(404\)</h1>'
-
- CDN_PATTERN = r'<param name="flashvars" value=".+?&id=(?P<ID>\d+)(?:&cdnLQ=(?P<cdnLQ>\d*))?(?:&cdnHQ=(?P<cdnHQ>\d*))?(?:&cdnHD=(?P<cdnHD>\d*))?&'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- if re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
-
- m = re.search(self.CDN_PATTERN, self.html)
- if m is None:
- self.error(_("CDN_PATTERN not found"))
- cdn = m.groupdict()
- self.logDebug(cdn)
- for cdnkey in ("cdnHD", "cdnHQ", "cdnLQ"):
- if cdnkey in cdn and cdn[cdnkey] > '':
- cdnid = cdn[cdnkey]
- break
- else:
- self.fail(_("Stream URL not found"))
-
- m = re.search(self.NAME_PATTERN, self.html)
- if m is None:
- self.error(_("NAME_PATTERN not found"))
- pyfile.name = "%s-%s.%s.mp4" % (m.group(2), m.group(1), cdnkey[-2:])
-
- download_url = "http://cdn-dispatcher.stream.cz/?id=" + cdnid
- self.logInfo(_("STREAM: %s") % cdnkey[-2:], download_url)
- self.download(download_url)
diff --git a/module/plugins/hoster/StreamcloudEu.py b/module/plugins/hoster/StreamcloudEu.py
deleted file mode 100644
index 54f430508..000000000
--- a/module/plugins/hoster/StreamcloudEu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class StreamcloudEu(XFSHoster):
- __name__ = "StreamcloudEu"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?streamcloud\.eu/\w{12}'
-
- __description__ = """Streamcloud.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("seoester", "seoester@googlemail.com")]
-
-
- WAIT_PATTERN = r'var count = (\d+)'
-
- LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud\.eu:?\d*/.*/video\.(mp4|flv))",'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
-getInfo = create_getInfo(StreamcloudEu)
diff --git a/module/plugins/hoster/TurbobitNet.py b/module/plugins/hoster/TurbobitNet.py
deleted file mode 100644
index 7995bd0c0..000000000
--- a/module/plugins/hoster/TurbobitNet.py
+++ /dev/null
@@ -1,168 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import pycurl
-import random
-import re
-import time
-import urllib
-
-from Crypto.Cipher import ARC4
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
-
-
-class TurbobitNet(SimpleHoster):
- __name__ = "TurbobitNet"
- __type__ = "hoster"
- __version__ = "0.19"
-
- __pattern__ = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """ Turbobit.net hoster plugin """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("prOq", None)]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://turbobit.net/\g<ID>.html")]
-
- COOKIES = [("turbobit.net", "user_lang", "en")]
-
- NAME_PATTERN = r'id="file-title">(?P<N>.+?)<'
- SIZE_PATTERN = r'class="file-size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'(/download/redirect/[^"\']+)'
-
- LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<'
- CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"'
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://turbobit.net/download/free/%s" % self.info['pattern']['ID'],
- decode=True)
-
- rtUpdate = self.getRtUpdate()
-
- self.solveCaptcha()
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- self.html = self.load(self.getDownloadUrl(rtUpdate))
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
-
-
- def solveCaptcha(self):
- for _i in xrange(5):
- m = re.search(self.LIMIT_WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1))
- self.wait(wait_time, wait_time > 60)
- self.retry()
-
- action, inputs = self.parseHtmlForm("action='#'")
- if not inputs:
- self.error(_("Captcha form not found"))
- self.logDebug(inputs)
-
- if inputs['captcha_type'] == 'recaptcha':
- recaptcha = ReCaptcha(self)
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
- else:
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("captcha"))
- captcha_url = m.group(1)
- inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
-
- self.logDebug(inputs)
- self.html = self.load(self.url, post=inputs)
-
- if '<div class="captcha-error">Incorrect, try again!<' in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("Invalid captcha"))
-
-
- def getRtUpdate(self):
- rtUpdate = self.getStorage("rtUpdate")
- if not rtUpdate:
- if self.getStorage("version") != self.__version__ \
- or int(self.getStorage("timestamp", 0)) + 86400000 < timestamp():
- # that's right, we are even using jdownloader updates
- rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js")
- rtUpdate = self.decrypt(rtUpdate.splitlines()[1])
- # but we still need to fix the syntax to work with other engines than rhino
- rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{',
- r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];', rtUpdate)
- rtUpdate = re.sub(r"for\((\w+)=", r"for(var \1=", rtUpdate)
-
- self.setStorage("rtUpdate", rtUpdate)
- self.setStorage("timestamp", timestamp())
- self.setStorage("version", self.__version__)
- else:
- self.logError(_("Unable to download, wait for update..."))
- self.tempOffline()
-
- return rtUpdate
-
-
- def getDownloadUrl(self, rtUpdate):
- self.req.http.lastURL = self.url
-
- m = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html)
- if m:
- url = "http://turbobit.net%s%s" % m.groups()
- else:
- url = "http://turbobit.net/files/timeout.js?ver=%s" % "".join(random.choice('0123456789ABCDEF') for _i in xrange(32))
-
- fun = self.load(url)
-
- self.setWait(65, False)
-
- for b in [1, 3]:
- self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % (
- self.info['pattern']['ID'], b, urllib.quote(fun), rtUpdate)
-
- try:
- out = self.js.eval(self.jscode)
- self.logDebug("URL", self.js.engine, out)
- if out.startswith('/download/'):
- return "http://turbobit.net%s" % out.strip()
-
- except Exception, e:
- self.logError(e)
- else:
- if self.retries >= 2:
- # retry with updated js
- self.delStorage("rtUpdate")
- else:
- self.retry()
-
- self.wait()
-
-
- def decrypt(self, data):
- cipher = ARC4.new(binascii.hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
- return binascii.unhexlify(cipher.encrypt(binascii.unhexlify(data)))
-
-
- def getLocalTimeString(self):
- lt = time.localtime()
- tz = time.altzone if lt.tm_isdst else time.timezone
- return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600)
-
-
-getInfo = create_getInfo(TurbobitNet)
diff --git a/module/plugins/hoster/TurbouploadCom.py b/module/plugins/hoster/TurbouploadCom.py
deleted file mode 100644
index 4b4d0c2e3..000000000
--- a/module/plugins/hoster/TurbouploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class TurbouploadCom(DeadHoster):
- __name__ = "TurbouploadCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?turboupload\.com/(\w+)'
- __config__ = []
-
- __description__ = """Turboupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(TurbouploadCom)
diff --git a/module/plugins/hoster/TusfilesNet.py b/module/plugins/hoster/TusfilesNet.py
deleted file mode 100644
index 6021a4c30..000000000
--- a/module/plugins/hoster/TusfilesNet.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.HTTPRequest import BadHeader
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class TusfilesNet(XFSHoster):
- __name__ = "TusfilesNet"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/\w{12}'
-
- __description__ = """Tusfiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
- INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\['
- OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!|The file you are trying to download is no longer available'
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def downloadLink(self, link, disposition=True):
- try:
- return super(TusfilesNet, self).downloadLink(link, disposition)
-
- except BadHeader, e:
- if e.code is 503:
- self.multiDL = False
- raise Retry("503")
-
-
-getInfo = create_getInfo(TusfilesNet)
diff --git a/module/plugins/hoster/TwoSharedCom.py b/module/plugins/hoster/TwoSharedCom.py
deleted file mode 100644
index 086228637..000000000
--- a/module/plugins/hoster/TwoSharedCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class TwoSharedCom(SimpleHoster):
- __name__ = "TwoSharedCom"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """2Shared.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
- SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
-
- LINK_FREE_PATTERN = r'window.location =\'(.+?)\';'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
-getInfo = create_getInfo(TwoSharedCom)
diff --git a/module/plugins/hoster/UlozTo.py b/module/plugins/hoster/UlozTo.py
deleted file mode 100644
index c48873387..000000000
--- a/module/plugins/hoster/UlozTo.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-def convertDecimalPrefix(m):
- # decimal prefixes used in filesize and traffic
- return ("%%.%df" % {'k': 3, 'M': 6, 'G': 9}[m.group(2)] % float(m.group(1))).replace('.', '')
-
-
-class UlozTo(SimpleHoster):
- __name__ = "UlozTo"
- __type__ = "hoster"
- __version__ = "1.08"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<ID>\w+/[^/?]*)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uloz.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
- NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz\.to</title>'
- SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[\d.,]+\s[kMG]?B)</span>'
- OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
-
- URL_REPLACEMENTS = [(r'(?<=http://)([^/]+)', "www.ulozto.net")]
- SIZE_REPLACEMENTS = [(r'([\d.]+)\s([kMG])B', convertDecimalPrefix)]
-
- CHECK_TRAFFIC = True
-
- ADULT_PATTERN = r'<form action="(.+?)" method="post" id="frm-askAgeForm">'
- PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
- VIPLINK_PATTERN = r'<a href=".+?\?disclaimer=1" class="linkVip">'
- TOKEN_PATTERN = r'<input type="hidden" name="_token_" .*?value="(.+?)"'
-
-
- def setup(self):
- self.chunkLimit = 16 if self.premium else 1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
- if not action or not inputs:
- self.error(_("Free download form not found"))
-
- self.logDebug("inputs.keys = " + str(inputs.keys()))
- # get and decrypt captcha
- if all(key in inputs for key in ("captcha_value", "captcha_id", "captcha_key")):
- # Old version - last seen 9.12.2013
- self.logDebug('Using "old" version')
-
- captcha_value = self.decryptCaptcha("http://img.uloz.to/captcha/%s.png" % inputs['captcha_id'])
- self.logDebug("CAPTCHA ID: " + inputs['captcha_id'] + ", CAPTCHA VALUE: " + captcha_value)
-
- inputs.update({'captcha_id': inputs['captcha_id'], 'captcha_key': inputs['captcha_key'], 'captcha_value': captcha_value})
-
- elif all(key in inputs for key in ("captcha_value", "timestamp", "salt", "hash")):
- # New version - better to get new parameters (like captcha reload) because of image url - since 6.12.2013
- self.logDebug('Using "new" version')
-
- xapca = self.load("http://www.ulozto.net/reloadXapca.php", get={'rnd': str(int(time.time()))})
- self.logDebug("xapca = " + str(xapca))
-
- data = json_loads(xapca)
- captcha_value = self.decryptCaptcha(str(data['image']))
- self.logDebug("CAPTCHA HASH: " + data['hash'], "CAPTCHA SALT: " + str(data['salt']), "CAPTCHA VALUE: " + captcha_value)
-
- inputs.update({'timestamp': data['timestamp'], 'salt': data['salt'], 'hash': data['hash'], 'captcha_value': captcha_value})
-
- else:
- self.error(_("CAPTCHA form changed"))
-
- self.download("http://www.ulozto.net" + action, post=inputs)
-
-
- def handlePremium(self, pyfile):
- self.download(pyfile.url, get={'do': "directDownload"})
-
-
- def checkErrors(self):
- if re.search(self.ADULT_PATTERN, self.html):
- self.logInfo(_("Adult content confirmation needed"))
-
- m = re.search(self.TOKEN_PATTERN, self.html)
- if m is None:
- self.error(_("TOKEN_PATTERN not found"))
-
- self.html = self.load(pyfile.url,
- get={'do': "askAgeForm-submit"},
- post={"agree": "Confirm", "_token_": m.group(1)})
-
- if self.PASSWD_PATTERN in self.html:
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + password)
- self.html = self.load(pyfile.url,
- get={'do': "passwordProtectedForm-submit"},
- post={"password": password, "password_send": 'Send'})
-
- if self.PASSWD_PATTERN in self.html:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- if re.search(self.VIPLINK_PATTERN, self.html):
- self.html = self.load(pyfile.url, get={'disclaimer': "1"})
-
- return super(UlozTo, self).checkErrors()
-
-
- def checkFile(self, rules={}):
- check = self.checkDownload({
- "wrong_captcha": re.compile(r'<ul class="error">\s*<li>Error rewriting the text.</li>'),
- "offline" : re.compile(self.OFFLINE_PATTERN),
- "passwd" : self.PASSWD_PATTERN,
- "server_error" : 'src="http://img.ulozto.cz/error403/vykricnik.jpg"', # paralell dl, server overload etc.
- "not_found" : "<title>UloÅŸ.to</title>"
- })
-
- if check == "wrong_captcha":
- self.invalidCaptcha()
- self.retry(reason=_("Wrong captcha code"))
-
- elif check == "offline":
- self.offline()
-
- elif check == "passwd":
- self.fail(_("Wrong password"))
-
- elif check == "server_error":
- self.logError(_("Server error, try downloading later"))
- self.multiDL = False
- self.wait(1 * 60 * 60, True)
- self.retry()
-
- elif check == "not_found":
- self.fail(_("Server error, file not downloadable"))
-
- return super(UlozTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UlozTo)
diff --git a/module/plugins/hoster/UloziskoSk.py b/module/plugins/hoster/UloziskoSk.py
deleted file mode 100644
index d687ae7cb..000000000
--- a/module/plugins/hoster/UloziskoSk.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UloziskoSk(SimpleHoster):
- __name__ = "UloziskoSk"
- __type__ = "hoster"
- __version__ = "0.25"
-
- __pattern__ = r'http://(?:www\.)?ulozisko\.sk/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Ulozisko.sk hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
- SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong><br />'
- OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
-
- LINK_FREE_PATTERN = r'<form name = "formular" action = "(.+?)" method = "post">'
- ID_PATTERN = r'<input type = "hidden" name = "id" value = "(.+?)" />'
- CAPTCHA_PATTERN = r'<img src="(/obrazky/obrazky\.php\?fid=.+?)" alt="" />'
- IMG_PATTERN = ur'<strong>PRE ZVÄČŠENIE KLIKNITE NA OBRÁZOK</strong><br /><a href = "(.+?)">'
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- m = re.search(self.IMG_PATTERN, self.html)
- if m:
- self.link = "http://ulozisko.sk" + m.group(1)
- else:
- self.handleFree(pyfile)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
- parsed_url = 'http://www.ulozisko.sk' + m.group(1)
-
- m = re.search(self.ID_PATTERN, self.html)
- if m is None:
- self.error(_("ID_PATTERN not found"))
- id = m.group(1)
-
- self.logDebug("URL:" + parsed_url + ' ID:' + id)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("CAPTCHA_PATTERN not found"))
- captcha_url = 'http://www.ulozisko.sk' + m.group(1)
-
- captcha = self.decryptCaptcha(captcha_url, cookies=True)
-
- self.logDebug("CAPTCHA_URL:" + captcha_url + ' CAPTCHA:' + captcha)
-
- self.download(parsed_url,
- post={"antispam": captcha,
- "id" : id,
- "name" : pyfile.name,
- "but" : "++++STIAHNI+S%DABOR++++"})
-
-
-getInfo = create_getInfo(UloziskoSk)
diff --git a/module/plugins/hoster/UnibytesCom.py b/module/plugins/hoster/UnibytesCom.py
deleted file mode 100644
index d090c8e7d..000000000
--- a/module/plugins/hoster/UnibytesCom.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UnibytesCom(SimpleHoster):
- __name__ = "UnibytesCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """UniBytes.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "unibytes.com"
-
- INFO_PATTERN = r'<span[^>]*?id="fileName".*?>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
-
- WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
- LINK_FREE_PATTERN = r'<a href="(.+?)">Download</a>'
-
-
- def handleFree(self, pyfile):
- domain = "http://www.%s/" % self.HOSTER_DOMAIN
- action, post_data = self.parseHtmlForm('id="startForm"')
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- for _i in xrange(8):
- self.logDebug(action, post_data)
- self.html = self.load(urlparse.urljoin(domain, action), post=post_data)
-
- m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
- if m:
- self.link = m.group(1)
- break
-
- if '>Somebody else is already downloading using your IP-address<' in self.html:
- self.wait(10 * 60, True)
- self.retry()
-
- if post_data['step'] == 'last':
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
-
- last_step = post_data['step']
- action, post_data = self.parseHtmlForm('id="stepForm"')
-
- if last_step == 'timer':
- m = re.search(self.WAIT_PATTERN, self.html)
- self.wait(m.group(1) if m else 60, False)
-
- elif last_step in ("captcha", "last"):
- post_data['captcha'] = self.decryptCaptcha(urlparse.urljoin(domain, "/captcha.jpg"))
-
- else:
- self.fail(_("No valid captcha code entered"))
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-
-getInfo = create_getInfo(UnibytesCom)
diff --git a/module/plugins/hoster/UnrestrictLi.py b/module/plugins/hoster/UnrestrictLi.py
deleted file mode 100644
index dab5abd44..000000000
--- a/module/plugins/hoster/UnrestrictLi.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-
-
-class UnrestrictLi(MultiHoster):
- __name__ = "UnrestrictLi"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'https?://(?:www\.)?(unrestrict|unr)\.li/dl/[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Unrestrict.li multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LOGIN_ACCOUNT = False
-
-
- def setup(self):
- self.chunkLimit = 16
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- for _i in xrange(5):
- self.html = self.load('https://unrestrict.li/unrestrict.php',
- post={'link': pyfile.url, 'domain': 'long'})
-
- self.logDebug("JSON data: " + self.html)
-
- if self.html:
- break
- else:
- self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
- self.retry(5, 60, "Unable to get API data")
-
- if 'Expired session' in self.html \
- or ("You are not allowed to download from this host" in self.html and self.premium):
- self.account.relogin(self.user)
- self.retry()
-
- elif "File offline" in self.html:
- self.offline()
-
- elif "You are not allowed to download from this host" in self.html:
- self.fail(_("You are not allowed to download from this host"))
-
- elif "You have reached your daily limit for this host" in self.html:
- self.logWarning(_("Reached daily limit for this host"))
- self.retry(5, secondsToMidnight(gmt=2), "Daily limit for this host reached")
-
- elif "ERROR_HOSTER_TEMPORARILY_UNAVAILABLE" in self.html:
- self.logInfo(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
- self.retry(5, 60, "Hoster is temporarily unavailable")
-
- self.html = json_loads(self.html)
- self.link = self.html.keys()[0]
- self.api_data = self.html[self.link]
-
- if hasattr(self, 'api_data'):
- self.setNameSize()
-
-
- def checkFile(self, rules={}):
- super(UnrestrictLi, self).checkFile(rules)
-
- if self.getConfig('history'):
- self.load("https://unrestrict.li/history/", get={'delete': "all"})
- self.logInfo(_("Download history deleted"))
-
-
- def setNameSize(self):
- if 'name' in self.api_data:
- self.pyfile.name = self.api_data['name']
- if 'size' in self.api_data:
- self.pyfile.size = self.api_data['size']
-
-
-getInfo = create_getInfo(UnrestrictLi)
diff --git a/module/plugins/hoster/UpleaCom.py b/module/plugins/hoster/UpleaCom.py
deleted file mode 100644
index 9d460ef98..000000000
--- a/module/plugins/hoster/UpleaCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class UpleaCom(XFSHoster):
- __name__ = "UpleaCom"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:www\.)?uplea\.com/dl/\w{15}'
-
- __description__ = """Uplea.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Redleon", None),
- ("GammaC0de", None)]
-
-
- DISPOSITION = False #@TODO: Remove in 0.4.10
-
- HOSTER_DOMAIN = "uplea.com"
-
- SIZE_REPLACEMENTS = [('ko','KB'), ('mo','MB'), ('go','GB'), ('Ko','KB'), ('Mo','MB'), ('Go','GB')]
-
- NAME_PATTERN = r'<span class="gold-text">(?P<N>.+?)</span>'
- SIZE_PATTERN = r'<span class="label label-info agmd">(?P<S>[\d.,]+) (?P<U>[\w^_]+?)</span>'
- OFFLINE_PATTERN = r'>You followed an invalid or expired link'
-
- LINK_PATTERN = r'"(https?://\w+\.uplea\.com/anonym/.*?)"'
-
- PREMIUM_ONLY_PATTERN = r'You need to have a Premium subscription to download this file'
- WAIT_PATTERN = r'timeText: ?([\d.]+),'
- STEP_PATTERN = r'<a href="(/step/.+)">'
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- m = re.search(self.STEP_PATTERN, self.html)
- if m is None:
- self.error(_("STEP_PATTERN not found"))
-
- self.html = self.load(urlparse.urljoin("http://uplea.com/", m.group(1)))
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logDebug(_("Waiting %s seconds") % m.group(1))
- self.wait(m.group(1), True)
- self.retry()
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_PATTERN not found"))
-
- self.link = m.group(1)
- self.wait(15)
-
-
-getInfo = create_getInfo(UpleaCom)
diff --git a/module/plugins/hoster/UploadStationCom.py b/module/plugins/hoster/UploadStationCom.py
deleted file mode 100644
index 90c177d71..000000000
--- a/module/plugins/hoster/UploadStationCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadStationCom(DeadHoster):
- __name__ = "UploadStationCom"
- __type__ = "hoster"
- __version__ = "0.52"
-
- __pattern__ = r'http://(?:www\.)?uploadstation\.com/file/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """UploadStation.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadStationCom)
diff --git a/module/plugins/hoster/UploadableCh.py b/module/plugins/hoster/UploadableCh.py
deleted file mode 100644
index 2907bd825..000000000
--- a/module/plugins/hoster/UploadableCh.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadableCh(SimpleHoster):
- __name__ = "UploadableCh"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uploadable.ch hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.uploadable.ch/file/\g<ID>')]
-
- INFO_PATTERN = r'div id=\"file_name\" title=.*>(?P<N>.+)<span class=\"filename_normal\">\((?P<S>[\d.]+) (?P<U>\w+)\)</span><'
-
- OFFLINE_PATTERN = r'>(File not available|This file is no longer available)'
- TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
-
- WAIT_PATTERN = r'>Please wait.+?<'
-
- RECAPTCHA_KEY = "6LdlJuwSAAAAAPJbPIoUhyqOJd7-yrah5Nhim5S3"
-
-
- def handleFree(self, pyfile):
- # Click the "free user" button and wait
- a = self.load(pyfile.url, post={'downloadLink': "wait"}, decode=True)
- self.logDebug(a)
-
- self.wait(30)
-
- # Make the recaptcha appear and show it the pyload interface
- b = self.load(pyfile.url, post={'checkDownload': "check"}, decode=True)
- self.logDebug(b) #: Expected output: {"success":"showCaptcha"}
-
- recaptcha = ReCaptcha(self)
-
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- # Submit the captcha solution
- self.load("http://www.uploadable.ch/checkReCaptcha.php",
- post={'recaptcha_challenge_field' : challenge,
- 'recaptcha_response_field' : response,
- 'recaptcha_shortencode_field': self.info['pattern']['ID']},
- decode=True)
-
- self.wait(3)
-
- # Get ready for downloading
- self.load(pyfile.url, post={'downloadLink': "show"}, decode=True)
-
- self.wait(3)
-
- # Download the file
- self.download(pyfile.url, post={'download': "normal"}, disposition=True)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'wait': re.compile("Please wait for")}):
- self.logInfo("Downloadlimit reached, please wait or reconnect")
- self.wait(60 * 60, True)
- self.retry()
-
- return super(UploadableCh, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UploadableCh)
diff --git a/module/plugins/hoster/UploadboxCom.py b/module/plugins/hoster/UploadboxCom.py
deleted file mode 100644
index 5a3856c6b..000000000
--- a/module/plugins/hoster/UploadboxCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadboxCom(DeadHoster):
- __name__ = "Uploadbox"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?uploadbox\.com/files/.+'
- __config__ = []
-
- __description__ = """UploadBox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadboxCom)
diff --git a/module/plugins/hoster/UploadedTo.py b/module/plugins/hoster/UploadedTo.py
deleted file mode 100644
index 75710ea8b..000000000
--- a/module/plugins/hoster/UploadedTo.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadedTo(SimpleHoster):
- __name__ = "UploadedTo"
- __type__ = "hoster"
- __version__ = "0.87"
-
- __pattern__ = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uploaded.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- DISPOSITION = False
-
- API_KEY = "lhF2IeeprweDfu9ccWlxXVVypA5nA3EL"
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://uploaded.net/file/\g<ID>')]
-
- TEMP_OFFLINE_PATTERN = r'<title>uploaded\.net - Maintenance'
-
- LINK_PREMIUM_PATTERN = r'<div class="tfree".*\s*<form method="post" action="(.+?)"'
-
- WAIT_PATTERN = r'Current waiting period: <span>(\d+)'
- DL_LIMIT_ERROR = r'You have reached the max. number of possible free downloads for this hour'
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- info = super(UploadedTo, cls).apiInfo(url)
-
- for _i in xrange(5):
- html = getURL("http://uploaded.net/api/filemultiple",
- get={"apikey": cls.API_KEY, 'id_0': re.match(cls.__pattern__, url).group('ID')},
- decode=True)
-
- if html != "can't find request":
- api = html.split(",", 4)
- if api[0] == "online":
- info.update({'name': api[4].strip(), 'size': api[2], 'status': 2})
- else:
- info['status'] = 1
- break
- else:
- time.sleep(3)
-
- return info
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
- self.chunkLimit = 1 # critical problems with more chunks
-
-
- def checkErrors(self):
- if 'var free_enabled = false;' in self.html:
- self.logError(_("Free-download capacities exhausted"))
- self.retry(24, 5 * 60)
-
- elif "limit-size" in self.html:
- self.fail(_("File too big for free download"))
-
- elif "limit-slot" in self.html: # Temporary restriction so just wait a bit
- self.wait(30 * 60, True)
- self.retry()
-
- elif "limit-parallel" in self.html:
- self.fail(_("Cannot download in parallel"))
-
- elif "limit-dl" in self.html or self.DL_LIMIT_ERROR in self.html: # limit-dl
- self.wait(3 * 60 * 60, True)
- self.retry()
-
- elif '"err":"captcha"' in self.html:
- self.invalidCaptcha()
-
- else:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
-
- def handleFree(self, pyfile):
- self.load("http://uploaded.net/language/en", just_header=True)
-
- self.html = self.load("http://uploaded.net/js/download.js", decode=True)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- self.html = self.load("http://uploaded.net/io/ticket/captcha/%s" % self.info['pattern']['ID'],
- post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- if "type:'download'" in self.html:
- self.correctCaptcha()
- try:
- self.link = re.search("url:'(.+?)'", self.html).group(1)
-
- except Exception:
- pass
-
- self.checkErrors()
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'limit-dl': self.DL_LIMIT_ERROR}):
- self.wait(3 * 60 * 60, True)
- self.retry()
-
- return super(UploadedTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UploadedTo)
diff --git a/module/plugins/hoster/UploadhereCom.py b/module/plugins/hoster/UploadhereCom.py
deleted file mode 100644
index d3b095c15..000000000
--- a/module/plugins/hoster/UploadhereCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadhereCom(DeadHoster):
- __name__ = "UploadhereCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?uploadhere\.com/\w{10}'
- __config__ = []
-
- __description__ = """Uploadhere.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadhereCom)
diff --git a/module/plugins/hoster/UploadheroCom.py b/module/plugins/hoster/UploadheroCom.py
deleted file mode 100644
index 5c74f10eb..000000000
--- a/module/plugins/hoster/UploadheroCom.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://uploadhero.co/dl/wQBRAVSM
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadheroCom(SimpleHoster):
- __name__ = "UploadheroCom"
- __type__ = "hoster"
- __version__ = "0.18"
-
- __pattern__ = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """UploadHero.co plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mcmyst", "mcmyst@hotmail.fr"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.+?)<'
- SIZE_PATTERN = r'>Filesize: </span><strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<p class="titre_dl_2">'
-
- COOKIES = [("uploadhero.co", "lang", "en")]
-
- IP_BLOCKED_PATTERN = r'href="(/lightbox_block_download\.php\?min=.+?)"'
- IP_WAIT_PATTERN = r'<span id="minutes">(\d+)</span>.*\s*<span id="seconds">(\d+)</span>'
-
- CAPTCHA_PATTERN = r'"(/captchadl\.php\?\w+)"'
-
- LINK_FREE_PATTERN = r'var magicomfg = \'<a href="(.+?)"|"(http://storage\d+\.uploadhero\.co.+?)"'
- LINK_PREMIUM_PATTERN = r'<a href="(.+?)" id="downloadnow"'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha not found"))
-
- captcha = self.decryptCaptcha(urlparse.urljoin("http://uploadhero.co", m.group(1)))
-
- self.html = self.load(pyfile.url,
- get={"code": captcha})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1) or m.group(2)
- self.wait(50)
-
-
- def checkErrors(self):
- m = re.search(self.IP_BLOCKED_PATTERN, self.html)
- if m:
- self.html = self.load(urlparse.urljoin("http://uploadhero.co", m.group(1)))
-
- m = re.search(self.IP_WAIT_PATTERN, self.html)
- wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 5 * 60
- self.wait(wait_time, True)
- self.retry()
-
- return super(UploadheroCom, self).checkErrors()
-
-
-getInfo = create_getInfo(UploadheroCom)
diff --git a/module/plugins/hoster/UploadingCom.py b/module/plugins/hoster/UploadingCom.py
deleted file mode 100644
index c2e0d2873..000000000
--- a/module/plugins/hoster/UploadingCom.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
-
-
-class UploadingCom(SimpleHoster):
- __name__ = "UploadingCom"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>\w+)'
-
- __description__ = """Uploading.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
- SIZE_PATTERN = r'size tip_container">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
- OFFLINE_PATTERN = r'(Page|file) not found'
-
- COOKIES = [("uploading.com", "lang", "1"),
- (".uploading.com", "language", "1"),
- (".uploading.com", "setlang", "en"),
- (".uploading.com", "_lang", "en")]
-
-
- def process(self, pyfile):
- if not "/get/" in pyfile.url:
- pyfile.url = pyfile.url.replace("/files", "/files/get")
-
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- if self.premium:
- self.handlePremium(pyfile)
- else:
- self.handleFree(pyfile)
-
-
- def handlePremium(self, pyfile):
- postData = {'action': 'get_link',
- 'code' : self.info['pattern']['ID'],
- 'pass' : 'undefined'}
-
- self.html = self.load('http://uploading.com/files/get/?JsHttpRequest=%d-xml' % timestamp(), post=postData)
- url = re.search(r'"link"\s*:\s*"(.*?)"', self.html)
- if url:
- self.link = url.group(1).replace("\\/", "/")
-
- raise Exception("Plugin defect")
-
-
- def handleFree(self, pyfile):
- m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
- if m:
- pyfile.error = m.group(1)
- self.logWarning(pyfile.error)
- self.retry(6, (6 * 60 if m.group(2) else 15) * 60, pyfile.error)
-
- ajax_url = "http://uploading.com/files/get/?ajax"
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = pyfile.url
-
- res = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.info['pattern']['ID']}))
-
- if 'answer' in res and 'wait_time' in res['answer']:
- wait_time = int(res['answer']['wait_time'])
- self.logInfo(_("Waiting %d seconds") % wait_time)
- self.wait(wait_time)
- else:
- self.error(_("No AJAX/WAIT"))
-
- res = json_loads(self.load(ajax_url, post={'action': 'get_link', 'code': self.info['pattern']['ID'], 'pass': 'false'}))
-
- if 'answer' in res and 'link' in res['answer']:
- url = res['answer']['link']
- else:
- self.error(_("No AJAX/URL"))
-
- self.html = self.load(url)
- m = re.search(r'<form id="file_form" action="(.*?)"', self.html)
- if m:
- url = m.group(1)
- else:
- self.error(_("No URL"))
-
- self.link = url
-
-
-getInfo = create_getInfo(UploadingCom)
diff --git a/module/plugins/hoster/UploadkingCom.py b/module/plugins/hoster/UploadkingCom.py
deleted file mode 100644
index 6c24fe51a..000000000
--- a/module/plugins/hoster/UploadkingCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadkingCom(DeadHoster):
- __name__ = "UploadkingCom"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'http://(?:www\.)?uploadking\.com/\w{10}'
- __config__ = []
-
- __description__ = """UploadKing.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadkingCom)
diff --git a/module/plugins/hoster/UpstoreNet.py b/module/plugins/hoster/UpstoreNet.py
deleted file mode 100644
index ec0c88c82..000000000
--- a/module/plugins/hoster/UpstoreNet.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UpstoreNet(SimpleHoster):
- __name__ = "UpstoreNet"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?upstore\.net/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Upstore.Net File Download Hoster"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- INFO_PATTERN = r'<div class="comment">.*?</div>\s*\n<h2 style="margin:0">(?P<N>.*?)</h2>\s*\n<div class="comment">\s*\n\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<span class="error">File not found</span>'
-
- WAIT_PATTERN = r'var sec = (\d+)'
- CHASH_PATTERN = r'<input type="hidden" name="hash" value="(.+?)">'
- LINK_FREE_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
-
-
- def handleFree(self, pyfile):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.error(_("CHASH_PATTERN not found"))
- chash = m.group(1)
- self.logDebug("Read hash " + chash)
- # continue to stage2
- post_data = {'hash': chash, 'free': 'Slow download'}
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- # STAGE 2: solv captcha and wait
- # first get the infos we need: recaptcha key and wait time
- recaptcha = ReCaptcha(self)
-
- # try the captcha 5 times
- for i in xrange(5):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m is None:
- self.error(_("Wait pattern not found"))
- wait_time = int(m.group(1))
-
- # then, do the waiting
- self.wait(wait_time)
-
- # then, handle the captcha
- response, challenge = recaptcha.challenge()
- post_data.update({'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- # STAGE 3: get direct link
- m = re.search(self.LINK_FREE_PATTERN, self.html, re.S)
- if m:
- break
-
- if m is None:
- self.error(_("Download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(UpstoreNet)
diff --git a/module/plugins/hoster/UptoboxCom.py b/module/plugins/hoster/UptoboxCom.py
deleted file mode 100644
index 991bc640e..000000000
--- a/module/plugins/hoster/UptoboxCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class UptoboxCom(XFSHoster):
- __name__ = "UptoboxCom"
- __type__ = "hoster"
- __version__ = "0.18"
-
- __pattern__ = r'https?://(?:www\.)?(uptobox|uptostream)\.com/\w{12}'
-
- __description__ = """Uptobox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'"para_title">(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)'
- OFFLINE_PATTERN = r'>(File not found|Access Denied|404 Not Found)'
- TEMP_OFFLINE_PATTERN = r'>Service Unavailable'
-
- LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
-
- ERROR_PATTERN = r'>(You have to wait.+till next download.)<' #@TODO: Check XFSHoster ERROR_PATTERN
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
-getInfo = create_getInfo(UptoboxCom)
diff --git a/module/plugins/hoster/VeehdCom.py b/module/plugins/hoster/VeehdCom.py
deleted file mode 100644
index 78da91020..000000000
--- a/module/plugins/hoster/VeehdCom.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class VeehdCom(Hoster):
- __name__ = "VeehdCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://veehd\.com/video/\d+_\S+'
- __config__ = [("filename_spaces", "bool", "Allow spaces in filename", False),
- ("replacement_char", "str", "Filename replacement character", "_")]
-
- __description__ = """Veehd.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("cat", "cat@pyload")]
-
-
- def setup(self):
- self.multiDL = True
- self.req.canContinue = True
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- def download_html(self):
- url = self.pyfile.url
- self.logDebug("Requesting page: %s" % url)
- self.html = self.load(url)
-
-
- def file_exists(self):
- if not self.html:
- self.download_html()
-
- if '<title>Veehd</title>' in self.html:
- return False
- return True
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- m = re.search(r'<title.*?>([^<]+) on Veehd</title>', self.html)
- if m is None:
- self.error(_("Video title not found"))
-
- name = m.group(1)
-
- # replace unwanted characters in filename
- if self.getConfig('filename_spaces'):
- pattern = '[^\w ]+'
- else:
- pattern = '[^\w.]+'
-
- return re.sub(pattern, self.getConfig('replacement_char'), name) + '.avi'
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- m = re.search(r'<embed type="video/divx" src="(http://([^/]*\.)?veehd\.com/dl/.+?)"',
- self.html)
- if m is None:
- self.error(_("Embedded video url not found"))
-
- return m.group(1)
diff --git a/module/plugins/hoster/VeohCom.py b/module/plugins/hoster/VeohCom.py
deleted file mode 100644
index 57b24623b..000000000
--- a/module/plugins/hoster/VeohCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class VeohCom(SimpleHoster):
- __name__ = "VeohCom"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
- __config__ = [("use_premium", "bool" , "Use premium account if available", True ),
- ("quality" , "Low;High;Auto", "Quality" , "Auto")]
-
- __description__ = """Veoh.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
- OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.veoh.com/watch/\g<ID>')]
-
- COOKIES = [("veoh.com", "lassieLocale", "en")]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- quality = self.getConfig('quality')
- if quality == "Auto":
- quality = ("High", "Low")
-
- for q in quality:
- pattern = r'"fullPreviewHash%sPath":"(.+?)"' % q
- m = re.search(pattern, self.html)
- if m:
- pyfile.name += ".mp4"
- self.link = m.group(1).replace("\\", "")
- return
- else:
- self.logInfo(_("No %s quality video found") % q.upper())
- else:
- self.fail(_("No video found!"))
-
-
-getInfo = create_getInfo(VeohCom)
diff --git a/module/plugins/hoster/VidPlayNet.py b/module/plugins/hoster/VidPlayNet.py
deleted file mode 100644
index f1a32a897..000000000
--- a/module/plugins/hoster/VidPlayNet.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://vidplay.net/38lkev0h3jv0
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class VidPlayNet(XFSHoster):
- __name__ = "VidPlayNet"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?vidplay\.net/\w{12}'
-
- __description__ = """VidPlay.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
- NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
-
-
-getInfo = create_getInfo(VidPlayNet)
diff --git a/module/plugins/hoster/VimeoCom.py b/module/plugins/hoster/VimeoCom.py
deleted file mode 100644
index a5196cb92..000000000
--- a/module/plugins/hoster/VimeoCom.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class VimeoCom(SimpleHoster):
- __name__ = "VimeoCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
- __config__ = [("use_premium", "bool" , "Use premium account if available" , True ),
- ("quality" , "Lowest;Mobile;SD;HD;Highest", "Quality" , "Highest"),
- ("original" , "bool" , "Try to download the original file", True )]
-
- __description__ = """Vimeo.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<title>(?P<N>.+) on Vimeo<'
- OFFLINE_PATTERN = r'class="exception_header"'
- TEMP_OFFLINE_PATTERN = r'Please try again in a few minutes.<'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'https://www.vimeo.com/\g<ID>')]
-
- COOKIES = [("vimeo.com", "language", "en")]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- password = self.getPassword()
-
- if self.js and 'class="btn iconify_down_b"' in self.html:
- html = self.js.eval(self.load(pyfile.url, get={'action': "download", 'password': password}, decode=True))
- pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
- else:
- html = self.load("https://player.vimeo.com/video/" + self.info['pattern']['ID'], get={'password': password})
- pattern = r'"(?P<QL>\w+)":{"profile".*?"(?P<URL>http://pdl\.vimeocdn\.com.+?)"'
-
- link = dict((l.group('QL').lower(), l.group('URL')) for l in re.finditer(pattern, html))
-
- if self.getConfig('original'):
- if "original" in link:
- self.link = link[q]
- return
- else:
- self.logInfo(_("Original file not downloadable"))
-
- quality = self.getConfig('quality')
- if quality == "Highest":
- qlevel = ("hd", "sd", "mobile")
- elif quality == "Lowest":
- qlevel = ("mobile", "sd", "hd")
- else:
- qlevel = quality.lower()
-
- for q in qlevel:
- if q in link:
- self.link = link[q]
- return
- else:
- self.logInfo(_("No %s quality video found") % q.upper())
- else:
- self.fail(_("No video found!"))
-
-
-getInfo = create_getInfo(VimeoCom)
diff --git a/module/plugins/hoster/Vipleech4UCom.py b/module/plugins/hoster/Vipleech4UCom.py
deleted file mode 100644
index 1a7ec2810..000000000
--- a/module/plugins/hoster/Vipleech4UCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class Vipleech4UCom(DeadHoster):
- __name__ = "Vipleech4UCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
- __config__ = []
-
- __description__ = """Vipleech4u.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
-getInfo = create_getInfo(Vipleech4UCom)
diff --git a/module/plugins/hoster/WarserverCz.py b/module/plugins/hoster/WarserverCz.py
deleted file mode 100644
index a8ae99a1b..000000000
--- a/module/plugins/hoster/WarserverCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class WarserverCz(DeadHoster):
- __name__ = "WarserverCz"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?warserver\.cz/stahnout/\d+'
- __config__ = []
-
- __description__ = """Warserver.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(WarserverCz)
diff --git a/module/plugins/hoster/WebshareCz.py b/module/plugins/hoster/WebshareCz.py
deleted file mode 100644
index 59f61dc87..000000000
--- a/module/plugins/hoster/WebshareCz.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class WebshareCz(SimpleHoster):
- __name__ = "WebshareCz"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """WebShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("rush", "radek.senfeld@gmail.com")]
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = super(WebshareCz, cls).getInfo(url, html)
-
- if url:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict()
-
- api_data = getURL("https://webshare.cz/api/file_info/",
- post={'ident': info['pattern']['ID']},
- decode=True)
-
- if 'File not found' in api_data:
- info['status'] = 1
- else:
- info["status"] = 2
- info['name'] = re.search('<name>(.+)</name>', api_data).group(1) or info['name']
- info['size'] = re.search('<size>(.+)</size>', api_data).group(1) or info['size']
-
- return info
-
-
- def handleFree(self, pyfile):
- wst = self.account.infos['wst'] if self.account and 'wst' in self.account.infos else ""
-
- api_data = getURL('https://webshare.cz/api/file_link/',
- post={'ident': self.info['pattern']['ID'], 'wst': wst},
- decode=True)
-
- self.logDebug("API data: " + api_data)
-
- m = re.search('<link>(.+)</link>', api_data)
- if m is None:
- self.error(_("Unable to detect direct link"))
-
- self.link = m.group(1)
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
-getInfo = create_getInfo(WebshareCz)
diff --git a/module/plugins/hoster/WrzucTo.py b/module/plugins/hoster/WrzucTo.py
deleted file mode 100644
index f11d03ea8..000000000
--- a/module/plugins/hoster/WrzucTo.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class WrzucTo(SimpleHoster):
- __name__ = "WrzucTo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Wrzuc.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
- SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
-
- COOKIES = [("wrzuc.to", "language", "en")]
-
-
- def setup(self):
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
- if len(data) != 2:
- self.error(_("No file ID"))
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = pyfile.url
- self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
-
- self.req.http.lastURL = pyfile.url
- self.html = self.load("http://www.wrzuc.to/ajax/server/download_link", post={"file": data['file']})
-
- data.update(re.findall(r'"(download_link|server_id)":"(.*?)"', self.html))
- if len(data) != 4:
- self.error(_("No download URL"))
-
- self.link = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
-
-
-getInfo = create_getInfo(WrzucTo)
diff --git a/module/plugins/hoster/WuploadCom.py b/module/plugins/hoster/WuploadCom.py
deleted file mode 100644
index 14c56aba3..000000000
--- a/module/plugins/hoster/WuploadCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class WuploadCom(DeadHoster):
- __name__ = "WuploadCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?wupload\..+?/file/((\w+/)?\d+)(/.*)?'
- __config__ = []
-
- __description__ = """Wupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("Paul King", None)]
-
-
-getInfo = create_getInfo(WuploadCom)
diff --git a/module/plugins/hoster/X7To.py b/module/plugins/hoster/X7To.py
deleted file mode 100644
index 15e011720..000000000
--- a/module/plugins/hoster/X7To.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class X7To(DeadHoster):
- __name__ = "X7To"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __pattern__ = r'http://(?:www\.)?x7\.to/'
- __config__ = []
-
- __description__ = """X7.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("ernieb", "ernieb")]
-
-
-getInfo = create_getInfo(X7To)
diff --git a/module/plugins/hoster/XFileSharingPro.py b/module/plugins/hoster/XFileSharingPro.py
deleted file mode 100644
index 1bfb504b7..000000000
--- a/module/plugins/hoster/XFileSharingPro.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class XFileSharingPro(XFSHoster):
- __name__ = "XFileSharingPro"
- __type__ = "hoster"
- __version__ = "0.45"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharingPro dummy hoster plugin for hook"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [("/embed-", "/")]
-
-
- def _log(self, type, args):
- msg = " | ".join(str(a).strip() for a in args if a)
- logger = getattr(self.log, type)
- logger("%s: %s: %s" % (self.__name__, self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
-
-
- def init(self):
- super(XFileSharingPro, self).init()
-
- self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
-
- self.HOSTER_DOMAIN = re.match(self.__pattern__, self.pyfile.url).group("DOMAIN").lower()
- self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
-
- account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
-
- if account and account.canUse():
- self.account = account
-
- elif self.account:
- self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
-
- else:
- return
-
- self.user, data = self.account.selectAccount()
- self.req = self.account.getAccountRequest(self.user)
- self.premium = self.account.isPremium(self.user)
-
-
- def setup(self):
- self.chunkLimit = 1
- self.resumeDownload = self.premium
- self.multiDL = True
-
-
-getInfo = create_getInfo(XFileSharingPro)
diff --git a/module/plugins/hoster/XHamsterCom.py b/module/plugins/hoster/XHamsterCom.py
deleted file mode 100644
index a1711cb0e..000000000
--- a/module/plugins/hoster/XHamsterCom.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-def clean_json(json_expr):
- json_expr = re.sub('[\n\r]', '', json_expr)
- json_expr = re.sub(' +', '', json_expr)
- json_expr = re.sub('\'', '"', json_expr)
-
- return json_expr
-
-
-class XHamsterCom(Hoster):
- __name__ = "XHamsterCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?xhamster\.com/movies/.+'
- __config__ = [("type", ".mp4;.flv", "Preferred type", ".mp4")]
-
- __description__ = """XHamster.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- if not self.file_exists():
- self.offline()
-
- if self.getConfig('type'):
- self.desired_fmt = self.getConfig('type')
-
- pyfile.name = self.get_file_name() + self.desired_fmt
- self.download(self.get_file_url())
-
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- flashvar_pattern = re.compile('flashvars = ({.*?});', re.S)
- json_flashvar = flashvar_pattern.search(self.html)
-
- if not json_flashvar:
- self.error(_("flashvar not found"))
-
- j = clean_json(json_flashvar.group(1))
- flashvars = json_loads(j)
-
- if flashvars['srv']:
- srv_url = flashvars['srv'] + '/'
- else:
- self.error(_("srv_url not found"))
-
- if flashvars['url_mode']:
- url_mode = flashvars['url_mode']
-
-
- else:
- self.error(_("url_mode not found"))
-
- if self.desired_fmt == ".mp4":
- file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
- if file_url is None:
- self.error(_("file_url not found"))
- file_url = file_url.group(1)
- long_url = srv_url + file_url
- self.logDebug("long_url = " + long_url)
- else:
- if flashvars['file']:
- file_url = urllib.unquote(flashvars['file'])
- else:
- self.error(_("file_url not found"))
-
- if url_mode == '3':
- long_url = file_url
- self.logDebug("long_url = " + long_url)
- else:
- long_url = srv_url + "key=" + file_url
- self.logDebug("long_url = " + long_url)
-
- return long_url
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- pattern = r'<title>(.*?) - xHamster\.com</title>'
- name = re.search(pattern, self.html)
- if name is None:
- pattern = r'<h1 >(.*)</h1>'
- name = re.search(pattern, self.html)
- if name is None:
- pattern = r'http://[www.]+xhamster\.com/movies/.*/(.*?)\.html?'
- name = re.match(file_name_pattern, self.pyfile.url)
- if name is None:
- pattern = r'<div id="element_str_id" style="display:none;">(.*)</div>'
- name = re.search(pattern, self.html)
- if name is None:
- return "Unknown"
-
- return name.group(1)
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"(.*Video not found.*)", self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/XdadevelopersCom.py b/module/plugins/hoster/XdadevelopersCom.py
deleted file mode 100644
index 45d1e92cb..000000000
--- a/module/plugins/hoster/XdadevelopersCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# http://forum.xda-developers.com/devdb/project/dl/?id=10885
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class XdadevelopersCom(SimpleHoster):
- __name__ = "XdadevelopersCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?forum\.xda-developers\.com/devdb/project/dl/\?id=\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Xda-developers.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<label>Filename:</label>\s*<div>\s*(?P<N>.*?)\n'
- SIZE_PATTERN = r'<label>Size:</label>\s*<div>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'</i> Device Filter</h3>'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- self.link = pyfile.url + "&task=get" #@TODO: Revert to `get={'task': "get"}` in 0.4.10
-
-
-getInfo = create_getInfo(XdadevelopersCom)
diff --git a/module/plugins/hoster/Xdcc.py b/module/plugins/hoster/Xdcc.py
deleted file mode 100644
index d167e4cab..000000000
--- a/module/plugins/hoster/Xdcc.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import socket
-import struct
-import sys
-import time
-
-from select import select
-
-from module.plugins.Hoster import Hoster
-from module.utils import save_join
-
-
-class Xdcc(Hoster):
- __name__ = "Xdcc"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __config__ = [("nick", "str", "Nickname", "pyload"),
- ("ident", "str", "Ident", "pyloadident"),
- ("realname", "str", "Realname", "pyloadreal")]
-
- __description__ = """Download from IRC XDCC bot"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.com")]
-
-
- def setup(self):
- self.debug = 0 # 0,1,2
- self.timeout = 30
- self.multiDL = False
-
-
- def process(self, pyfile):
- # change request type
- self.req = pyfile.m.core.requestFactory.getRequest(self.__name__, type="XDCC")
-
- self.pyfile = pyfile
- for _i in xrange(0, 3):
- try:
- nmn = self.doDownload(pyfile.url)
- self.logDebug("Download of %s finished." % nmn)
- return
- except socket.error, e:
- if hasattr(e, "errno"):
- errno = e.errno
- else:
- errno = e.args[0]
-
- if errno == 10054:
- self.logDebug("Server blocked our ip, retry in 5 min")
- self.setWait(300)
- self.wait()
- continue
-
- self.fail(_("Failed due to socket errors. Code: %d") % errno)
-
- self.fail(_("Server blocked our ip, retry again later manually"))
-
-
- def doDownload(self, url):
- self.pyfile.setStatus("waiting") # real link
-
- m = re.match(r'xdcc://(.*?)/#?(.*?)/(.*?)/#?(\d+)/?', url)
- server = m.group(1)
- chan = m.group(2)
- bot = m.group(3)
- pack = m.group(4)
- nick = self.getConfig('nick')
- ident = self.getConfig('ident')
- real = self.getConfig('realname')
-
- temp = server.split(':')
- ln = len(temp)
- if ln == 2:
- host, port = temp
- elif ln == 1:
- host, port = temp[0], 6667
- else:
- self.fail(_("Invalid hostname for IRC Server: %s") % server)
-
- #######################
- # CONNECT TO IRC AND IDLE FOR REAL LINK
- dl_time = time.time()
-
- sock = socket.socket()
- sock.connect((host, int(port)))
- if nick == "pyload":
- nick = "pyload-%d" % (time.time() % 1000) # last 3 digits
- sock.send("NICK %s\r\n" % nick)
- sock.send("USER %s %s bla :%s\r\n" % (ident, host, real))
-
- self.setWait(3)
- self.wait()
-
- sock.send("JOIN #%s\r\n" % chan)
- sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
-
- # IRC recv loop
- readbuffer = ""
- done = False
- retry = None
- m = None
- while True:
-
- # done is set if we got our real link
- if done:
- break
-
- if retry:
- if time.time() > retry:
- retry = None
- dl_time = time.time()
- sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
-
- else:
- if (dl_time + self.timeout) < time.time(): # todo: add in config
- sock.send("QUIT :byebye\r\n")
- sock.close()
- self.fail(_("XDCC Bot did not answer"))
-
- fdset = select([sock], [], [], 0)
- if sock not in fdset[0]:
- continue
-
- readbuffer += sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- if self.debug is 2:
- print "*> " + unicode(line, errors='ignore')
- line = line.rstrip()
- first = line.split()
-
- if first[0] == "PING":
- sock.send("PONG %s\r\n" % first[1])
-
- if first[0] == "ERROR":
- self.fail(_("IRC-Error: %s") % line)
-
- msg = line.split(None, 3)
- if len(msg) != 4:
- continue
-
- msg = {
- "origin": msg[0][1:],
- "action": msg[1],
- "target": msg[2],
- "text": msg[3][1:]
- }
-
- if nick == msg['target'][0:len(nick)] and "PRIVMSG" == msg['action']:
- if msg['text'] == "\x01VERSION\x01":
- self.logDebug("Sending CTCP VERSION")
- sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
- elif msg['text'] == "\x01TIME\x01":
- self.logDebug("Sending CTCP TIME")
- sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
- elif msg['text'] == "\x01LAG\x01":
- pass # don't know how to answer
-
- if not (bot == msg['origin'][0:len(bot)]
- and nick == msg['target'][0:len(nick)]
- and msg['action'] in ("PRIVMSG", "NOTICE")):
- continue
-
- if self.debug is 1:
- print "%s: %s" % (msg['origin'], msg['text'])
-
- if "You already requested that pack" in msg['text']:
- retry = time.time() + 300
-
- if "you must be on a known channel to request a pack" in msg['text']:
- self.fail(_("Wrong channel"))
-
- m = re.match('\x01DCC SEND (.*?) (\d+) (\d+)(?: (\d+))?\x01', msg['text'])
- if m:
- done = True
-
- # get connection data
- ip = socket.inet_ntoa(struct.pack('L', socket.ntohl(int(m.group(2)))))
- port = int(m.group(3))
- packname = m.group(1)
-
- if len(m.groups()) > 3:
- self.req.filesize = int(m.group(4))
-
- self.pyfile.name = packname
-
- download_folder = self.config['general']['download_folder']
- filename = save_join(download_folder, packname)
-
- self.logInfo(_("Downloading %s from %s:%d") % (packname, ip, port))
-
- self.pyfile.setStatus("downloading")
- newname = self.req.download(ip, port, filename, sock, self.pyfile.setProgress)
- if newname and newname != filename:
- self.logInfo(_("%(name)s saved as %(newname)s") % {"name": self.pyfile.name, "newname": newname})
- filename = newname
-
- # kill IRC socket
- # sock.send("QUIT :byebye\r\n")
- sock.close()
-
- self.lastDownload = filename
- return self.lastDownload
diff --git a/module/plugins/hoster/YadiSk.py b/module/plugins/hoster/YadiSk.py
deleted file mode 100644
index ad63ffb32..000000000
--- a/module/plugins/hoster/YadiSk.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import random
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class YadiSk(SimpleHoster):
- __name__ = "YadiSk"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://yadi\.sk/d/\w+'
-
- __description__ = """Yadi.sk hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GammaC0de", None)]
-
-
- OFFLINE_PATTERN = r'Nothing found'
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = super(YadiSk, cls).getInfo(url, html)
-
- if html:
- if 'idclient' not in info:
- info['idclient'] = ""
- for _i in xrange(32):
- info ['idclient'] += random.choice('0123456abcdef')
-
- m = re.search(r'<script id="models-client" type="application/json">(.+?)</script>', html)
- if m:
- api_data = json_loads(m.group(1))
- try:
- for sect in api_data:
- if 'model' in sect:
- if sect['model'] == "config":
- info['version'] = sect['data']['version']
- info['sk'] = sect['data']['sk']
-
- elif sect['model'] == "resource":
- info['id'] = sect['data']['id']
- info['size'] = sect['data']['meta']['size']
- info['name'] = sect['data']['name']
-
- except Exception, e:
- info['status'] = 8
- info['error'] = _("Unexpected server response: %s") % e.message
-
- else:
- info['status'] = 8
- info['error'] = _("could not find required json data")
-
- return info
-
-
- def setup(self):
- self.resumeDownload = False
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- if any(True for _k in ['id', 'sk', 'version', 'idclient'] if _k not in self.info):
- self.error(_("Missing JSON data"))
-
-
- try:
- self.html = self.load("https://yadi.sk/models/",
- get={'_m': "do-get-resource-url"},
- post={'idClient': self.info['idclient'],
- 'version' : self.info['version'],
- '_model.0': "do-get-resource-url",
- 'sk' : self.info['sk'],
- 'id.0' : self.info['id']})
-
- self.link = json_loads(self.html)['models'][0]['data']['file']
-
- except Exception:
- pass
-
-
-getInfo = create_getInfo(YadiSk)
diff --git a/module/plugins/hoster/YibaishiwuCom.py b/module/plugins/hoster/YibaishiwuCom.py
deleted file mode 100644
index 7ca6e1ac0..000000000
--- a/module/plugins/hoster/YibaishiwuCom.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class YibaishiwuCom(SimpleHoster):
- __name__ = "YibaishiwuCom"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """115.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'file_name: \'(?P<N>.+?)\''
- SIZE_PATTERN = r'file_size: \'(?P<S>.+?)\''
- OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
-
- LINK_FREE_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- url = m.group(1)
-
- self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
-
- res = json_loads(self.load("http://115.com" + url, decode=False))
- if "urls" in res:
- mirrors = res['urls']
-
- elif "data" in res:
- mirrors = res['data']
-
- else:
- mirrors = None
-
- for mr in mirrors:
- try:
- self.link = mr['url'].replace("\\", "")
- self.logDebug("Trying URL: " + self.link)
- break
- except Exception:
- continue
- else:
- self.fail(_("No working link found"))
-
-
-getInfo = create_getInfo(YibaishiwuCom)
diff --git a/module/plugins/hoster/YoupornCom.py b/module/plugins/hoster/YoupornCom.py
deleted file mode 100644
index 19d07fa36..000000000
--- a/module/plugins/hoster/YoupornCom.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class YoupornCom(Hoster):
- __name__ = "YoupornCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?youporn\.com/watch/.+'
-
- __description__ = """Youporn.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("willnix", "willnix@pyload.org")]
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url, post={"user_choice": "Enter"}, cookies=False)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- return re.search(r'(http://download\.youporn\.com/download/\d+\?save=1)">', self.html).group(1)
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- file_name_pattern = r'<title>(.+) - '
- return re.search(file_name_pattern, self.html).group(1).replace("&amp;", "&").replace("/", "") + '.flv'
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"(.*invalid video_id.*)", self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YourfilesTo.py b/module/plugins/hoster/YourfilesTo.py
deleted file mode 100644
index d0316d3ac..000000000
--- a/module/plugins/hoster/YourfilesTo.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import reimport urllib
-
-from module.plugins.Hoster import Hoster
-
-
-class YourfilesTo(Hoster):
- __name__ = "YourfilesTo"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?yourfiles\.(to|biz)/\?d=\w+'
-
- __description__ = """Youfiles.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("skydancer", "skydancer@hasnomail.de")]
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.prepare()
- self.download(self.get_file_url())
-
-
- def prepare(self):
- if not self.file_exists():
- self.offline()
-
- self.pyfile.name = self.get_file_name()
-
- wait_time = self.get_waiting_time()
- self.setWait(wait_time)
- self.wait()
-
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- #var zzipitime = 15;
- m = re.search(r'var zzipitime = (\d+);', self.html)
- if m:
- sec = int(m.group(1))
- else:
- sec = 0
-
- return sec
-
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- url = re.search(r"var bla = '(.*?)';", self.html)
- if url:
- url = url.group(1)
- url = urllib.unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
- return url
- else:
- self.error(_("Absolute filepath not found"))
-
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search("<title>(.*)</title>", self.html).group(1)
-
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r"HTTP Status 404", self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YoutubeCom.py b/module/plugins/hoster/YoutubeCom.py
deleted file mode 100644
index e2ab146a9..000000000
--- a/module/plugins/hoster/YoutubeCom.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocessimport urllib
-
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.SimpleHoster import replace_patterns
-from module.utils import html_unescape
-
-
-def which(program):
- """Works exactly like the unix command which
- Courtesy of http://stackoverflow.com/a/377028/675646"""
-
- isExe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK)
-
- fpath, fname = os.path.split(program)
-
- if fpath:
- if isExe(program):
- return program
- else:
- for path in os.environ['PATH'].split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if isExe(exe_file):
- return exe_file
-
-
-class YoutubeCom(Hoster):
- __name__ = "YoutubeCom"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __pattern__ = r'https?://(?:[^/]*\.)?(youtube\.com|youtu\.be)/watch\?(?:.*&)?v=.+'
- __config__ = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting" , "hd" ),
- ("fmt" , "int" , "FMT/ITAG Number (0 for auto)", 0 ),
- (".mp4" , "bool" , "Allow .mp4" , True ),
- (".flv" , "bool" , "Allow .flv" , True ),
- (".webm" , "bool" , "Allow .webm" , False),
- (".3gp" , "bool" , "Allow .3gp" , False),
- ("3d" , "bool" , "Prefer 3D" , False)]
-
- __description__ = """Youtube.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- URL_REPLACEMENTS = [(r'youtu\.be/', 'youtube.com/')]
-
- # Invalid characters that must be removed from the file name
- invalidChars = u'\u2605:?><"|\\'
-
- # name, width, height, quality ranking, 3D
- formats = {5 : (".flv" , 400 , 240 , 1 , False),
- 6 : (".flv" , 640 , 400 , 4 , False),
- 17 : (".3gp" , 176 , 144 , 0 , False),
- 18 : (".mp4" , 480 , 360 , 2 , False),
- 22 : (".mp4" , 1280, 720 , 8 , False),
- 43 : (".webm", 640 , 360 , 3 , False),
- 34 : (".flv" , 640 , 360 , 4 , False),
- 35 : (".flv" , 854 , 480 , 6 , False),
- 36 : (".3gp" , 400 , 240 , 1 , False),
- 37 : (".mp4" , 1920, 1080, 9 , False),
- 38 : (".mp4" , 4096, 3072, 10, False),
- 44 : (".webm", 854 , 480 , 5 , False),
- 45 : (".webm", 1280, 720 , 7 , False),
- 46 : (".webm", 1920, 1080, 9 , False),
- 82 : (".mp4" , 640 , 360 , 3 , True ),
- 83 : (".mp4" , 400 , 240 , 1 , True ),
- 84 : (".mp4" , 1280, 720 , 8 , True ),
- 85 : (".mp4" , 1920, 1080, 9 , True ),
- 100: (".webm", 640 , 360 , 3 , True ),
- 101: (".webm", 640 , 360 , 4 , True ),
- 102: (".webm", 1280, 720 , 8 , True )}
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def process(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
- html = self.load(pyfile.url, decode=True)
-
- if re.search(r'<div id="player-unavailable" class="\s*player-width player-height\s*">', html):
- self.offline()
-
- if "We have been receiving a large volume of requests from your network." in html:
- self.tempOffline()
-
- #get config
- use3d = self.getConfig('3d')
-
- if use3d:
- quality = {"sd": 82, "hd": 84, "fullhd": 85, "240p": 83, "360p": 82,
- "480p": 82, "720p": 84, "1080p": 85, "3072p": 85}
- else:
- quality = {"sd": 18, "hd": 22, "fullhd": 37, "240p": 5, "360p": 18,
- "480p": 35, "720p": 22, "1080p": 37, "3072p": 38}
-
- desired_fmt = self.getConfig('fmt')
-
- if not desired_fmt:
- desired_fmt = quality.get(self.getConfig('quality'), 18)
-
- elif desired_fmt not in self.formats:
- self.logWarning(_("FMT %d unknown, using default") % desired_fmt)
- desired_fmt = 0
-
- #parse available streams
- streams = re.search(r'"url_encoded_fmt_stream_map":"(.+?)",', html).group(1)
- streams = [x.split('\u0026') for x in streams.split(',')]
- streams = [dict((y.split('=', 1)) for y in x) for x in streams]
- streams = [(int(x['itag']), urllib.unquote(x['url'])) for x in streams]
-
- # self.logDebug("Found links: %s" % streams)
-
- self.logDebug("AVAILABLE STREAMS: %s" % [x[0] for x in streams])
-
- #build dictionary of supported itags (3D/2D)
- allowed = lambda x: self.getConfig(self.formats[x][0])
- streams = [x for x in streams if x[0] in self.formats and allowed(x[0])]
-
- if not streams:
- self.fail(_("No available stream meets your preferences"))
-
- fmt_dict = dict([x for x in streams if self.formats[x[0]][4] == use3d] or streams)
-
- self.logDebug("DESIRED STREAM: ITAG:%d (%s) %sfound, %sallowed" %
- (desired_fmt, "%s %dx%d Q:%d 3D:%s" % self.formats[desired_fmt],
- "" if desired_fmt in fmt_dict else "NOT ", "" if allowed(desired_fmt) else "NOT "))
-
- #return fmt nearest to quality index
- if desired_fmt in fmt_dict and allowed(desired_fmt):
- fmt = desired_fmt
- else:
- sel = lambda x: self.formats[x][3] # select quality index
- comp = lambda x, y: abs(sel(x) - sel(y))
-
- self.logDebug("Choosing nearest fmt: %s" % [(x, allowed(x), comp(x, desired_fmt)) for x in fmt_dict.keys()])
-
- fmt = reduce(lambda x, y: x if comp(x, desired_fmt) <= comp(y, desired_fmt) and
- sel(x) > sel(y) else y, fmt_dict.keys())
-
- self.logDebug("Chosen fmt: %s" % fmt)
-
- url = fmt_dict[fmt]
-
- self.logDebug("URL: %s" % url)
-
- #set file name
- file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv"
- file_name_pattern = '<meta name="title" content="(.+?)">'
- name = re.search(file_name_pattern, html).group(1).replace("/", "")
-
- # Cleaning invalid characters from the file name
- name = name.encode('ascii', 'replace')
- for c in self.invalidChars:
- name = name.replace(c, '_')
-
- pyfile.name = html_unescape(name)
-
- time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url)
- ffmpeg = which("ffmpeg")
- if ffmpeg and time:
- m, s = time.groups()[1:]
- if m is None:
- m = "0"
-
- pyfile.name += " (starting at %s:%s)" % (m, s)
-
- pyfile.name += file_suffix
- filename = self.download(url)
-
- if ffmpeg and time:
- inputfile = filename + "_"
- os.rename(filename, inputfile)
-
- subprocess.call([
- ffmpeg,
- "-ss", "00:%s:%s" % (m, s),
- "-i", inputfile,
- "-vcodec", "copy",
- "-acodec", "copy",
- filename])
-
- os.remove(inputfile)
diff --git a/module/plugins/hoster/ZDF.py b/module/plugins/hoster/ZDF.py
deleted file mode 100644
index 8d3de5b26..000000000
--- a/module/plugins/hoster/ZDF.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from xml.etree.ElementTree import fromstring
-
-from module.plugins.Hoster import Hoster
-
-
-# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/)
-class ZDF(Hoster):
- __name__ = "ZDF Mediathek"
- __type__ = "hoster"
- __version__ = "0.80"
-
- __pattern__ = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*'
-
- __description__ = """ZDF.de hoster 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 = fromstring(self.load(self.XML_API % self.get_id(pyfile.url)))
-
- 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
-
- 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)
diff --git a/module/plugins/hoster/ZShareNet.py b/module/plugins/hoster/ZShareNet.py
deleted file mode 100644
index 8142136bb..000000000
--- a/module/plugins/hoster/ZShareNet.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ZShareNet(DeadHoster):
- __name__ = "ZShareNet"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
- __config__ = []
-
- __description__ = """ZShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("espes", None),
- ("Cptn Sandwich", None)]
-
-
-getInfo = create_getInfo(ZShareNet)
diff --git a/module/plugins/hoster/ZippyshareCom.py b/module/plugins/hoster/ZippyshareCom.py
deleted file mode 100644
index fbac432cd..000000000
--- a/module/plugins/hoster/ZippyshareCom.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ZippyshareCom(SimpleHoster):
- __name__ = "ZippyshareCom"
- __type__ = "hoster"
- __version__ = "0.78"
-
- __pattern__ = r'http://www\d{0,2}\.zippyshare\.com/v(/|iew\.jsp.*key=)(?P<KEY>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Zippyshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("sebdelsol", "seb.morin@gmail.com")]
-
-
- COOKIES = [("zippyshare.com", "ziplocale", "en")]
-
- NAME_PATTERN = r'(<title>Zippyshare.com - |"/)(?P<N>[^/]+)(</title>|";)'
- SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'does not exist (anymore )?on this server<'
-
- LINK_PREMIUM_PATTERN = r"document.location = '(.+?)'"
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
-
- if captcha_key:
- try:
- self.link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- recaptcha.challenge()
-
- except Exception, e:
- self.error(e)
-
- else:
- self.link = self.get_link()
-
- 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(self.html)
- scripts = (s.getText().strip() for s in soup.body.findAll('script', type='text/javascript'))
-
- # meant to be populated with the initialization of all the DOM elements found in the scripts
- initScripts = set()
-
- def replElementById(element):
- id = element.group(1) # id might be either 'x' (a real id) or x (a variable)
- attr = element.group(4) # attr might be None
-
- varName = re.sub(r'-', '', 'GVAR[%s+"_%s"]' %(id, attr))
-
- realid = id.strip('"\'')
- if id != realid: #id is not a variable, so look for realid.attr in the html
- initValues = filter(None, [elt.get(attr, None) for elt in soup.findAll(id=realid)])
- initValue = '"%s"' % initValues[-1] if initValues else 'null'
- initScripts.add('%s = %s;' % (varName, initValue))
-
- return varName
-
- # handle all getElementById
- reVar = r'document.getElementById\(([\'"\w-]+)\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?'
- scripts = [re.sub(reVar, replElementById, script) for script in scripts if script]
-
- # 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 = ['var GVAR = {}'] + list(initScripts) + scripts + ['GVAR["dlbutton_href"]']
- return self.js.eval('\n'.join(scripts))
-
-
-getInfo = create_getInfo(ZippyshareCom)
diff --git a/module/plugins/hoster/__init__.py b/module/plugins/hoster/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/hoster/__init__.py
+++ /dev/null
diff --git a/module/plugins/internal/DeadCrypter.py b/module/plugins/internal/DeadCrypter.py
deleted file mode 100644
index 866d177cf..000000000
--- a/module/plugins/internal/DeadCrypter.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import create_getInfo
-from module.plugins.Crypter import Crypter as _Crypter
-
-
-class DeadCrypter(_Crypter):
- __name__ = "DeadCrypter"
- __type__ = "crypter"
- __version__ = "0.04"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """ Crypter is no longer available """
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- api = super(DeadCrypter, self).apiInfo(url, get, post)
- api['status'] = 1
- return api
-
-
- def setup(self):
- self.pyfile.error = "Crypter is no longer available"
- self.offline() #@TODO: self.offline("Crypter is no longer available")
-
-
-getInfo = create_getInfo(DeadCrypter)
diff --git a/module/plugins/internal/DeadHoster.py b/module/plugins/internal/DeadHoster.py
deleted file mode 100644
index a6ad92607..000000000
--- a/module/plugins/internal/DeadHoster.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import create_getInfo
-from module.plugins.Hoster import Hoster as _Hoster
-
-
-class DeadHoster(_Hoster):
- __name__ = "DeadHoster"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """ Hoster is no longer available """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- api = super(DeadHoster, self).apiInfo(url, get, post)
- api['status'] = 1
- return api
-
-
- def setup(self):
- self.pyfile.error = "Hoster is no longer available"
- self.offline() #@TODO: self.offline("Hoster is no longer available")
-
-
-getInfo = create_getInfo(DeadHoster)
diff --git a/module/plugins/internal/Extractor.py b/module/plugins/internal/Extractor.py
deleted file mode 100644
index 159b65ffe..000000000
--- a/module/plugins/internal/Extractor.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-
-from module.PyFile import PyFile
-
-
-class ArchiveError(Exception):
- pass
-
-
-class CRCError(Exception):
- pass
-
-
-class PasswordError(Exception):
- pass
-
-
-class Extractor:
- __name__ = "Extractor"
- __version__ = "0.24"
-
- __description__ = """Base extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- EXTENSIONS = []
- VERSION = ""
- REPAIR = False
-
-
- @classmethod
- def isArchive(cls, filename):
- name = os.path.basename(filename).lower()
- return any(name.endswith(ext) for ext in cls.EXTENSIONS)
-
-
- @classmethod
- def isMultipart(cls, filename):
- return False
-
-
- @classmethod
- def isUsable(cls):
- """ Check if system statisfy dependencies
- :return: boolean
- """
- return None
-
-
- @classmethod
- def getTargets(cls, files_ids):
- """ Filter suited targets from list of filename id tuple list
- :param files_ids: List of filepathes
- :return: List of targets, id tuple list
- """
- targets = []
- processed = []
-
- for fname, id, fout in files_ids:
- if cls.isArchive(fname):
- pname = re.sub(cls.re_multipart, '', fname) if cls.isMultipart(fname) else os.path.splitext(fname)[0]
- if pname not in processed:
- processed.append(pname)
- targets.append((fname, id, fout))
- return targets
-
-
- def __init__(self, manager, filename, out,
- fullpath=True,
- overwrite=False,
- excludefiles=[],
- renice=0,
- delete='No',
- keepbroken=False,
- fid=None):
- """ Initialize extractor for specific file """
- self.manager = manager
- self.filename = filename
- self.out = out
- self.fullpath = fullpath
- self.overwrite = overwrite
- self.excludefiles = excludefiles
- self.renice = renice
- self.delete = delete
- self.keepbroken = keepbroken
- self.files = [] #: Store extracted files here
-
- pyfile = self.manager.core.files.getFile(fid) if fid else None
- self.notifyProgress = lambda x: pyfile.setProgress(x) if pyfile else lambda x: None
-
-
- def init(self):
- """ Initialize additional data structures """
- pass
-
-
- def check(self):
- """Quick Check by listing content of archive.
- Raises error if password is needed, integrity is questionable or else.
-
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- """
- raise NotImplementedError
-
- def verify(self):
- """Testing with Extractors buildt-in method
- Raises error if password is needed, integrity is questionable or else.
-
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- """
- raise NotImplementedError
-
-
- def repair(self):
- return None
-
-
- def extract(self, password=None):
- """Extract the archive. Raise specific errors in case of failure.
-
- :param progress: Progress function, call this to update status
- :param password password to use
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- :return:
- """
- raise NotImplementedError
-
-
- def getDeleteFiles(self):
- """Return list of files to delete, do *not* delete them here.
-
- :return: List with paths of files to delete
- """
- return [self.filename]
-
-
- def list(self, password=None):
- """Populate self.files at some point while extracting"""
- return self.files
diff --git a/module/plugins/internal/MultiHook.py b/module/plugins/internal/MultiHook.py
deleted file mode 100644
index 51c8ea89f..000000000
--- a/module/plugins/internal/MultiHook.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import traceback
-
-from module.plugins.Hook import Hook
-from module.utils import decode, remove_chars
-
-
-class MultiHook(Hook):
- __name__ = "MultiHook"
- __type__ = "hook"
- __version__ = "0.44"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Hook plugin for multi hoster/crypter"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team" , "admin@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- MIN_RELOAD_INTERVAL = 1 * 60 * 60 #: 1 hour
-
- DOMAIN_REPLACEMENTS = [(r'180upload\.com' , "hundredeightyupload.com"),
- (r'bayfiles\.net' , "bayfiles.com" ),
- (r'cloudnator\.com' , "shragle.com" ),
- (r'dfiles\.eu' , "depositfiles.com" ),
- (r'easy-share\.com' , "crocko.com" ),
- (r'freakshare\.net' , "freakshare.com" ),
- (r'hellshare\.com' , "hellshare.cz" ),
- (r'ifile\.it' , "filecloud.io" ),
- (r'nowdownload\.\w+', "nowdownload.sx" ),
- (r'nowvideo\.\w+' , "nowvideo.sx" ),
- (r'putlocker\.com' , "firedrive.com" ),
- (r'share-?rapid\.cz', "multishare.cz" ),
- (r'ul\.to' , "uploaded.to" ),
- (r'uploaded\.net' , "uploaded.to" ),
- (r'uploadhero\.co' , "uploadhero.com" ),
- (r'zshares\.net' , "zshare.net" ),
- (r'^1' , "one" ),
- (r'^2' , "two" ),
- (r'^3' , "three" ),
- (r'^4' , "four" ),
- (r'^5' , "five" ),
- (r'^6' , "six" ),
- (r'^7' , "seven" ),
- (r'^8' , "eight" ),
- (r'^9' , "nine" ),
- (r'^0' , "zero" )]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
- self.plugins = []
- self.supported = []
- self.new_supported = []
-
- self.account = None
- self.pluginclass = None
- self.pluginmodule = None
- self.pluginname = None
- self.plugintype = None
-
- self.initPlugin()
-
-
- def initPlugin(self):
- self.pluginname = self.__name__.rsplit("Hook", 1)[0]
- plugin, self.plugintype = self.core.pluginManager.findPlugin(self.pluginname)
-
- if plugin:
- self.pluginmodule = self.core.pluginManager.loadModule(self.plugintype, self.pluginname)
- self.pluginclass = getattr(self.pluginmodule, self.pluginname)
- else:
- self.logWarning("Hook plugin will be deactivated due missing plugin reference")
- self.setConfig('activated', False)
-
-
- def loadAccount(self):
- self.account = self.core.accountManager.getAccountPlugin(self.pluginname)
-
- if self.account and not self.account.canUse():
- self.account = None
-
- if not self.account and hasattr(self.pluginclass, "LOGIN_ACCOUNT") and self.pluginclass.LOGIN_ACCOUNT:
- self.logWarning("Hook plugin will be deactivated due missing account reference")
- self.setConfig('activated', False)
-
-
- def getURL(self, *args, **kwargs): #@TODO: Remove in 0.4.10
- """ see HTTPRequest for argument list """
- h = pyreq.getHTTPRequest(timeout=120)
- try:
- if not 'decode' in kwargs:
- kwargs['decode'] = True
- rep = h.load(*args, **kwargs)
- finally:
- h.close()
-
- return rep
-
-
- def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
- """getConfig with default value - sublass may not implements all config options"""
- try:
- return self.getConf(option)
-
- except KeyError:
- return default
-
-
- def pluginsCached(self):
- if self.plugins:
- return self.plugins
-
- for _i in xrange(2):
- try:
- pluginset = self._pluginSet(self.getHosters() if self.plugintype == "hoster" else self.getCrypters())
- break
-
- except Exception, e:
- self.logDebug(e, "Waiting 1 minute and retry")
- time.sleep(60)
- else:
- self.logWarning(_("Fallback to default reload interval due plugin parse error"))
- self.interval = self.MIN_RELOAD_INTERVAL
- return list()
-
- try:
- configmode = self.getConfig("pluginmode", 'all')
- if configmode in ("listed", "unlisted"):
- pluginlist = self.getConfig("pluginlist", '').replace('|', ',').replace(';', ',').split(',')
- configset = self._pluginSet(pluginlist)
-
- if configmode == "listed":
- pluginset &= configset
- else:
- pluginset -= configset
-
- except Exception, e:
- self.logError(e)
-
- self.plugins = list(pluginset)
-
- return self.plugins
-
-
- def _pluginSet(self, plugins):
- regexp = re.compile(r'^[\w\-.^_]{3,63}\.[a-zA-Z]{2,}$', re.U)
- plugins = [decode(p.strip()).lower() for p in plugins if regexp.match(p.strip())]
-
- for r in self.DOMAIN_REPLACEMENTS:
- rf, rt = r
- repr = re.compile(rf, re.I|re.U)
- plugins = [re.sub(rf, rt, p) if repr.match(p) else p for p in plugins]
-
- return set(plugins)
-
-
- def getHosters(self):
- """Load list of supported hoster
-
- :return: List of domain names
- """
- raise NotImplementedError
-
-
- def getCrypters(self):
- """Load list of supported crypters
-
- :return: List of domain names
- """
- raise NotImplementedError
-
-
- #: Threaded _periodical, remove in 0.4.10 and use built-in flag for that
- def _periodical(self):
- try:
- if self.isActivated():
- self.periodical()
-
- except Exception, e:
- self.core.log.error(_("Error executing hooks: %s") % str(e))
- if self.core.debug:
- traceback.print_exc()
-
- self.cb = self.core.scheduler.addJob(self.interval, self._periodical)
-
-
- def periodical(self):
- """reload plugin list periodically"""
- self.loadAccount()
-
- if self.getConfig("reload", True):
- self.interval = max(self.getConfig("reloadinterval", 12) * 60 * 60, self.MIN_RELOAD_INTERVAL)
- else:
- self.core.scheduler.removeJob(self.cb)
- self.cb = None
-
- self.logInfo(_("Reloading supported %s list") % self.plugintype)
-
- old_supported = self.supported
-
- self.supported = []
- self.new_supported = []
- self.plugins = []
-
- self.overridePlugins()
-
- old_supported = [plugin for plugin in old_supported if plugin not in self.supported]
-
- if old_supported:
- self.logDebug("Unload: %s" % ", ".join(old_supported))
- for plugin in old_supported:
- self.unloadPlugin(plugin)
-
-
- def overridePlugins(self):
- excludedList = []
-
- if self.plugintype == "hoster":
- pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.iterkeys())
- accountList = [account.type.lower() for account in self.core.api.getAccounts(False) if account.valid and account.premium]
- else:
- pluginMap = {}
- accountList = [name[::-1].replace("Folder"[::-1], "", 1).lower()[::-1] for name in self.core.pluginManager.crypterPlugins.iterkeys()]
-
- for plugin in self.pluginsCached():
- name = remove_chars(plugin, "-.")
-
- if name in accountList:
- excludedList.append(plugin)
- else:
- if name in pluginMap:
- self.supported.append(pluginMap[name])
- else:
- self.new_supported.append(plugin)
-
- if not self.supported and not self.new_supported:
- self.logError(_("No %s loaded") % self.plugintype)
- return
-
- # inject plugin plugin
- self.logDebug("Overwritten %ss: %s" % (self.plugintype, ", ".join(sorted(self.supported))))
-
- for plugin in self.supported:
- hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
- hdict['new_module'] = self.pluginmodule
- hdict['new_name'] = self.pluginname
-
- if excludedList:
- self.logInfo(_("%ss not overwritten: %s") % (self.plugintype.capitalize(), ", ".join(sorted(excludedList))))
-
- if self.new_supported:
- plugins = sorted(self.new_supported)
-
- self.logDebug("New %ss: %s" % (self.plugintype, ", ".join(plugins)))
-
- # create new regexp
- regexp = r'.*(?P<DOMAIN>%s).*' % "|".join(x.replace('.', '\.') for x in plugins)
- if hasattr(self.pluginclass, "__pattern__") and isinstance(self.pluginclass.__pattern__, basestring) and '://' in self.pluginclass.__pattern__:
- regexp = r'%s|%s' % (self.pluginclass.__pattern__, regexp)
-
- self.logDebug("Regexp: %s" % regexp)
-
- hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
- hdict['pattern'] = regexp
- hdict['re'] = re.compile(regexp)
-
-
- def unloadPlugin(self, plugin):
- hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
- if "module" in hdict:
- hdict.pop('module', None)
-
- if "new_module" in hdict:
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
-
- def unload(self):
- """Remove override for all plugins. Scheduler job is removed by hookmanager"""
- for plugin in self.supported:
- self.unloadPlugin(plugin)
-
- # reset pattern
- hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
-
- hdict['pattern'] = getattr(self.pluginclass, "__pattern__", r'^unmatchable$')
- hdict['re'] = re.compile(hdict['pattern'])
diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py
deleted file mode 100644
index 350397f8b..000000000
--- a/module/plugins/internal/MultiHoster.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Plugin import Fail, Retry
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns, set_cookies
-
-
-class MultiHoster(SimpleHoster):
- __name__ = "MultiHoster"
- __type__ = "hoster"
- __version__ = "0.39"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("revertfailed", "bool", "Revert to standard download if fails", True)]
-
- __description__ = """Multi hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LOGIN_ACCOUNT = True
-
-
- def setup(self):
- self.chunkLimit = 1
- self.multiDL = bool(self.account)
- self.resumeDownload = self.premium
-
-
- def prepare(self):
- self.info = {}
- self.html = ""
- self.link = "" #@TODO: Move to hoster class in 0.4.10
- self.directDL = False #@TODO: Move to hoster class in 0.4.10
-
- if not self.getConfig('use_premium', True):
- self.retryFree()
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- if self.DIRECT_LINK is None:
- self.directDL = self.__pattern__ != r'^unmatchable$' and re.match(self.__pattern__, self.pyfile.url)
- else:
- self.directDL = self.DIRECT_LINK
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def process(self, pyfile):
- try:
- self.prepare()
-
- if self.directDL:
- self.checkInfo()
- self.logDebug("Looking for direct download link...")
- self.handleDirect(pyfile)
-
- if not self.link and not self.lastDownload:
- self.preload()
-
- self.checkErrors()
- self.checkStatus(getinfo=False)
-
- if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as premium download")
- self.handlePremium(pyfile)
-
- elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- self.downloadLink(self.link, True)
- self.checkFile()
-
- except Fail, e: #@TODO: Move to PluginThread in 0.4.10
- if self.premium:
- self.logWarning(_("Premium download failed"))
- self.retryFree()
-
- elif self.getConfig("revertfailed", True) \
- and "new_module" in self.core.pluginManager.hosterPlugins[self.__name__]:
- hdict = self.core.pluginManager.hosterPlugins[self.__name__]
-
- tmp_module = hdict['new_module']
- tmp_name = hdict['new_name']
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
- pyfile.initPlugin()
-
- hdict['new_module'] = tmp_module
- hdict['new_name'] = tmp_name
-
- raise Retry(_("Revert to original hoster plugin"))
-
- else:
- raise Fail(e)
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
- def handleFree(self, pyfile):
- if self.premium:
- raise NotImplementedError
- else:
- self.fail(_("Required premium account not found"))
diff --git a/module/plugins/internal/SevenZip.py b/module/plugins/internal/SevenZip.py
deleted file mode 100644
index 624f6c939..000000000
--- a/module/plugins/internal/SevenZip.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocess
-
-from module.plugins.internal.UnRar import ArchiveError, CRCError, PasswordError, UnRar, renice
-from module.utils import fs_encode, save_join
-
-
-class SevenZip(UnRar):
- __name__ = "SevenZip"
- __version__ = "0.11"
-
- __description__ = """7-Zip extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Michael Nowak" , "" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- CMD = "7z"
- VERSION = ""
-
- EXTENSIONS = [".7z", ".xz", ".zip", ".gz", ".gzip", ".tgz", ".bz2", ".bzip2",
- ".tbz2", ".tbz", ".tar", ".wim", ".swm", ".lzma", ".rar", ".cab",
- ".arj", ".z", ".taz", ".cpio", ".rpm", ".deb", ".lzh", ".lha",
- ".chm", ".chw", ".hxs", ".iso", ".msi", ".doc", ".xls", ".ppt",
- ".dmg", ".xar", ".hfs", ".exe", ".ntfs", ".fat", ".vhd", ".mbr",
- ".squashfs", ".cramfs", ".scap"]
-
-
- #@NOTE: there are some more uncovered 7z formats
- re_filelist = re.compile(r'([\d\:]+)\s+([\d\:]+)\s+([\w\.]+)\s+(\d+)\s+(\d+)\s+(.+)')
- re_wrongpwd = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I)
- re_wrongcrc = re.compile(r'CRC Failed|Can not open file', re.I)
- re_version = re.compile(r'7-Zip\s(?:\[64\]\s)?(\d+\.\d+)', re.I)
-
-
- @classmethod
- def isUsable(cls):
- if os.name == "nt":
- cls.CMD = os.path.join(pypath, "7z.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- else:
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
-
- m = cls.re_version.search(out)
- cls.VERSION = m.group(1) if m else '(version unknown)'
-
- return True
-
-
- def verify(self, password):
- # 7z can't distinguish crc and pw error in test
- p = self.call_cmd("l", "-slt", fs_encode(self.filename))
- out, err = p.communicate()
-
- if self.re_wrongpwd.search(out):
- raise PasswordError
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
-
-
- def check(self, password):
- p = self.call_cmd("l", "-slt", fs_encode(self.filename))
- out, err = p.communicate()
-
- # check if output or error macthes the 'wrong password'-Regexp
- if self.re_wrongpwd.search(out):
- raise PasswordError
-
- if self.re_wrongcrc.search(out):
- raise CRCError(_("Header protected"))
-
-
- def repair(self):
- return False
-
-
- def extract(self, password=None):
- command = "x" if self.fullpath else "e"
-
- p = self.call_cmd(command, '-o' + self.out, fs_encode(self.filename), password=password)
-
- renice(p.pid, self.renice)
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
-
- if err:
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- elif self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- else: #: raise error if anything is on stderr
- raise ArchiveError(err)
-
- if p.returncode > 1:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- self.files = self.list(password)
-
-
- def list(self, password=None):
- command = "l" if self.fullpath else "l"
-
- p = self.call_cmd(command, fs_encode(self.filename), password=password)
- out, err = p.communicate()
-
- if "Can not open" in err:
- raise ArchiveError(_("Cannot open file"))
-
- if p.returncode > 1:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- result = set()
- for groups in self.re_filelist.findall(out):
- f = groups[-1].strip()
- result.add(save_join(self.out, f))
-
- return list(result)
-
-
- def call_cmd(self, command, *xargs, **kwargs):
- args = []
-
- #overwrite flag
- if self.overwrite:
- args.append("-y")
-
- #set a password
- if "password" in kwargs and kwargs["password"]:
- args.append("-p%s" % kwargs["password"])
- else:
- args.append("-p-")
-
- #@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
- call = [self.CMD, command] + args + list(xargs)
-
- self.manager.logDebug(" ".join(call))
-
- p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return p
diff --git a/module/plugins/internal/SimpleCrypter.py b/module/plugins/internal/SimpleCrypter.py
deleted file mode 100644
index 09805cf1a..000000000
--- a/module/plugins/internal/SimpleCrypter.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns, set_cookies
-from module.utils import fixup
-
-
-class SimpleCrypter(Crypter, SimpleHoster):
- __name__ = "SimpleCrypter"
- __type__ = "crypter"
- __version__ = "0.43"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True), #: Overrides core.config['general']['folder_per_package']
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Simple decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Following patterns should be defined by each crypter:
-
- LINK_PATTERN: Download link or regex to catch links in group(1)
- example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- NAME_PATTERN: (optional) folder name or page title
- example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>'
-
- OFFLINE_PATTERN: (optional) Checks if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
-
- You can override the getLinks method if you need a more sophisticated way to extract the links.
-
-
- If the links are splitted on multiple pages you can define the PAGES_PATTERN regex:
-
- PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links
- example: PAGES_PATTERN = r'Pages: (\d+)'
-
- and its loadPage method:
-
-
- def loadPage(self, page_n):
- return the html of the page number page_n
- """
-
- LINK_PATTERN = None
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
- URL_REPLACEMENTS = []
-
- TEXT_ENCODING = False #: Set to True or encoding name if encoding in http header is not correct
- COOKIES = True #: or False or list of tuples [(domain, name, value)]
-
- LOGIN_ACCOUNT = False
- LOGIN_PREMIUM = False
-
-
- #@TODO: Remove in 0.4.10
- def init(self):
- account_name = (self.__name__ + ".py").replace("Folder.py", "").replace(".py", "")
- account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
-
- if account and account.canUse():
- self.user, data = account.selectAccount()
- self.req = account.getAccountRequest(self.user)
- self.premium = account.isPremium(self.user)
-
- self.account = account
-
-
- def prepare(self):
- self.pyfile.error = "" #@TODO: Remove in 0.4.10
-
- self.info = {}
- self.html = ""
- self.links = [] #@TODO: Move to hoster class in 0.4.10
-
- if self.LOGIN_PREMIUM and not self.premium:
- self.fail(_("Required premium account not found"))
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def decrypt(self, pyfile):
- self.prepare()
-
- self.preload()
- self.checkInfo()
-
- self.links = self.getLinks()
-
- if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
- self.handlePages(pyfile)
-
- self.logDebug("Package has %d links" % len(self.links))
-
- if self.links:
- self.packages = [(self.info['name'], self.links, self.info['folder'])]
-
- elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("File info (BEFORE): %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("File info (AFTER): %s" % self.info)
-
- try:
- url = self.info['url'].strip()
- name = self.info['name'].strip()
- if name and name != url:
- self.pyfile.name = name
-
- except Exception:
- pass
-
- try:
- folder = self.info['folder'] = self.pyfile.name
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File folder: %s" % self.pyfile.name)
-
-
- def getLinks(self):
- """
- Returns the links extracted from self.html
- You should override this only if it's impossible to extract links using only the LINK_PATTERN.
- """
- url_p = urlparse.urlparse(self.pyfile.url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
-
- return [urlparse.urljoin(baseurl, link) if not urlparse.urlparse(link).scheme else link \
- for link in re.findall(self.LINK_PATTERN, self.html)]
-
-
- def handlePages(self, pyfile):
- try:
- pages = int(re.search(self.PAGES_PATTERN, self.html).group(1))
- except Exception:
- pages = 1
-
- for p in xrange(2, pages + 1):
- self.html = self.loadPage(p)
- self.links += self.getLinks()
diff --git a/module/plugins/internal/SimpleDereferer.py b/module/plugins/internal/SimpleDereferer.py
deleted file mode 100644
index fad1559c7..000000000
--- a/module/plugins/internal/SimpleDereferer.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.SimpleHoster import getFileURL, set_cookies
-
-
-class SimpleDereferer(Crypter):
- __name__ = "SimpleDereferer"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Simple dereferer plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Following patterns should be defined by each crypter:
-
- LINK_PATTERN: Regex to catch the redirect url in group(1)
- example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- OFFLINE_PATTERN: (optional) Checks if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
-
- You can override the getLinks method if you need a more sophisticated way to extract the redirect url.
- """
-
- LINK_PATTERN = None
-
- TEXT_ENCODING = False
- COOKIES = True
-
-
- def decrypt(self, pyfile):
- link = getFileURL(self, pyfile.url)
-
- if not link:
- try:
- link = urllib.unquote(re.match(self.__pattern__, pyfile.url).group('LINK'))
-
- except Exception:
- self.prepare()
- self.preload()
- self.checkStatus()
-
- link = self.getLink()
-
- if link.strip():
- self.urls = [link.strip()] #@TODO: Remove `.strip()` in 0.4.10
-
- elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
-
-
- def prepare(self):
- self.info = {}
- self.html = ""
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
-
- def preload(self):
- self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def checkStatus(self):
- if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
-
- elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html):
- self.tempOffline()
-
-
- def getLink(self):
- try:
- return re.search(self.LINK_PATTERN, self.html).group(1)
-
- except Exception:
- pass
diff --git a/module/plugins/internal/SimpleHoster.py b/module/plugins/internal/SimpleHoster.py
deleted file mode 100644
index df3d66ea2..000000000
--- a/module/plugins/internal/SimpleHoster.py
+++ /dev/null
@@ -1,761 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import mimetypes
-import os
-import re
-import time
-import urllib
-import urlparse
-
-from module.PyFile import statusMap as _statusMap
-from module.network.CookieJar import CookieJar
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import Fail, Retry
-from module.utils import fixup, fs_encode, parseFileSize
-
-
-#@TODO: Adapt and move to PyFile in 0.4.10
-statusMap = dict((v, k) for k, v in _statusMap.iteritems())
-
-
-#@TODO: Remove in 0.4.10 and redirect to self.error instead
-def _error(self, reason, type):
- if not reason and not type:
- type = "unknown"
-
- msg = _("%s error") % type.strip().capitalize() if type else _("Error")
- msg += (": %s" % reason.strip()) if reason else ""
- msg += _(" | Plugin may be out of date")
-
- raise Fail(msg)
-
-
-#@TODO: Remove in 0.4.10
-def _wait(self, seconds, reconnect):
- if seconds:
- self.setWait(int(seconds) + 1)
-
- if reconnect is not None:
- self.wantReconnect = reconnect
-
- super(SimpleHoster, self).wait()
-
-
-def replace_patterns(string, ruleslist):
- for r in ruleslist:
- rf, rt = r
- string = re.sub(rf, rt, string)
- return string
-
-
-def set_cookies(cj, cookies):
- for cookie in cookies:
- if isinstance(cookie, tuple) and len(cookie) == 3:
- domain, name, value = cookie
- cj.setCookie(domain, name, value)
-
-
-def parseHtmlTagAttrValue(attr_name, tag):
- m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I)
- return m.group(2) if m else None
-
-
-def parseHtmlForm(attr_str, html, input_names={}):
- for form in re.finditer(r"(?P<TAG><form[^>]*%s[^>]*>)(?P<CONTENT>.*?)</?(form|body|html)[^>]*>" % attr_str,
- html, re.S | re.I):
- inputs = {}
- action = parseHtmlTagAttrValue("action", form.group('TAG'))
-
- for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('CONTENT'), re.S | re.I):
- name = parseHtmlTagAttrValue("name", inputtag.group(1))
- if name:
- value = parseHtmlTagAttrValue("value", inputtag.group(1))
- if not value:
- inputs[name] = inputtag.group(3) or ''
- else:
- inputs[name] = value
-
- if input_names:
- # check input attributes
- for key, val in input_names.iteritems():
- if key in inputs:
- if isinstance(val, basestring) and inputs[key] == val:
- continue
- elif isinstance(val, tuple) and inputs[key] in val:
- continue
- elif hasattr(val, "search") and re.match(val, inputs[key]):
- continue
- break #: attibute value does not match
- else:
- break #: attibute name does not match
- else:
- return action, inputs #: passed attribute check
- else:
- # no attribute check
- return action, inputs
-
- return {}, None #: no matching form found
-
-
-#: Deprecated
-def parseFileInfo(plugin, url="", html=""):
- if hasattr(plugin, "getInfo"):
- info = plugin.getInfo(url, html)
- res = info['name'], info['size'], info['status'], info['url']
- else:
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- res = ((url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 0,
- 3 if url else 8,
- url)
-
- return res
-
-
-#@TODO: Remove in 0.4.10
-#@NOTE: Every plugin must have own parseInfos classmethod to work with 0.4.10
-def create_getInfo(plugin):
-
- def generator(list):
- for x in list:
- yield x
-
- if hasattr(plugin, "parseInfos"):
- fn = lambda urls: generator((info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfos(urls))
- else:
- fn = lambda urls: generator(parseFileInfo(url) for url in urls)
-
- return fn
-
-
-def timestamp():
- return int(time.time() * 1000)
-
-
-#@TODO: Move to hoster class in 0.4.10
-def getFileURL(self, url, follow_location=None):
- link = ""
- redirect = 1
-
- if type(follow_location) is int:
- redirect = max(follow_location, 1)
- else:
- redirect = 10
-
- for i in xrange(redirect):
- try:
- self.logDebug("Redirect #%d to: %s" % (i, url))
- header = self.load(url, just_header=True, decode=True)
-
- except Exception: #: Bad bad bad...
- req = pyreq.getHTTPRequest()
- res = req.load(url, just_header=True, decode=True)
-
- req.close()
-
- header = {"code": req.code}
- for line in res.splitlines():
- line = line.strip()
- if not line or ":" not in line:
- continue
-
- key, none, value = line.partition(":")
- key = key.lower().strip()
- value = value.strip()
-
- if key in header:
- if type(header[key]) == list:
- header[key].append(value)
- else:
- header[key] = [header[key], value]
- else:
- header[key] = value
-
- if 'content-disposition' in header:
- link = url
-
- elif 'location' in header and header['location'].strip():
- location = header['location']
-
- if not urlparse.urlparse(location).scheme:
- url_p = urlparse.urlparse(url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
- location = urlparse.urljoin(baseurl, location)
-
- if 'code' in header and header['code'] == 302:
- link = location
-
- if follow_location:
- url = location
- continue
-
- else:
- extension = os.path.splitext(urlparse.urlparse(url).path.split('/')[-1])[-1]
-
- if 'content-type' in header and header['content-type'].strip():
- mimetype = header['content-type'].split(';')[0].strip()
-
- elif extension:
- mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream"
-
- else:
- mimetype = ""
-
- if mimetype and (link or 'html' not in mimetype):
- link = url
- else:
- link = ""
-
- break
-
- else:
- try:
- self.logError(_("Too many redirects"))
- except Exception:
- pass
-
- return link
-
-
-def secondsToMidnight(gmt=0):
- now = datetime.datetime.utcnow() + datetime.timedelta(hours=gmt)
-
- if now.hour is 0 and now.minute < 10:
- midnight = now
- else:
- midnight = now + datetime.timedelta(days=1)
-
- td = midnight.replace(hour=0, minute=10, second=0, microsecond=0) - now
-
- if hasattr(td, 'total_seconds'):
- res = td.total_seconds()
- else: #@NOTE: work-around for python 2.5 and 2.6 missing datetime.timedelta.total_seconds
- res = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
-
- return int(res)
-
-
-class SimpleHoster(Hoster):
- __name__ = "SimpleHoster"
- __type__ = "hoster"
- __version__ = "1.40"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Simple hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Info patterns should be defined by each hoster:
-
- INFO_PATTERN: (optional) Name and Size of the file
- example: INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
- or
- NAME_PATTERN: (optional) Name that will be set for the file
- example: NAME_PATTERN = r'(?P<N>file_name)'
- SIZE_PATTERN: (optional) Size that will be checked for the file
- example: SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
-
- HASHSUM_PATTERN: (optional) Hash code and type of the file
- example: HASHSUM_PATTERN = r'(?P<H>hash_code) (?P<T>MD5)'
-
- OFFLINE_PATTERN: (optional) Check if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Check if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)'
-
-
- Error handling patterns are all optional:
-
- WAIT_PATTERN: (optional) Detect waiting time
- example: WAIT_PATTERN = r''
-
- PREMIUM_ONLY_PATTERN: (optional) Check if the file can be downloaded only with a premium account
- example: PREMIUM_ONLY_PATTERN = r'Premium account required'
-
- ERROR_PATTERN: (optional) Detect any error preventing download
- example: ERROR_PATTERN = r''
-
-
- Instead overriding handleFree and handlePremium methods you can define the following patterns for direct download:
-
- LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download
- example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download
- example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"'
- """
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
- SIZE_REPLACEMENTS = []
- URL_REPLACEMENTS = []
-
- TEXT_ENCODING = False #: Set to True or encoding name if encoding value in http header is not correct
- COOKIES = True #: or False or list of tuples [(domain, name, value)]
- CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account
- DIRECT_LINK = None #: Set to True to looking for direct link (as defined in handleDirect method), set to None to do it if self.account is True else False
- MULTI_HOSTER = False #: Set to True to leech other hoster link (as defined in handleMulti method)
- LOGIN_ACCOUNT = False #: Set to True to require account login
- DISPOSITION = True #: Set to True to use any content-disposition value in http header as file name
-
- directLink = getFileURL #@TODO: Remove in 0.4.10
-
-
- @classmethod
- def parseInfos(cls, urls): #@TODO: Built-in in 0.4.10 core (remove from plugins)
- for url in urls:
- url = replace_patterns(url, cls.URL_REPLACEMENTS)
- yield cls.getInfo(url)
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- return {'name' : (url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 'size' : 0,
- 'status': 3 if url else 8,
- 'url' : url}
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = cls.apiInfo(url)
- online = False if info['status'] != 2 else True
-
- try:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict() #: pattern groups will be saved here
-
- except Exception:
- info['pattern'] = {}
-
- if not html and not online:
- if not url:
- info['error'] = "missing url"
- info['status'] = 1
-
- elif info['status'] is 3 and not getFileURL(None, url):
- try:
- html = getURL(url, cookies=cls.COOKIES, decode=not cls.TEXT_ENCODING)
-
- if isinstance(cls.TEXT_ENCODING, basestring):
- html = unicode(html, cls.TEXT_ENCODING)
-
- except BadHeader, e:
- info['error'] = "%d: %s" % (e.code, e.content)
-
- if e.code is 404:
- info['status'] = 1
-
- elif e.code is 503:
- info['status'] = 6
-
- if html:
- if hasattr(cls, "OFFLINE_PATTERN") and re.search(cls.OFFLINE_PATTERN, html):
- info['status'] = 1
-
- elif hasattr(cls, "TEMP_OFFLINE_PATTERN") and re.search(cls.TEMP_OFFLINE_PATTERN, html):
- info['status'] = 6
-
- else:
- for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"):
- try:
- attr = getattr(cls, pattern)
- pdict = re.search(attr, html).groupdict()
-
- if all(True for k in pdict if k not in info['pattern']):
- info['pattern'].update(pdict)
-
- except AttributeError:
- continue
-
- else:
- online = True
-
- if online:
- info['status'] = 2
-
- if 'N' in info['pattern']:
- info['name'] = replace_patterns(urllib.unquote(info['pattern']['N'].strip()),
- cls.NAME_REPLACEMENTS)
-
- if 'S' in info['pattern']:
- size = replace_patterns(info['pattern']['S'] + info['pattern']['U'] if 'U' in info['pattern'] else info['pattern']['S'],
- cls.SIZE_REPLACEMENTS)
- info['size'] = parseFileSize(size)
-
- elif isinstance(info['size'], basestring):
- unit = info['units'] if 'units' in info else None
- info['size'] = parseFileSize(info['size'], unit)
-
- if 'H' in info['pattern']:
- hashtype = info['pattern']['T'] if 'T' in info['pattern'] else "hash"
- info[hashtype] = info['pattern']['H']
-
- if not info['pattern']:
- info.pop('pattern', None)
-
- return info
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
-
- def prepare(self):
- self.pyfile.error = "" #@TODO: Remove in 0.4.10
-
- self.info = {}
- self.html = ""
- self.link = "" #@TODO: Move to hoster class in 0.4.10
- self.directDL = False #@TODO: Move to hoster class in 0.4.10
- self.multihost = False #@TODO: Move to hoster class in 0.4.10
-
- if not self.getConfig('use_premium', True):
- self.retryFree()
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- if (self.MULTI_HOSTER
- and (self.__pattern__ != self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
- or re.match(self.__pattern__, self.pyfile.url) is None)):
- self.multihost = True
- return
-
- if self.DIRECT_LINK is None:
- self.directDL = bool(self.account)
- else:
- self.directDL = self.DIRECT_LINK
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def preload(self):
- self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def process(self, pyfile):
- try:
- self.prepare()
- self.checkInfo()
-
- if self.directDL:
- self.logDebug("Looking for direct download link...")
- self.handleDirect(pyfile)
-
- if self.multihost and not self.link and not self.lastDownload:
- self.logDebug("Looking for leeched download link...")
- self.handleMulti(pyfile)
-
- if not self.link and not self.lastDownload:
- self.MULTI_HOSTER = False
- self.retry(1, reason="Multi hoster fails")
-
- if not self.link and not self.lastDownload:
- self.preload()
- self.checkInfo()
-
- if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as premium download")
- self.handlePremium(pyfile)
-
- elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- self.downloadLink(self.link, self.DISPOSITION)
- self.checkFile()
-
- except Fail, e: #@TODO: Move to PluginThread in 0.4.10
- if self.premium:
- self.logWarning(_("Premium download failed"))
- self.retryFree()
- else:
- raise Fail(e)
-
-
- def downloadLink(self, link, disposition=True):
- if link and isinstance(link, basestring):
- self.correctCaptcha()
-
- if not urlparse.urlparse(link).scheme:
- url_p = urlparse.urlparse(self.pyfile.url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
- link = urlparse.urljoin(baseurl, link)
-
- self.download(link, ref=False, disposition=disposition)
-
-
- def checkFile(self, rules={}):
- if self.cTask and not self.lastDownload:
- self.invalidCaptcha()
- self.retry(10, reason=_("Wrong captcha"))
-
- elif not self.lastDownload or not os.path.exists(fs_encode(self.lastDownload)):
- self.lastDownload = ""
- self.error(self.pyfile.error or _("No file downloaded"))
-
- else:
- errmsg = self.checkDownload({'Empty file': re.compile(r'\A\s*\Z'),
- 'Html error': re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)')})
-
- if not errmsg:
- for r, p in [('Html file' , re.compile(r'\A\s*<!DOCTYPE html') ),
- ('Request error', re.compile(r'([Aa]n error occured while processing your request)'))]:
- if r not in rules:
- rules[r] = p
-
- for r, a in [('Error' , "ERROR_PATTERN" ),
- ('Premium only', "PREMIUM_ONLY_PATTERN"),
- ('Wait error' , "WAIT_PATTERN" )]:
- if r not in rules and hasattr(self, a):
- rules[r] = getattr(self, a)
-
- errmsg = self.checkDownload(rules)
-
- if not errmsg:
- return
-
- errmsg = errmsg.strip().capitalize()
-
- try:
- errmsg += " | " + self.lastCheck.group(1).strip()
- except Exception:
- pass
-
- self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
- self.retry(3, 60, errmsg)
-
-
- def checkErrors(self):
- if not self.html:
- self.logWarning(_("No html code to check"))
- return
-
- if hasattr(self, 'PREMIUM_ONLY_PATTERN') and not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
- self.fail(_("Link require a premium account to be handled"))
-
- elif hasattr(self, 'ERROR_PATTERN'):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- try:
- errmsg = m.group(1).strip()
- except Exception:
- errmsg = m.group(0).strip()
-
- self.info['error'] = errmsg
-
- if "hour" in errmsg:
- self.wait(1 * 60 * 60, True)
-
- elif re.search("da(il)?y|today", errmsg):
- self.wait(secondsToMidnight(gmt=2), True)
-
- elif "minute" in errmsg:
- self.wait(1 * 60)
-
- else:
- self.error(errmsg)
-
- elif hasattr(self, 'WAIT_PATTERN'):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- try:
- waitmsg = m.group(1).strip()
- except Exception:
- waitmsg = m.group(0).strip()
-
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec|)', waitmsg, re.I))
- self.wait(wait_time, wait_time > 300)
-
- self.info.pop('error', None)
-
-
- def checkStatus(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("Update file info...")
- self.logDebug("Previous file info: %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("Current file info: %s" % self.info)
-
- try:
- status = self.info['status']
-
- if status is 1:
- self.offline()
-
- elif status is 6:
- self.tempOffline()
-
- elif status is 8:
- self.fail(self.info['error'] if 'error' in self.info else "Failed")
-
- finally:
- self.logDebug("File status: %s" % statusMap[status])
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("Update file info...")
- self.logDebug("Previous file info: %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("Current file info: %s" % self.info)
-
- try:
- url = self.info['url'].strip()
- name = self.info['name'].strip()
- if name and name != url:
- self.pyfile.name = name
-
- except Exception:
- pass
-
- try:
- size = self.info['size']
- if size > 0:
- self.pyfile.size = size
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File size: %s byte" % self.pyfile.size if self.pyfile.size > 0 else "File size: Unknown")
-
-
- def checkInfo(self):
- self.checkNameSize()
-
- if self.html:
- self.checkErrors()
- self.checkNameSize()
-
- self.checkStatus(getinfo=False)
-
-
- #: Deprecated
- def getFileInfo(self):
- self.info = {}
- self.checkInfo()
- return self.info
-
-
- def handleDirect(self, pyfile):
- link = self.directLink(pyfile.url, self.resumeDownload)
-
- if link:
- self.logInfo(_("Direct download link detected"))
- self.link = link
- else:
- self.logDebug("Direct download link not found")
-
-
- def handleMulti(self, pyfile): #: Multi-hoster handler
- pass
-
-
- def handleFree(self, pyfile):
- if not hasattr(self, 'LINK_FREE_PATTERN'):
- self.logError(_("Free download not implemented"))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
- else:
- self.link = m.group(1)
-
-
- def handlePremium(self, pyfile):
- if not hasattr(self, 'LINK_PREMIUM_PATTERN'):
- self.logError(_("Premium download not implemented"))
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- if m is None:
- self.error(_("Premium download link not found"))
- else:
- self.link = m.group(1)
-
-
- def longWait(self, wait_time=None, max_tries=3):
- if wait_time and isinstance(wait_time, (int, long, float)):
- time_str = "%dh %dm" % divmod(wait_time / 60, 60)
- else:
- wait_time = 900
- time_str = _("(unknown time)")
- max_tries = 100
-
- self.logInfo(_("Download limit reached, reconnect or wait %s") % time_str)
-
- self.wait(wait_time, True)
- self.retry(max_tries=max_tries, reason=_("Download limit reached"))
-
-
- def parseHtmlForm(self, attr_str="", input_names={}):
- return parseHtmlForm(attr_str, self.html, input_names)
-
-
- def checkTrafficLeft(self):
- if not self.account:
- return True
-
- traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
-
- if traffic is None:
- return False
- elif traffic == -1:
- return True
- else:
- size = self.pyfile.size / 1024
- self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size, self.user, traffic))
- return size <= traffic
-
-
- def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
- """getConfig with default value - sublass may not implements all config options"""
- try:
- return self.getConf(option)
-
- except KeyError:
- return default
-
-
- def retryFree(self):
- if not self.premium:
- return
- self.premium = False
- self.account = None
- self.req = self.core.requestFactory.getRequest(self.__name__)
- self.retries = 0
- raise Retry(_("Fallback to free download"))
-
-
- #@TODO: Remove in 0.4.10
- def wait(self, seconds=0, reconnect=None):
- return _wait(self, seconds, reconnect)
-
-
- def error(self, reason="", type="parse"):
- return _error(self, reason, type)
diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py
deleted file mode 100644
index 5b9f2e1c3..000000000
--- a/module/plugins/internal/UnRar.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocess
-
-from glob import glob
-from string import digits
-
-from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
-from module.utils import fs_decode, fs_encode, save_join
-
-
-def renice(pid, value):
- if value and os.name != "nt":
- try:
- subprocess.Popen(["renice", str(value), str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
-
- except Exception:
- pass
-
-
-class UnRar(Extractor):
- __name__ = "UnRar"
- __version__ = "1.20"
-
- __description__ = """Rar extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- CMD = "unrar"
- VERSION = ""
- EXTENSIONS = [".rar"]
-
-
- re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?',re.I)
-
- re_filefixed = re.compile(r'Building (.+)')
- re_filelist = re.compile(r'^(.)(\s*[\w\.\-]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.M|re.I)
-
- re_wrongpwd = re.compile(r'password', re.I)
- re_wrongcrc = 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
- def isUsable(cls):
- if os.name == "nt":
- try:
- cls.CMD = os.path.join(pypath, "RAR.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- cls.__name__ = "RAR"
- cls.REPAIR = True
-
- except OSError:
- cls.CMD = os.path.join(pypath, "UnRAR.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- else:
- try:
- p = subprocess.Popen(["rar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- cls.__name__ = "RAR"
- cls.REPAIR = True
-
- except OSError: #: fallback to unrar
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
-
- m = cls.re_version.search(out)
- cls.VERSION = m.group(1) if m else '(version unknown)'
-
- return True
-
-
- @classmethod
- def isMultipart(cls, filename):
- return True if cls.re_multipart.search(filename) else False
-
-
- def verify(self, password):
- p = self.call_cmd("t", "-v", fs_encode(self.filename), password=password)
- self._progress(p)
- err = p.stderr.read().strip()
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
-
- def check(self, password):
- p = self.call_cmd("l", "-v", fs_encode(self.filename), password=password)
- out, err = p.communicate()
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- # output only used to check if passworded files are present
- for attr in self.re_filelist.findall(out):
- if attr[0].startswith("*"):
- raise PasswordError
-
-
- def repair(self):
- p = self.call_cmd("rc", fs_encode(self.filename))
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
- if err or p.returncode:
- return False
- return True
-
-
- def _progress(self, process):
- s = ""
- while True:
- c = process.stdout.read(1)
- # quit loop on eof
- if not c:
- break
- # reading a percentage sign -> set progress and restart
- if c == '%':
- self.notifyProgress(int(s))
- s = ""
- # not reading a digit -> therefore restart
- elif c not in digits:
- s = ""
- # add digit to progressstring
- else:
- s += c
-
-
- def extract(self, password=None):
- command = "x" if self.fullpath else "e"
-
- p = self.call_cmd(command, fs_encode(self.filename), self.out, password=password)
-
- renice(p.pid, self.renice)
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
-
- if err:
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- elif self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- else: #: raise error if anything is on stderr
- raise ArchiveError(err)
-
- if p.returncode:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- self.files = self.list(password)
-
-
- def getDeleteFiles(self):
- dir, name = os.path.split(self.filename)
-
- # actually extracted file
- files = [self.filename]
-
- # eventually Multipart Files
- files.extend(save_join(dir, os.path.basename(file)) for file in filter(self.isMultipart, os.listdir(dir))
- if re.sub(self.re_multipart,".rar",name) == re.sub(self.re_multipart,".rar",file))
-
- return files
-
-
- def list(self, password=None):
- command = "vb" if self.fullpath else "lb"
-
- p = self.call_cmd(command, "-v", fs_encode(self.filename), password=password)
- out, err = p.communicate()
-
- if "Cannot open" in err:
- raise ArchiveError(_("Cannot open file"))
-
- if err.strip(): #: only log error at this point
- self.manager.logError(err.strip())
-
- result = set()
- if not self.fullpath and self.VERSION.startswith('5'):
- # NOTE: Unrar 5 always list full path
- for f in fs_decode(out).splitlines():
- f = save_join(self.out, os.path.basename(f.strip()))
- if os.path.isfile(f):
- result.add(save_join(self.out, os.path.basename(f)))
- else:
- for f in fs_decode(out).splitlines():
- f = f.strip()
- result.add(save_join(self.out, f))
-
- return list(result)
-
-
- def call_cmd(self, command, *xargs, **kwargs):
- args = []
-
- # overwrite flag
- if self.overwrite:
- args.append("-o+")
- else:
- args.append("-o-")
- if self.delete != 'No':
- args.append("-or")
-
- for word in self.excludefiles:
- args.append("-x'%s'" % word.strip())
-
- # assume yes on all queries
- args.append("-y")
-
- # set a password
- if "password" in kwargs and kwargs['password']:
- args.append("-p%s" % kwargs['password'])
- else:
- args.append("-p-")
-
- 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)
-
- self.manager.logDebug(" ".join(call))
-
- p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return p
diff --git a/module/plugins/internal/UnZip.py b/module/plugins/internal/UnZip.py
deleted file mode 100644
index 8d3fec370..000000000
--- a/module/plugins/internal/UnZip.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import sys
-import zipfile
-
-from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
-from module.utils import fs_encode
-
-
-class UnZip(Extractor):
- __name__ = "UnZip"
- __version__ = "1.12"
-
- __description__ = """Zip extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- EXTENSIONS = [".zip", ".zip64"]
- VERSION ="(python %s.%s.%s)" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
-
-
- @classmethod
- def isUsable(cls):
- return sys.version_info[:2] >= (2, 6)
-
-
- def list(self, password=None):
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- z.setpassword(password)
- return z.namelist()
-
-
- def check(self, password):
- pass
-
-
- def verify(self):
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- badfile = z.testzip()
-
- if badfile:
- raise CRCError(badfile)
- else:
- raise PasswordError
-
-
- def extract(self, password=None):
- try:
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- z.setpassword(password)
-
- badfile = z.testzip()
-
- if badfile:
- raise CRCError(badfile)
- else:
- z.extractall(self.out)
-
- except (zipfile.BadZipfile, zipfile.LargeZipFile), e:
- raise ArchiveError(e)
-
- except RuntimeError, e:
- if "encrypted" in e:
- raise PasswordError
- else:
- raise ArchiveError(e)
- else:
- self.files = z.namelist()
diff --git a/module/plugins/internal/XFSAccount.py b/module/plugins/internal/XFSAccount.py
deleted file mode 100644
index 41e1bde4d..000000000
--- a/module/plugins/internal/XFSAccount.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import parseHtmlForm, set_cookies
-
-
-class XFSAccount(Account):
- __name__ = "XFSAccount"
- __type__ = "account"
- __version__ = "0.36"
-
- __description__ = """XFileSharing account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- HOSTER_DOMAIN = None
- HOSTER_URL = None
- LOGIN_URL = None
-
- COOKIES = True
-
- PREMIUM_PATTERN = r'\(Premium only\)'
-
- VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})'
-
- TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?<b>\s*(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
- TRAFFIC_LEFT_UNIT = "MB" #: used only if no group <U> was found
-
- LEECH_TRAFFIC_PATTERN = r'Leech Traffic left:<b>.*?(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
- LEECH_TRAFFIC_UNIT = "MB" #: used only if no group <U> was found
-
- LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|account was banned|Error<'
-
-
- def __init__(self, manager, accounts): #@TODO: remove in 0.4.10
- self.init()
- return super(XFSAccount, self).__init__(manager, accounts)
-
-
- def init(self):
- if not self.HOSTER_DOMAIN:
- self.logError(_("Missing HOSTER_DOMAIN"))
- self.COOKIES = False
-
- else:
- if not self.HOSTER_URL:
- self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
- set_cookies(req.cj, self.COOKIES)
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- leechtraffic = None
- premium = None
-
- if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
- return {'validuntil' : validuntil,
- 'trafficleft' : trafficleft,
- 'leechtraffic': leechtraffic,
- 'premium' : premium}
-
- html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True)
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- self.logDebug("Valid until: %s" % validuntil)
-
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- trafficleft = -1
- else:
- premium = False
- validuntil = None #: registered account type (not premium)
- else:
- self.logDebug("VALID_UNTIL_PATTERN not found")
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- try:
- traffic = m.groupdict()
- size = traffic['S']
-
- if "nlimited" in size:
- trafficleft = -1
- if validuntil is None:
- validuntil = -1
- else:
- if 'U' in traffic:
- unit = traffic['U']
- elif isinstance(self.TRAFFIC_LEFT_UNIT, basestring):
- unit = self.TRAFFIC_LEFT_UNIT
- else:
- unit = ""
-
- trafficleft = self.parseTraffic(size + unit)
-
- except Exception, e:
- self.logError(e)
- else:
- self.logDebug("TRAFFIC_LEFT_PATTERN not found")
-
- leech = [m.groupdict() for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, html)]
- if leech:
- leechtraffic = 0
- try:
- for traffic in leech:
- size = traffic['S']
-
- if "nlimited" in size:
- leechtraffic = -1
- if validuntil is None:
- validuntil = -1
- break
- else:
- if 'U' in traffic:
- unit = traffic['U']
- elif isinstance(self.LEECH_TRAFFIC_UNIT, basestring):
- unit = self.LEECH_TRAFFIC_UNIT
- else:
- unit = ""
-
- leechtraffic += self.parseTraffic(size + unit)
-
- except Exception, e:
- self.logError(e)
- else:
- self.logDebug("LEECH_TRAFFIC_PATTERN not found")
-
- return {'validuntil' : validuntil,
- 'trafficleft' : trafficleft,
- 'leechtraffic': leechtraffic,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
- raise Exception(_("Missing HOSTER_DOMAIN"))
-
- if not self.LOGIN_URL:
- self.LOGIN_URL = urlparse.urljoin(self.HOSTER_URL, "login.html")
- html = req.load(self.LOGIN_URL, decode=True)
-
- action, inputs = parseHtmlForm('name="FL"', html)
- if not inputs:
- inputs = {'op' : "login",
- 'redirect': self.HOSTER_URL}
-
- inputs.update({'login' : user,
- 'password': data['password']})
-
- if not action:
- action = self.HOSTER_URL
- html = req.load(action, post=inputs, decode=True)
-
- if re.search(self.LOGIN_FAIL_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/internal/XFSCrypter.py b/module/plugins/internal/XFSCrypter.py
deleted file mode 100644
index 0612be876..000000000
--- a/module/plugins/internal/XFSCrypter.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class XFSCrypter(SimpleCrypter):
- __name__ = "XFSCrypter"
- __type__ = "crypter"
- __version__ = "0.07"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharing decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = None
-
- URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')]
-
- LINK_PATTERN = r'<a href="(.+?)".*?>.+?(?:</a>)?\s*</(?:td|TD)>'
- NAME_PATTERN = r'<[tT]itle>.*?\: (?P<N>.+) folder</[tT]itle>'
-
- OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
- TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
-
-
- def prepare(self):
- if not self.HOSTER_DOMAIN:
- if self.account:
- account = self.account
- else:
- account_name = (self.__name__ + ".py").replace("Folder.py", "").replace(".py", "")
- account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
-
- if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
- self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
- else:
- self.fail(_("Missing HOSTER_DOMAIN"))
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
-
- return super(XFSCrypter, self).prepare()
diff --git a/module/plugins/internal/XFSHoster.py b/module/plugins/internal/XFSHoster.py
deleted file mode 100644
index 33dc6d16b..000000000
--- a/module/plugins/internal/XFSHoster.py
+++ /dev/null
@@ -1,329 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import random
-import re
-import urlparse
-
-from module.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, secondsToMidnight
-from module.utils import html_unescape
-
-
-class XFSHoster(SimpleHoster):
- __name__ = "XFSHoster"
- __type__ = "hoster"
- __version__ = "0.46"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharing hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("stickell" , "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- HOSTER_DOMAIN = None
-
- TEXT_ENCODING = False
- DIRECT_LINK = None
- MULTI_HOSTER = True #@NOTE: Should be default to False for safe, but I'm lazy...
-
- NAME_PATTERN = r'(Filename[ ]*:[ ]*</b>(</td><td nowrap>)?|name="fname"[ ]+value="|<[\w^_]+ class="(file)?name">)\s*(?P<N>.+?)(\s*<|")'
- SIZE_PATTERN = r'(Size[ ]*:[ ]*</b>(</td><td>)?|File:.*>|</font>\s*\(|<[\w^_]+ class="size">)\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
- TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
-
- WAIT_PATTERN = r'<span id="countdown_str">.*?>(\d+)</span>|id="countdown" value=".*?(\d+).*?"'
- PREMIUM_ONLY_PATTERN = r'>This file is available for Premium Users only'
- ERROR_PATTERN = r'(?:class=["\']err["\'].*?>|<[Cc]enter><b>|>Error</td>|>\(ERROR:)(?:\s*<.+?>\s*)*(.+?)(?:["\']|<|\))'
-
- LINK_LEECH_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
- LINK_PATTERN = None #: final download url pattern
-
- CAPTCHA_PATTERN = r'(https?://[^"\']+?/captchas?/[^"\']+)'
- CAPTCHA_BLOCK_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>'
- RECAPTCHA_PATTERN = None
- SOLVEMEDIA_PATTERN = None
-
- FORM_PATTERN = None
- FORM_INPUTS_MAP = None #: dict passed as input_names to parseHtmlForm
-
-
- def setup(self):
- self.chunkLimit = -1 if self.premium else 1
- self.resumeDownload = self.multiDL = self.premium
-
-
- def prepare(self):
- """ Initialize important variables """
- if not self.HOSTER_DOMAIN:
- if self.account:
- account = self.account
- else:
- account = self.pyfile.m.core.accountManager.getAccountPlugin(self.__name__)
-
- if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
- self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
- else:
- self.fail(_("Missing HOSTER_DOMAIN"))
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
-
- if not self.LINK_PATTERN:
- pattern = r'(https?://(?:www\.)?([^/]*?%s|\d+\.\d+\.\d+\.\d+)(\:\d+)?(/d/|(/files)?/\d+/\w+/).+?)["\'<]'
- self.LINK_PATTERN = pattern % self.HOSTER_DOMAIN.replace('.', '\.')
-
- self.captcha = None
- self.errmsg = None
-
- super(XFSHoster, self).prepare()
-
- if self.DIRECT_LINK is None:
- self.directDL = self.premium
-
-
- def handleFree(self, pyfile):
- for i in xrange(1, 6):
- self.logDebug("Getting download link: #%d" % i)
-
- self.checkErrors()
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
-
- data = self.getPostParameters()
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- self.html = self.load(pyfile.url, post=data, decode=True)
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
- if m and not "op=" in m.group(1):
- break
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
- else:
- self.logError(data['op'] if 'op' in data else _("UNKNOWN"))
- return ""
-
- self.link = m.group(1).strip() #@TODO: Remove .strip() in 0.4.10
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
- def handleMulti(self, pyfile):
- if not self.account:
- self.fail(_("Only registered or premium users can use url leech feature"))
-
- #only tested with easybytez.com
- self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN)
-
- action, inputs = self.parseHtmlForm()
-
- upload_id = "%012d" % int(random.random() * 10 ** 12)
- action += upload_id + "&js_on=1&utype=prem&upload_type=url"
-
- inputs['tos'] = '1'
- inputs['url_mass'] = pyfile.url
- inputs['up1oad_type'] = 'url'
-
- self.logDebug(action, inputs)
-
- self.req.setOption("timeout", 600) #: wait for file to upload to easybytez.com
-
- self.html = self.load(action, post=inputs)
-
- self.checkErrors()
-
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- if self.errmsg:
- self.retry(reason=self.errmsg)
- else:
- self.error(_("TEXTAREA F1 not found"))
-
- self.logDebug(inputs)
-
- stmsg = inputs['st']
-
- if stmsg == 'OK':
- self.html = self.load(action, post=inputs)
-
- elif 'Can not leech file' in stmsg:
- self.retry(20, 3 * 60, _("Can not leech file"))
-
- elif 'today' in stmsg:
- self.retry(wait_time=secondsToMidnight(gmt=2), reason=_("You've used all Leech traffic today"))
-
- else:
- self.fail(stmsg)
-
- #get easybytez.com link for uploaded file
- m = re.search(self.LINK_LEECH_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_LEECH_PATTERN not found"))
-
- header = self.load(m.group(1), just_header=True, decode=True)
-
- if 'location' in header: #: Direct download link
- self.link = header['location']
-
-
- def checkErrors(self):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m is None:
- self.errmsg = None
- else:
- self.errmsg = m.group(1).strip()
-
- self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
-
- if 'wait' in self.errmsg:
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec|)', self.errmsg, re.I))
- self.wait(wait_time, wait_time > 300)
-
- elif 'country' in self.errmsg:
- self.fail(_("Downloads are disabled for your country"))
-
- elif 'captcha' in self.errmsg:
- self.invalidCaptcha()
-
- elif 'premium' in self.errmsg and 'require' in self.errmsg:
- self.fail(_("File can be downloaded by premium users only"))
-
- elif 'limit' in self.errmsg:
- if 'days' in self.errmsg:
- delay = secondsToMidnight(gmt=2)
- retries = 3
- else:
- delay = 1 * 60 * 60
- retries = 24
-
- self.wantReconnect = True
- self.retry(retries, delay, _("Download limit exceeded"))
-
- elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
- self.retry(reason=_("Link expired"))
-
- elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg:
- self.tempOffline()
-
- elif 'up to' in self.errmsg:
- self.fail(_("File too large for free download"))
-
- else:
- self.wantReconnect = True
- self.retry(wait_time=60, reason=self.errmsg)
-
- if self.errmsg:
- self.info['error'] = self.errmsg
- else:
- self.info.pop('error', None)
-
-
- def getPostParameters(self):
- if self.FORM_PATTERN or self.FORM_INPUTS_MAP:
- action, inputs = self.parseHtmlForm(self.FORM_PATTERN or "", self.FORM_INPUTS_MAP or {})
- else:
- action, inputs = self.parseHtmlForm(input_names={'op': re.compile(r'^download')})
-
- if not inputs:
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- if self.errmsg:
- self.retry(reason=self.errmsg)
- else:
- self.error(_("TEXTAREA F1 not found"))
-
- self.logDebug(inputs)
-
- if 'op' in inputs:
- if "password" in inputs:
- password = self.getPassword()
- if password:
- inputs['password'] = password
- else:
- self.fail(_("Missing password"))
-
- if not self.premium:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1))
- self.setWait(wait_time, False)
-
- self.captcha = self.handleCaptcha(inputs)
-
- self.wait()
- else:
- inputs['referer'] = self.pyfile.url
-
- if self.premium:
- inputs['method_premium'] = "Premium Download"
- inputs.pop('method_free', None)
- else:
- inputs['method_free'] = "Free Download"
- inputs.pop('method_premium', None)
-
- return inputs
-
-
- def handleCaptcha(self, inputs):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- captcha_url = m.group(1)
- inputs['code'] = self.decryptCaptcha(captcha_url)
- return 1
-
- m = re.search(self.CAPTCHA_BLOCK_PATTERN, self.html, re.S)
- if m:
- captcha_div = m.group(1)
- numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
-
- self.logDebug(captcha_div)
-
- inputs['code'] = "".join(a[1] for a in sorted(numerals, key=lambda num: int(num[0])))
-
- self.logDebug("Captcha code: %s" % inputs['code'], numerals)
- return 2
-
- recaptcha = ReCaptcha(self)
- try:
- captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1)
-
- except Exception:
- captcha_key = recaptcha.detect_key()
-
- else:
- self.logDebug("ReCaptcha key: %s" % captcha_key)
-
- if captcha_key:
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge(captcha_key)
- return 3
-
- solvemedia = SolveMedia(self)
- try:
- captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1)
-
- except Exception:
- captcha_key = solvemedia.detect_key()
-
- else:
- self.logDebug("SolveMedia key: %s" % captcha_key)
-
- if captcha_key:
- inputs['adcopy_response'], inputs['adcopy_challenge'] = solvemedia.challenge(captcha_key)
- return 4
-
- return 0
diff --git a/module/plugins/internal/__init__.py b/module/plugins/internal/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/internal/__init__.py
+++ /dev/null
diff --git a/module/remote/ClickAndLoadBackend.py b/module/remote/ClickAndLoadBackend.py
deleted file mode 100644
index ad8031587..000000000
--- a/module/remote/ClickAndLoadBackend.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-import re
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-from cgi import FieldStorage
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-from RemoteManager import BackendBase
-
-core = None
-js = None
-
-class ClickAndLoadBackend(BackendBase):
- def setup(self, host, port):
- self.httpd = HTTPServer((host, port), CNLHandler)
- global core, js
- core = self.m.core
- js = core.js
-
- def serve(self):
- while self.enabled:
- self.httpd.handle_request()
-
-class CNLHandler(BaseHTTPRequestHandler):
-
- def add_package(self, name, urls, queue=0):
- print "name", name
- print "urls", urls
- print "queue", queue
-
- def get_post(self, name, default=""):
- if name in self.post:
- return self.post[name]
- else:
- return default
-
- def start_response(self, string):
-
- self.send_response(200)
-
- self.send_header("Content-Length", len(string))
- self.send_header("Content-Language", "de")
- self.send_header("Vary", "Accept-Language, Cookie")
- self.send_header("Cache-Control", "no-cache, must-revalidate")
- self.send_header("Content-type", "text/html")
- self.end_headers()
-
- def do_GET(self):
- path = self.path.strip("/").lower()
- #self.wfile.write(path+"\n")
-
- self.map = [ (r"add$", self.add),
- (r"addcrypted$", self.addcrypted),
- (r"addcrypted2$", self.addcrypted2),
- (r"flashgot", self.flashgot),
- (r"crossdomain\.xml", self.crossdomain),
- (r"checkSupportForUrl", self.checksupport),
- (r"jdcheck.js", self.jdcheck),
- (r"", self.flash) ]
-
- func = None
- for r, f in self.map:
- if re.match(r"(flash(got)?/?)?"+r, path):
- func = f
- break
-
- if func:
- try:
- resp = func()
- if not resp: resp = "success"
- resp += "\r\n"
- self.start_response(resp)
- self.wfile.write(resp)
- except Exception,e :
- self.send_error(500, str(e))
- else:
- self.send_error(404, "Not Found")
-
- def do_POST(self):
- form = FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD':'POST',
- 'CONTENT_TYPE':self.headers['Content-Type'],
- })
-
- self.post = {}
- for name in form.keys():
- self.post[name] = form[name].value
-
- return self.do_GET()
-
- def flash(self):
- return "JDownloader"
-
- def add(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, 0)
-
- def addcrypted(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- dlc = self.get_post('crypted').replace(" ", "+")
-
- core.upload_container(package, dlc)
-
- def addcrypted2(self):
- package = self.get_post("source", "ClickAndLoad Package")
- crypted = self.get_post("crypted")
- jk = self.get_post("jk")
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- jk = "%s f()" % jk
- jk = js.eval(jk)
- Key = unhexlify(jk)
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- self.add_package(package, result, 0)
-
-
- def flashgot(self):
- autostart = int(self.get_post('autostart', 0))
- package = self.get_post('package', "FlashGot")
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, autostart)
-
- def crossdomain(self):
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
- def checksupport(self):
- pass
-
- def jdcheck(self):
- rep = "jdownloader=true;\n"
- rep += "var version='10629';\n"
- return rep
diff --git a/module/remote/RemoteManager.py b/module/remote/RemoteManager.py
deleted file mode 100644
index 36eb52a4a..000000000
--- a/module/remote/RemoteManager.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from threading import Thread
-from traceback import print_exc
-
-class BackendBase(Thread):
- def __init__(self, manager):
- Thread.__init__(self)
- self.m = manager
- self.core = manager.core
- self.enabled = True
- self.running = False
-
- def run(self):
- self.running = True
- try:
- self.serve()
- except Exception, e:
- self.core.log.error(_("Remote backend error: %s") % e)
- if self.core.debug:
- print_exc()
- finally:
- self.running = False
-
- def setup(self, host, port):
- pass
-
- def checkDeps(self):
- return True
-
- def serve(self):
- pass
-
- def shutdown(self):
- pass
-
- def stop(self):
- self.enabled = False# set flag and call shutdowm message, so thread can react
- self.shutdown()
-
-
-class RemoteManager():
- available = []
-
- def __init__(self, core):
- self.core = core
- self.backends = []
-
- if self.core.remote:
- self.available.append("ThriftBackend")
-# else:
-# self.available.append("SocketBackend")
-
-
- def startBackends(self):
- host = self.core.config["remote"]["listenaddr"]
- port = self.core.config["remote"]["port"]
-
- for b in self.available:
- klass = getattr(__import__("module.remote.%s" % b, globals(), locals(), [b], -1), b)
- backend = klass(self)
- if not backend.checkDeps():
- continue
- try:
- backend.setup(host, port)
- self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port})
- except Exception, e:
- self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)})
- if self.core.debug:
- print_exc()
- else:
- backend.start()
- self.backends.append(backend)
-
- port += 1
diff --git a/module/remote/SocketBackend.py b/module/remote/SocketBackend.py
deleted file mode 100644
index 1a157cf1d..000000000
--- a/module/remote/SocketBackend.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import SocketServer
-
-from RemoteManager import BackendBase
-
-class RequestHandler(SocketServer.BaseRequestHandler):
-
- def setup(self):
- pass
-
- def handle(self):
-
- print self.request.recv(1024)
-
-
-
-class SocketBackend(BackendBase):
-
- def setup(self, host, port):
- #local only
- self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler)
-
- def serve(self):
- self.server.serve_forever()
diff --git a/module/remote/ThriftBackend.py b/module/remote/ThriftBackend.py
deleted file mode 100644
index b4a2bb25e..000000000
--- a/module/remote/ThriftBackend.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-from os.path import exists
-
-from module.remote.RemoteManager import BackendBase
-
-from thriftbackend.Processor import Processor
-from thriftbackend.Protocol import ProtocolFactory
-from thriftbackend.Socket import ServerSocket
-from thriftbackend.Transport import TransportFactory
-#from thriftbackend.Transport import TransportFactoryCompressed
-
-from thrift.server import TServer
-
-class ThriftBackend(BackendBase):
- def setup(self, host, port):
- processor = Processor(self.core.api)
-
- key = None
- cert = None
-
- if self.core.config['ssl']['activated']:
- if exists(self.core.config['ssl']['cert']) and exists(self.core.config['ssl']['key']):
- self.core.log.info(_("Using SSL ThriftBackend"))
- key = self.core.config['ssl']['key']
- cert = self.core.config['ssl']['cert']
-
- transport = ServerSocket(port, host, key, cert)
-
-
-# tfactory = TransportFactoryCompressed()
- tfactory = TransportFactory()
- pfactory = ProtocolFactory()
-
- self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
- #self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory)
-
- #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
-
- def serve(self):
- self.server.serve()
diff --git a/module/remote/__init__.py b/module/remote/__init__.py
deleted file mode 100644
index 9298f5337..000000000
--- a/module/remote/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# -*- coding: utf-8 -*-
-activated = True
diff --git a/module/remote/socketbackend/__init__.py b/module/remote/socketbackend/__init__.py
deleted file mode 100644
index de6d13128..000000000
--- a/module/remote/socketbackend/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-__author__ = 'christian'
- \ No newline at end of file
diff --git a/module/remote/socketbackend/create_ttypes.py b/module/remote/socketbackend/create_ttypes.py
deleted file mode 100644
index 05662cb50..000000000
--- a/module/remote/socketbackend/create_ttypes.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import inspect
-import sys
-from os.path import abspath, dirname, join
-
-path = dirname(abspath(__file__))
-module = join(path, "..", "..")
-
-sys.path.append(join(module, "lib"))
-sys.path.append(join(module, "remote"))
-
-from thriftbackend.thriftgen.pyload import ttypes
-from thriftbackend.thriftgen.pyload.Pyload import Iface
-
-
-def main():
-
- enums = []
- classes = []
-
- print "generating lightweight ttypes.py"
-
- for name in dir(ttypes):
- klass = getattr(ttypes, name)
-
- if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)):
- continue
-
- if hasattr(klass, "thrift_spec"):
- classes.append(klass)
- else:
- enums.append(klass)
-
-
- f = open(join(path, "ttypes.py"), "wb")
-
- f.write(
- """#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Autogenerated by pyload
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-
-class BaseObject(object):
-\t__slots__ = []
-
-""")
-
- ## generate enums
- for enum in enums:
- name = enum.__name__
- f.write("class %s:\n" % name)
-
- for attr in dir(enum):
- if attr.startswith("_") or attr in ("read", "write"): continue
-
- f.write("\t%s = %s\n" % (attr, getattr(enum, attr)))
-
- f.write("\n")
-
- for klass in classes:
- name = klass.__name__
- base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject"
- f.write("class %s(%s):\n" % (name, base))
- f.write("\t__slots__ = %s\n\n" % klass.__slots__)
-
- #create init
- args = ["self"] + ["%s=None" % x for x in klass.__slots__]
-
- f.write("\tdef __init__(%s):\n" % ", ".join(args))
- for attr in klass.__slots__:
- f.write("\t\tself.%s = %s\n" % (attr, attr))
-
- f.write("\n")
-
- f.write("class Iface:\n")
-
- for name in dir(Iface):
- if name.startswith("_"): continue
-
- func = inspect.getargspec(getattr(Iface, name))
-
- f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args)))
-
- f.write("\n")
-
- f.close()
-
-if __name__ == "__main__":
- main() \ No newline at end of file
diff --git a/module/remote/socketbackend/ttypes.py b/module/remote/socketbackend/ttypes.py
deleted file mode 100644
index f8ea121fa..000000000
--- a/module/remote/socketbackend/ttypes.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Autogenerated by pyload
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-
-class BaseObject(object):
- __slots__ = []
-
-class Destination:
- Collector = 0
- Queue = 1
-
-class DownloadStatus:
- Aborted = 9
- Custom = 11
- Decrypting = 10
- Downloading = 12
- Failed = 8
- Finished = 0
- Offline = 1
- Online = 2
- Processing = 13
- Queued = 3
- Skipped = 4
- Starting = 7
- TempOffline = 6
- Unknown = 14
- Waiting = 5
-
-class ElementType:
- File = 1
- Package = 0
-
-class Input:
- BOOL = 4
- CHOICE = 6
- CLICK = 5
- LIST = 8
- MULTIPLE = 7
- NONE = 0
- PASSWORD = 3
- TABLE = 9
- TEXT = 1
- TEXTBOX = 2
-
-class Output:
- CAPTCHA = 1
- NOTIFICATION = 4
- QUESTION = 2
-
-class AccountInfo(BaseObject):
- __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type']
-
- def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None):
- self.validuntil = validuntil
- self.login = login
- self.options = options
- self.valid = valid
- self.trafficleft = trafficleft
- self.maxtraffic = maxtraffic
- self.premium = premium
- self.type = type
-
-class CaptchaTask(BaseObject):
- __slots__ = ['tid', 'data', 'type', 'resultType']
-
- def __init__(self, tid=None, data=None, type=None, resultType=None):
- self.tid = tid
- self.data = data
- self.type = type
- self.resultType = resultType
-
-class ConfigItem(BaseObject):
- __slots__ = ['name', 'description', 'value', 'type']
-
- def __init__(self, name=None, description=None, value=None, type=None):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
-
-class ConfigSection(BaseObject):
- __slots__ = ['name', 'description', 'items', 'outline']
-
- def __init__(self, name=None, description=None, items=None, outline=None):
- self.name = name
- self.description = description
- self.items = items
- self.outline = outline
-
-class DownloadInfo(BaseObject):
- __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
-
- def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None):
- self.fid = fid
- self.name = name
- self.speed = speed
- self.eta = eta
- self.format_eta = format_eta
- self.bleft = bleft
- self.size = size
- self.format_size = format_size
- self.percent = percent
- self.status = status
- self.statusmsg = statusmsg
- self.format_wait = format_wait
- self.wait_until = wait_until
- self.packageID = packageID
- self.packageName = packageName
- self.plugin = plugin
-
-class EventInfo(BaseObject):
- __slots__ = ['eventname', 'id', 'type', 'destination']
-
- def __init__(self, eventname=None, id=None, type=None, destination=None):
- self.eventname = eventname
- self.id = id
- self.type = type
- self.destination = destination
-
-class FileData(BaseObject):
- __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order']
-
- def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None):
- self.fid = fid
- self.url = url
- self.name = name
- self.plugin = plugin
- self.size = size
- self.format_size = format_size
- self.status = status
- self.statusmsg = statusmsg
- self.packageID = packageID
- self.error = error
- self.order = order
-
-class FileDoesNotExists(Exception):
- __slots__ = ['fid']
-
- def __init__(self, fid=None):
- self.fid = fid
-
-class InteractionTask(BaseObject):
- __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin']
-
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None):
- self.iid = iid
- self.input = input
- self.structure = structure
- self.preset = preset
- self.output = output
- self.data = data
- self.title = title
- self.description = description
- self.plugin = plugin
-
-class OnlineCheck(BaseObject):
- __slots__ = ['rid', 'data']
-
- def __init__(self, rid=None, data=None):
- self.rid = rid
- self.data = data
-
-class OnlineStatus(BaseObject):
- __slots__ = ['name', 'plugin', 'packagename', 'status', 'size']
-
- def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None):
- self.name = name
- self.plugin = plugin
- self.packagename = packagename
- self.status = status
- self.size = size
-
-class PackageData(BaseObject):
- __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids']
-
- def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None):
- self.pid = pid
- self.name = name
- self.folder = folder
- self.site = site
- self.password = password
- self.dest = dest
- self.order = order
- self.linksdone = linksdone
- self.sizedone = sizedone
- self.sizetotal = sizetotal
- self.linkstotal = linkstotal
- self.links = links
- self.fids = fids
-
-class PackageDoesNotExists(Exception):
- __slots__ = ['pid']
-
- def __init__(self, pid=None):
- self.pid = pid
-
-class ServerStatus(BaseObject):
- __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect']
-
- def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None):
- self.pause = pause
- self.active = active
- self.queue = queue
- self.total = total
- self.speed = speed
- self.download = download
- self.reconnect = reconnect
-
-class ServiceCall(BaseObject):
- __slots__ = ['plugin', 'func', 'arguments', 'parseArguments']
-
- def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None):
- self.plugin = plugin
- self.func = func
- self.arguments = arguments
- self.parseArguments = parseArguments
-
-class ServiceDoesNotExists(Exception):
- __slots__ = ['plugin', 'func']
-
- def __init__(self, plugin=None, func=None):
- self.plugin = plugin
- self.func = func
-
-class ServiceException(Exception):
- __slots__ = ['msg']
-
- def __init__(self, msg=None):
- self.msg = msg
-
-class UserData(BaseObject):
- __slots__ = ['name', 'email', 'role', 'permission', 'templateName']
-
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None):
- self.name = name
- self.email = email
- self.role = role
- self.permission = permission
- self.templateName = templateName
-
-class Iface:
- def addFiles(self, pid, links):
- pass
- def addPackage(self, name, links, dest):
- pass
- def call(self, info):
- pass
- def checkOnlineStatus(self, urls):
- pass
- def checkOnlineStatusContainer(self, urls, filename, data):
- pass
- def checkURLs(self, urls):
- pass
- def deleteFiles(self, fids):
- pass
- def deleteFinished(self):
- pass
- def deletePackages(self, pids):
- pass
- def freeSpace(self):
- pass
- def generateAndAddPackages(self, links, dest):
- pass
- def generatePackages(self, links):
- pass
- def getAccountTypes(self):
- pass
- def getAccounts(self, refresh):
- pass
- def getAllInfo(self):
- pass
- def getAllUserData(self):
- pass
- def getCaptchaTask(self, exclusive):
- pass
- def getCaptchaTaskStatus(self, tid):
- pass
- def getCollector(self):
- pass
- def getCollectorData(self):
- pass
- def getConfig(self):
- pass
- def getConfigValue(self, category, option, section):
- pass
- def getEvents(self, uuid):
- pass
- def getFileData(self, fid):
- pass
- def getFileOrder(self, pid):
- pass
- def getInfoByPlugin(self, plugin):
- pass
- def getLog(self, offset):
- pass
- def getPackageData(self, pid):
- pass
- def getPackageInfo(self, pid):
- pass
- def getPackageOrder(self, destination):
- pass
- def getPluginConfig(self):
- pass
- def getQueue(self):
- pass
- def getQueueData(self):
- pass
- def getServerVersion(self):
- pass
- def getServices(self):
- pass
- def getUserData(self, username, password):
- pass
- def hasService(self, plugin, func):
- pass
- def isCaptchaWaiting(self):
- pass
- def isTimeDownload(self):
- pass
- def isTimeReconnect(self):
- pass
- def kill(self):
- pass
- def login(self, username, password):
- pass
- def moveFiles(self, fids, pid):
- pass
- def movePackage(self, destination, pid):
- pass
- def orderFile(self, fid, position):
- pass
- def orderPackage(self, pid, position):
- pass
- def parseURLs(self, html, url):
- pass
- def pauseServer(self):
- pass
- def pollResults(self, rid):
- pass
- def pullFromQueue(self, pid):
- pass
- def pushToQueue(self, pid):
- pass
- def recheckPackage(self, pid):
- pass
- def removeAccount(self, plugin, account):
- pass
- def restart(self):
- pass
- def restartFailed(self):
- pass
- def restartFile(self, fid):
- pass
- def restartPackage(self, pid):
- pass
- def setCaptchaResult(self, tid, result):
- pass
- def setConfigValue(self, category, option, value, section):
- pass
- def setPackageData(self, pid, data):
- pass
- def setPackageName(self, pid, name):
- pass
- def statusDownloads(self):
- pass
- def statusServer(self):
- pass
- def stopAllDownloads(self):
- pass
- def stopDownloads(self, fids):
- pass
- def togglePause(self):
- pass
- def toggleReconnect(self):
- pass
- def unpauseServer(self):
- pass
- def updateAccount(self, plugin, account, password, options):
- pass
- def uploadContainer(self, filename, data):
- pass
-
diff --git a/module/remote/thriftbackend/Processor.py b/module/remote/thriftbackend/Processor.py
deleted file mode 100644
index a8b87c82c..000000000
--- a/module/remote/thriftbackend/Processor.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thriftgen.pyload import Pyload
-
-class Processor(Pyload.Processor):
- def __init__(self, *args, **kwargs):
- Pyload.Processor.__init__(self, *args, **kwargs)
- self.authenticated = {}
-
- def process(self, iprot, oprot):
- trans = oprot.trans
- if trans not in self.authenticated:
- self.authenticated[trans] = False
- oldclose = trans.close
-
- def wrap():
- if self in self.authenticated:
- del self.authenticated[trans]
- oldclose()
-
- trans.close = wrap
- authenticated = self.authenticated[trans]
- (name, type, seqid) = iprot.readMessageBegin()
-
- # unknown method
- if name not in self._processMap:
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name)
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- # not logged in
- elif not authenticated and not name == "login":
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- # 20 - Not logged in (in situ declared error code)
- x = Pyload.TApplicationException(20, 'Not logged in')
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- elif not authenticated and name == "login":
- args = Pyload.login_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = Pyload.login_result()
- # api login
- self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0])
-
- result.success = True if self.authenticated[trans] else False
- oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- elif self._handler.isAuthorized(name, authenticated):
- self._processMap[name](self, seqid, iprot, oprot)
-
- else:
- #no permission
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- # 21 - Not authorized
- x = Pyload.TApplicationException(21, 'Not authorized')
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- return True
diff --git a/module/remote/thriftbackend/Protocol.py b/module/remote/thriftbackend/Protocol.py
deleted file mode 100644
index c42d01459..000000000
--- a/module/remote/thriftbackend/Protocol.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thrift.protocol import TBinaryProtocol
-
-class Protocol(TBinaryProtocol.TBinaryProtocol):
- def writeString(self, str):
- try:
- str = str.encode("utf8", "ignore")
- except Exception, e:
- pass
-
- self.writeI32(len(str))
- self.trans.write(str)
-
- def readString(self):
- len = self.readI32()
- str = self.trans.readAll(len)
- try:
- str = str.decode("utf8", "ignore")
- except:
- pass
-
- return str
-
-
-class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory):
-
- def getProtocol(self, trans):
- prot = Protocol(trans, self.strictRead, self.strictWrite)
- return prot \ No newline at end of file
diff --git a/module/remote/thriftbackend/Socket.py b/module/remote/thriftbackend/Socket.py
deleted file mode 100644
index 2243f9df2..000000000
--- a/module/remote/thriftbackend/Socket.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import socket
-import errno
-
-from time import sleep
-
-from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException
-
-WantReadError = Exception #overwritten when ssl is used
-
-class SecureSocketConnection:
- def __init__(self, connection):
- self.__dict__["connection"] = connection
-
- def __getattr__(self, name):
- return getattr(self.__dict__["connection"], name)
-
- def __setattr__(self, name, value):
- setattr(self.__dict__["connection"], name, value)
-
- def shutdown(self, how=1):
- self.__dict__["connection"].shutdown()
-
- def accept(self):
- connection, address = self.__dict__["connection"].accept()
- return SecureSocketConnection(connection), address
-
- def send(self, buff):
- try:
- return self.__dict__["connection"].send(buff)
- except WantReadError:
- sleep(0.1)
- return self.send(buff)
-
- def recv(self, buff):
- try:
- return self.__dict__["connection"].recv(buff)
- except WantReadError:
- sleep(0.1)
- return self.recv(buff)
-
-class Socket(TSocket):
- def __init__(self, host='localhost', port=7228, ssl=False):
- TSocket.__init__(self, host, port)
- self.ssl = ssl
-
- def open(self):
- if self.ssl:
- SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
- WantReadError = SSL.WantReadError
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
- c.set_connect_state()
- self.handle = SecureSocketConnection(c)
- else:
- self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
- #errno 104 connection reset
-
- self.handle.settimeout(self._timeout)
- self.handle.connect((self.host, self.port))
-
- def read(self, sz):
- try:
- buff = self.handle.recv(sz)
- except socket.error, e:
- if (e.args[0] == errno.ECONNRESET and
- (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
- # freebsd and Mach don't follow POSIX semantic of recv
- # and fail with ECONNRESET if peer performed shutdown.
- # See corresponding comment and code in TSocket::read()
- # in lib/cpp/src/transport/TSocket.cpp.
- self.close()
- # Trigger the check to raise the END_OF_FILE exception below.
- buff = ''
- else:
- raise
- except Exception, e:
- # SSL connection was closed
- if e.args == (-1, 'Unexpected EOF'):
- buff = ''
- elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],):
- #a socket not using ssl tried to connect
- buff = ''
- else:
- raise
-
- if not len(buff):
- raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes')
- return buff
-
-
-class ServerSocket(TServerSocket, Socket):
- def __init__(self, port=7228, host="0.0.0.0", key="", cert=""):
- self.host = host
- self.port = port
- self.key = key
- self.cert = cert
- self.handle = None
-
- def listen(self):
- if self.cert and self.key:
- SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
- WantReadError = SSL.WantReadError
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.use_privatekey_file(self.key)
- ctx.use_certificate_file(self.cert)
-
- tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
- tmpConnection.set_accept_state()
- self.handle = SecureSocketConnection(tmpConnection)
-
- else:
- self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-
- self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- if hasattr(self.handle, 'set_timeout'):
- self.handle.set_timeout(None)
- self.handle.bind((self.host, self.port))
- self.handle.listen(128)
-
- def accept(self):
- client, addr = self.handle.accept()
- result = Socket()
- result.setHandle(client)
- return result
diff --git a/module/remote/thriftbackend/ThriftClient.py b/module/remote/thriftbackend/ThriftClient.py
deleted file mode 100644
index 74363cf62..000000000
--- a/module/remote/thriftbackend/ThriftClient.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-from socket import error
-from os.path import dirname, abspath, join
-from traceback import print_exc
-
-try:
- import thrift
-except ImportError:
- sys.path.append(abspath(join(dirname(abspath(__file__)), "..", "..", "lib")))
-
-from thrift.transport import TTransport
-#from thrift.transport.TZlibTransport import TZlibTransport
-from Socket import Socket
-from Protocol import Protocol
-
-# modules should import ttypes from here, when want to avoid importing API
-
-from thriftgen.pyload import Pyload
-from thriftgen.pyload.ttypes import *
-
-ConnectionClosed = TTransport.TTransportException
-
-class WrongLogin(Exception):
- pass
-
-class NoConnection(Exception):
- pass
-
-class NoSSL(Exception):
- pass
-
-class ThriftClient:
- def __init__(self, host="localhost", port=7227, user="", password=""):
-
- self.createConnection(host, port)
- try:
- self.transport.open()
- except error, e:
- if e.args and e.args[0] in (111, 10061):
- raise NoConnection
- else:
- print_exc()
- raise NoConnection
-
- try:
- correct = self.client.login(user, password)
- except error, e:
- if e.args and e.args[0] == 104:
- #connection reset by peer, probably wants ssl
- try:
- self.createConnection(host, port, True)
- #set timeout or a ssl socket will block when querying none ssl server
- self.socket.setTimeout(10)
-
- except ImportError:
- #@TODO untested
- raise NoSSL
- try:
- self.transport.open()
- correct = self.client.login(user, password)
- finally:
- self.socket.setTimeout(None)
- elif e.args and e.args[0] == 32:
- raise NoConnection
- else:
- print_exc()
- raise NoConnection
-
- if not correct:
- self.transport.close()
- raise WrongLogin
-
- def createConnection(self, host, port, ssl=False):
- self.socket = Socket(host, port, ssl)
- self.transport = TTransport.TBufferedTransport(self.socket)
-# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket))
-
- protocol = Protocol(self.transport)
- self.client = Pyload.Client(protocol)
-
- def close(self):
- self.transport.close()
-
- def __getattr__(self, item):
- return getattr(self.client, item)
-
-if __name__ == "__main__":
-
- client = ThriftClient(user="User", password="pwhere")
-
- print client.getServerVersion()
- print client.statusServer()
- print client.statusDownloads()
- q = client.getQueue()
-
-# for p in q:
-# data = client.getPackageData(p.pid)
-# print data
-# print "Package Name: ", data.name
-
-
- print client.getServices()
- print client.call(Pyload.ServiceCall("UpdateManager", "recheckForUpdates"))
-
- print client.getConfigValue("download", "limit_speed", "core")
-
- client.close() \ No newline at end of file
diff --git a/module/remote/thriftbackend/ThriftTest.py b/module/remote/thriftbackend/ThriftTest.py
deleted file mode 100644
index 69ac6a745..000000000
--- a/module/remote/thriftbackend/ThriftTest.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-from os.path import join,abspath,dirname
-
-path = join((abspath(dirname(__file__))), "..","..", "lib")
-sys.path.append(path)
-
-from thriftgen.pyload import Pyload
-from thriftgen.pyload.ttypes import *
-from Socket import Socket
-
-from thrift import Thrift
-from thrift.transport import TTransport
-
-from Protocol import Protocol
-
-from time import time
-
-import xmlrpclib
-
-def bench(f, *args, **kwargs):
- s = time()
- ret = [f(*args, **kwargs) for i in range(0,100)]
- e = time()
- try:
- print "%s: %f s" % (f._Method__name, e-s)
- except :
- print "%s: %f s" % (f.__name__, e-s)
- return ret
-
-from getpass import getpass
-user = raw_input("user ")
-passwd = getpass("password ")
-
-server_url = "http%s://%s:%s@%s:%s/" % (
- "",
- user,
- passwd,
- "127.0.0.1",
- 7227
-)
-proxy = xmlrpclib.ServerProxy(server_url, allow_none=True)
-
-bench(proxy.get_server_version)
-bench(proxy.status_server)
-bench(proxy.status_downloads)
-#bench(proxy.get_queue)
-#bench(proxy.get_collector)
-print
-try:
-
- # Make socket
- transport = Socket('localhost', 7228, False)
-
- # Buffering is critical. Raw sockets are very slow
- transport = TTransport.TBufferedTransport(transport)
-
- # Wrap in a protocol
- protocol = Protocol(transport)
-
- # Create a client to use the protocol encoder
- client = Pyload.Client(protocol)
-
- # Connect!
- transport.open()
-
- print "Login", client.login(user, passwd)
-
- bench(client.getServerVersion)
- bench(client.statusServer)
- bench(client.statusDownloads)
- #bench(client.getQueue)
- #bench(client.getCollector)
-
- print
- print client.getServerVersion()
- print client.statusServer()
- print client.statusDownloads()
- q = client.getQueue()
-
- for p in q:
- data = client.getPackageData(p.pid)
- print data
- print "Package Name: ", data.name
-
- # Close!
- transport.close()
-
-except Thrift.TException, tx:
- print 'ThriftExpection: %s' % tx.message
diff --git a/module/remote/thriftbackend/Transport.py b/module/remote/thriftbackend/Transport.py
deleted file mode 100644
index 5772c5a9e..000000000
--- a/module/remote/thriftbackend/Transport.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thrift.transport.TTransport import TBufferedTransport
-from thrift.transport.TZlibTransport import TZlibTransport
-
-class Transport(TBufferedTransport):
- DEFAULT_BUFFER = 4096
-
- def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
- TBufferedTransport.__init__(self, trans, rbuf_size)
- self.handle = trans.handle
- self.remoteaddr = trans.handle.getpeername()
-
-class TransportCompressed(TZlibTransport):
- DEFAULT_BUFFER = 4096
-
- def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
- TZlibTransport.__init__(self, trans, rbuf_size)
- self.handle = trans.handle
- self.remoteaddr = trans.handle.getpeername()
-
-class TransportFactory:
- def getTransport(self, trans):
- buffered = Transport(trans)
- return buffered
-
-class TransportFactoryCompressed:
- _last_trans = None
- _last_z = None
-
- def getTransport(self, trans, compresslevel=9):
- if trans == self._last_trans:
- return self._last_z
- ztrans = TransportCompressed(Transport(trans), compresslevel)
- self._last_trans = trans
- self._last_z = ztrans
- return ztrans \ No newline at end of file
diff --git a/module/remote/thriftbackend/__init__.py b/module/remote/thriftbackend/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/remote/thriftbackend/__init__.py
+++ /dev/null
diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift
deleted file mode 100644
index 1542e651a..000000000
--- a/module/remote/thriftbackend/pyload.thrift
+++ /dev/null
@@ -1,337 +0,0 @@
-namespace java org.pyload.thrift
-
-typedef i32 FileID
-typedef i32 PackageID
-typedef i32 TaskID
-typedef i32 ResultID
-typedef i32 InteractionID
-typedef list<string> LinkList
-typedef string PluginName
-typedef byte Progress
-typedef byte Priority
-
-
-enum DownloadStatus {
- Finished
- Offline,
- Online,
- Queued,
- Skipped,
- Waiting,
- TempOffline,
- Starting,
- Failed,
- Aborted,
- Decrypting,
- Custom,
- Downloading,
- Processing,
- Unknown
-}
-
-enum Destination {
- Collector,
- Queue
-}
-
-enum ElementType {
- Package,
- File
-}
-
-// types for user interaction
-// some may only be place holder currently not supported
-// also all input - output combination are not reasonable, see InteractionManager for further info
-enum Input {
- NONE,
- TEXT,
- TEXTBOX,
- PASSWORD,
- BOOL, // confirm like, yes or no dialog
- CLICK, // for positional captchas
- CHOICE, // choice from list
- MULTIPLE, // multiple choice from list of elements
- LIST, // arbitary list of elements
- TABLE // table like data structure
-}
-// more can be implemented by need
-
-// this describes the type of the outgoing interaction
-// ensure they can be logcial or'ed
-enum Output {
- CAPTCHA = 1,
- QUESTION = 2,
- NOTIFICATION = 4,
-}
-
-struct DownloadInfo {
- 1: FileID fid,
- 2: string name,
- 3: i64 speed,
- 4: i32 eta,
- 5: string format_eta,
- 6: i64 bleft,
- 7: i64 size,
- 8: string format_size,
- 9: Progress percent,
- 10: DownloadStatus status,
- 11: string statusmsg,
- 12: string format_wait,
- 13: i64 wait_until,
- 14: PackageID packageID,
- 15: string packageName,
- 16: PluginName plugin,
-}
-
-struct ServerStatus {
- 1: bool pause,
- 2: i16 active,
- 3: i16 queue,
- 4: i16 total,
- 5: i64 speed,
- 6: bool download,
- 7: bool reconnect
-}
-
-struct ConfigItem {
- 1: string name,
- 2: string description,
- 3: string value,
- 4: string type,
-}
-
-struct ConfigSection {
- 1: string name,
- 2: string description,
- 3: list<ConfigItem> items,
- 4: optional string outline
-}
-
-struct FileData {
- 1: FileID fid,
- 2: string url,
- 3: string name,
- 4: PluginName plugin,
- 5: i64 size,
- 6: string format_size,
- 7: DownloadStatus status,
- 8: string statusmsg,
- 9: PackageID packageID,
- 10: string error,
- 11: i16 order
-}
-
-struct PackageData {
- 1: PackageID pid,
- 2: string name,
- 3: string folder,
- 4: string site,
- 5: string password,
- 6: Destination dest,
- 7: i16 order,
- 8: optional i16 linksdone,
- 9: optional i64 sizedone,
- 10: optional i64 sizetotal,
- 11: optional i16 linkstotal,
- 12: optional list<FileData> links,
- 13: optional list<FileID> fids
-}
-
-struct InteractionTask {
- 1: InteractionID iid,
- 2: Input input,
- 3: list<string> structure,
- 4: list<string> preset,
- 5: Output output,
- 6: list<string> data,
- 7: string title,
- 8: string description,
- 9: string plugin,
-}
-
-struct CaptchaTask {
- 1: i16 tid,
- 2: binary data,
- 3: string type,
- 4: string resultType
-}
-
-struct EventInfo {
- 1: string eventname,
- 2: optional i32 id,
- 3: optional ElementType type,
- 4: optional Destination destination
-}
-
-struct UserData {
- 1: string name,
- 2: string email,
- 3: i32 role,
- 4: i32 permission,
- 5: string templateName
-}
-
-struct AccountInfo {
- 1: i64 validuntil,
- 2: string login,
- 3: map<string, list<string>> options,
- 4: bool valid,
- 5: i64 trafficleft,
- 6: i64 maxtraffic,
- 7: bool premium,
- 8: string type,
-}
-
-struct ServiceCall {
- 1: PluginName plugin,
- 2: string func,
- 3: optional list<string> arguments,
- 4: optional bool parseArguments, //default False
-}
-
-struct OnlineStatus {
- 1: string name,
- 2: PluginName plugin,
- 3: string packagename,
- 4: DownloadStatus status,
- 5: i64 size, // size <= 0 : unknown
-}
-
-struct OnlineCheck {
- 1: ResultID rid, // -1 -> nothing more to get
- 2: map<string, OnlineStatus> data, //url to result
-}
-
-
-// exceptions
-
-exception PackageDoesNotExists{
- 1: PackageID pid
-}
-
-exception FileDoesNotExists{
- 1: FileID fid
-}
-
-exception ServiceDoesNotExists{
- 1: string plugin
- 2: string func
-}
-
-exception ServiceException{
- 1: string msg
-}
-
-service Pyload {
-
- //config
- string getConfigValue(1: string category, 2: string option, 3: string section),
- void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section),
- map<string, ConfigSection> getConfig(),
- map<string, ConfigSection> getPluginConfig(),
-
- // server status
- void pauseServer(),
- void unpauseServer(),
- bool togglePause(),
- ServerStatus statusServer(),
- i64 freeSpace(),
- string getServerVersion(),
- void kill(),
- void restart(),
- list<string> getLog(1: i32 offset),
- bool isTimeDownload(),
- bool isTimeReconnect(),
- bool toggleReconnect(),
-
- // download preparing
-
- // packagename - urls
- map<string, LinkList> generatePackages(1: LinkList links),
- map<PluginName, LinkList> checkURLs(1: LinkList urls),
- map<PluginName, LinkList> parseURLs(1: string html, 2: string url),
-
- // parses results and generates packages
- OnlineCheck checkOnlineStatus(1: LinkList urls),
- OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
-
- // poll results from previosly started online check
- OnlineCheck pollResults(1: ResultID rid),
-
- // downloads - information
- list<DownloadInfo> statusDownloads(),
- PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e),
- PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
- FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e),
- list<PackageData> getQueue(),
- list<PackageData> getCollector(),
- list<PackageData> getQueueData(),
- list<PackageData> getCollectorData(),
- map<i16, PackageID> getPackageOrder(1: Destination destination),
- map<i16, FileID> getFileOrder(1: PackageID pid)
-
- // downloads - adding/deleting
- list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest),
- PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest),
- void addFiles(1: PackageID pid, 2: LinkList links),
- void uploadContainer(1: string filename, 2: binary data),
- void deleteFiles(1: list<FileID> fids),
- void deletePackages(1: list<PackageID> pids),
-
- // downloads - modifying
- void pushToQueue(1: PackageID pid),
- void pullFromQueue(1: PackageID pid),
- void restartPackage(1: PackageID pid),
- void restartFile(1: FileID fid),
- void recheckPackage(1: PackageID pid),
- void stopAllDownloads(),
- void stopDownloads(1: list<FileID> fids),
- void setPackageName(1: PackageID pid, 2: string name),
- void movePackage(1: Destination destination, 2: PackageID pid),
- void moveFiles(1: list<FileID> fids, 2: PackageID pid),
- void orderPackage(1: PackageID pid, 2: i16 position),
- void orderFile(1: FileID fid, 2: i16 position),
- void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
- list<PackageID> deleteFinished(),
- void restartFailed(),
-
- //events
- list<EventInfo> getEvents(1: string uuid)
-
- //accounts
- list<AccountInfo> getAccounts(1: bool refresh),
- list<string> getAccountTypes()
- void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options),
- void removeAccount(1: PluginName plugin, 2: string account),
-
- //auth
- bool login(1: string username, 2: string password),
- UserData getUserData(1: string username, 2:string password),
- map<string, UserData> getAllUserData(),
-
- //services
-
- // servicename : description
- map<PluginName, map<string, string>> getServices(),
- bool hasService(1: PluginName plugin, 2: string func),
- string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e),
-
-
- //info
- // {plugin: {name: value}}
- map<PluginName, map<string,string>> getAllInfo(),
- map<string, string> getInfoByPlugin(1: PluginName plugin),
-
- //scheduler
-
- // TODO
-
-
- // User interaction
-
- //captcha
- bool isCaptchaWaiting(),
- CaptchaTask getCaptchaTask(1: bool exclusive),
- string getCaptchaTaskStatus(1: TaskID tid),
- void setCaptchaResult(1: TaskID tid, 2: string result),
-}
diff --git a/module/remote/thriftbackend/thriftgen/__init__.py b/module/remote/thriftbackend/thriftgen/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/remote/thriftbackend/thriftgen/__init__.py
+++ /dev/null
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
deleted file mode 100755
index bfaf5b078..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
+++ /dev/null
@@ -1,571 +0,0 @@
-#!/usr/bin/env python
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-import sys
-import pprint
-from urlparse import urlparse
-from thrift.transport import TTransport
-from thrift.transport import TSocket
-from thrift.transport import THttpClient
-from thrift.protocol import TBinaryProtocol
-
-import Pyload
-from ttypes import *
-
-if len(sys.argv) <= 1 or sys.argv[1] == '--help':
- print ''
- print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]'
- print ''
- print 'Functions:'
- print ' string getConfigValue(string category, string option, string section)'
- print ' void setConfigValue(string category, string option, string value, string section)'
- print ' getConfig()'
- print ' getPluginConfig()'
- print ' void pauseServer()'
- print ' void unpauseServer()'
- print ' bool togglePause()'
- print ' ServerStatus statusServer()'
- print ' i64 freeSpace()'
- print ' string getServerVersion()'
- print ' void kill()'
- print ' void restart()'
- print ' getLog(i32 offset)'
- print ' bool isTimeDownload()'
- print ' bool isTimeReconnect()'
- print ' bool toggleReconnect()'
- print ' generatePackages(LinkList links)'
- print ' checkURLs(LinkList urls)'
- print ' parseURLs(string html, string url)'
- print ' OnlineCheck checkOnlineStatus(LinkList urls)'
- print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)'
- print ' OnlineCheck pollResults(ResultID rid)'
- print ' statusDownloads()'
- print ' PackageData getPackageData(PackageID pid)'
- print ' PackageData getPackageInfo(PackageID pid)'
- print ' FileData getFileData(FileID fid)'
- print ' getQueue()'
- print ' getCollector()'
- print ' getQueueData()'
- print ' getCollectorData()'
- print ' getPackageOrder(Destination destination)'
- print ' getFileOrder(PackageID pid)'
- print ' generateAndAddPackages(LinkList links, Destination dest)'
- print ' PackageID addPackage(string name, LinkList links, Destination dest)'
- print ' void addFiles(PackageID pid, LinkList links)'
- print ' void uploadContainer(string filename, string data)'
- print ' void deleteFiles( fids)'
- print ' void deletePackages( pids)'
- print ' void pushToQueue(PackageID pid)'
- print ' void pullFromQueue(PackageID pid)'
- print ' void restartPackage(PackageID pid)'
- print ' void restartFile(FileID fid)'
- print ' void recheckPackage(PackageID pid)'
- print ' void stopAllDownloads()'
- print ' void stopDownloads( fids)'
- print ' void setPackageName(PackageID pid, string name)'
- print ' void movePackage(Destination destination, PackageID pid)'
- print ' void moveFiles( fids, PackageID pid)'
- print ' void orderPackage(PackageID pid, i16 position)'
- print ' void orderFile(FileID fid, i16 position)'
- print ' void setPackageData(PackageID pid, data)'
- print ' deleteFinished()'
- print ' void restartFailed()'
- print ' getEvents(string uuid)'
- print ' getAccounts(bool refresh)'
- print ' getAccountTypes()'
- print ' void updateAccount(PluginName plugin, string account, string password, options)'
- print ' void removeAccount(PluginName plugin, string account)'
- print ' bool login(string username, string password)'
- print ' UserData getUserData(string username, string password)'
- print ' getAllUserData()'
- print ' getServices()'
- print ' bool hasService(PluginName plugin, string func)'
- print ' string call(ServiceCall info)'
- print ' getAllInfo()'
- print ' getInfoByPlugin(PluginName plugin)'
- print ' bool isCaptchaWaiting()'
- print ' CaptchaTask getCaptchaTask(bool exclusive)'
- print ' string getCaptchaTaskStatus(TaskID tid)'
- print ' void setCaptchaResult(TaskID tid, string result)'
- print ''
- sys.exit(0)
-
-pp = pprint.PrettyPrinter(indent = 2)
-host = 'localhost'
-port = 9090
-uri = ''
-framed = False
-http = False
-argi = 1
-
-if sys.argv[argi] == '-h':
- parts = sys.argv[argi+1].split(':')
- host = parts[0]
- if len(parts) > 1:
- port = int(parts[1])
- argi += 2
-
-if sys.argv[argi] == '-u':
- url = urlparse(sys.argv[argi+1])
- parts = url[1].split(':')
- host = parts[0]
- if len(parts) > 1:
- port = int(parts[1])
- else:
- port = 80
- uri = url[2]
- if url[4]:
- uri += '?%s' % url[4]
- http = True
- argi += 2
-
-if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':
- framed = True
- argi += 1
-
-cmd = sys.argv[argi]
-args = sys.argv[argi+1:]
-
-if http:
- transport = THttpClient.THttpClient(host, port, uri)
-else:
- socket = TSocket.TSocket(host, port)
- if framed:
- transport = TTransport.TFramedTransport(socket)
- else:
- transport = TTransport.TBufferedTransport(socket)
-protocol = TBinaryProtocol.TBinaryProtocol(transport)
-client = Pyload.Client(protocol)
-transport.open()
-
-if cmd == 'getConfigValue':
- if len(args) != 3:
- print 'getConfigValue requires 3 args'
- sys.exit(1)
- pp.pprint(client.getConfigValue(args[0],args[1],args[2],))
-
-elif cmd == 'setConfigValue':
- if len(args) != 4:
- print 'setConfigValue requires 4 args'
- sys.exit(1)
- pp.pprint(client.setConfigValue(args[0],args[1],args[2],args[3],))
-
-elif cmd == 'getConfig':
- if len(args) != 0:
- print 'getConfig requires 0 args'
- sys.exit(1)
- pp.pprint(client.getConfig())
-
-elif cmd == 'getPluginConfig':
- if len(args) != 0:
- print 'getPluginConfig requires 0 args'
- sys.exit(1)
- pp.pprint(client.getPluginConfig())
-
-elif cmd == 'pauseServer':
- if len(args) != 0:
- print 'pauseServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.pauseServer())
-
-elif cmd == 'unpauseServer':
- if len(args) != 0:
- print 'unpauseServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.unpauseServer())
-
-elif cmd == 'togglePause':
- if len(args) != 0:
- print 'togglePause requires 0 args'
- sys.exit(1)
- pp.pprint(client.togglePause())
-
-elif cmd == 'statusServer':
- if len(args) != 0:
- print 'statusServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.statusServer())
-
-elif cmd == 'freeSpace':
- if len(args) != 0:
- print 'freeSpace requires 0 args'
- sys.exit(1)
- pp.pprint(client.freeSpace())
-
-elif cmd == 'getServerVersion':
- if len(args) != 0:
- print 'getServerVersion requires 0 args'
- sys.exit(1)
- pp.pprint(client.getServerVersion())
-
-elif cmd == 'kill':
- if len(args) != 0:
- print 'kill requires 0 args'
- sys.exit(1)
- pp.pprint(client.kill())
-
-elif cmd == 'restart':
- if len(args) != 0:
- print 'restart requires 0 args'
- sys.exit(1)
- pp.pprint(client.restart())
-
-elif cmd == 'getLog':
- if len(args) != 1:
- print 'getLog requires 1 args'
- sys.exit(1)
- pp.pprint(client.getLog(eval(args[0]),))
-
-elif cmd == 'isTimeDownload':
- if len(args) != 0:
- print 'isTimeDownload requires 0 args'
- sys.exit(1)
- pp.pprint(client.isTimeDownload())
-
-elif cmd == 'isTimeReconnect':
- if len(args) != 0:
- print 'isTimeReconnect requires 0 args'
- sys.exit(1)
- pp.pprint(client.isTimeReconnect())
-
-elif cmd == 'toggleReconnect':
- if len(args) != 0:
- print 'toggleReconnect requires 0 args'
- sys.exit(1)
- pp.pprint(client.toggleReconnect())
-
-elif cmd == 'generatePackages':
- if len(args) != 1:
- print 'generatePackages requires 1 args'
- sys.exit(1)
- pp.pprint(client.generatePackages(eval(args[0]),))
-
-elif cmd == 'checkURLs':
- if len(args) != 1:
- print 'checkURLs requires 1 args'
- sys.exit(1)
- pp.pprint(client.checkURLs(eval(args[0]),))
-
-elif cmd == 'parseURLs':
- if len(args) != 2:
- print 'parseURLs requires 2 args'
- sys.exit(1)
- pp.pprint(client.parseURLs(args[0],args[1],))
-
-elif cmd == 'checkOnlineStatus':
- if len(args) != 1:
- print 'checkOnlineStatus requires 1 args'
- sys.exit(1)
- pp.pprint(client.checkOnlineStatus(eval(args[0]),))
-
-elif cmd == 'checkOnlineStatusContainer':
- if len(args) != 3:
- print 'checkOnlineStatusContainer requires 3 args'
- sys.exit(1)
- pp.pprint(client.checkOnlineStatusContainer(eval(args[0]),args[1],args[2],))
-
-elif cmd == 'pollResults':
- if len(args) != 1:
- print 'pollResults requires 1 args'
- sys.exit(1)
- pp.pprint(client.pollResults(eval(args[0]),))
-
-elif cmd == 'statusDownloads':
- if len(args) != 0:
- print 'statusDownloads requires 0 args'
- sys.exit(1)
- pp.pprint(client.statusDownloads())
-
-elif cmd == 'getPackageData':
- if len(args) != 1:
- print 'getPackageData requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageData(eval(args[0]),))
-
-elif cmd == 'getPackageInfo':
- if len(args) != 1:
- print 'getPackageInfo requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageInfo(eval(args[0]),))
-
-elif cmd == 'getFileData':
- if len(args) != 1:
- print 'getFileData requires 1 args'
- sys.exit(1)
- pp.pprint(client.getFileData(eval(args[0]),))
-
-elif cmd == 'getQueue':
- if len(args) != 0:
- print 'getQueue requires 0 args'
- sys.exit(1)
- pp.pprint(client.getQueue())
-
-elif cmd == 'getCollector':
- if len(args) != 0:
- print 'getCollector requires 0 args'
- sys.exit(1)
- pp.pprint(client.getCollector())
-
-elif cmd == 'getQueueData':
- if len(args) != 0:
- print 'getQueueData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getQueueData())
-
-elif cmd == 'getCollectorData':
- if len(args) != 0:
- print 'getCollectorData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getCollectorData())
-
-elif cmd == 'getPackageOrder':
- if len(args) != 1:
- print 'getPackageOrder requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageOrder(eval(args[0]),))
-
-elif cmd == 'getFileOrder':
- if len(args) != 1:
- print 'getFileOrder requires 1 args'
- sys.exit(1)
- pp.pprint(client.getFileOrder(eval(args[0]),))
-
-elif cmd == 'generateAndAddPackages':
- if len(args) != 2:
- print 'generateAndAddPackages requires 2 args'
- sys.exit(1)
- pp.pprint(client.generateAndAddPackages(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'addPackage':
- if len(args) != 3:
- print 'addPackage requires 3 args'
- sys.exit(1)
- pp.pprint(client.addPackage(args[0],eval(args[1]),eval(args[2]),))
-
-elif cmd == 'addFiles':
- if len(args) != 2:
- print 'addFiles requires 2 args'
- sys.exit(1)
- pp.pprint(client.addFiles(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'uploadContainer':
- if len(args) != 2:
- print 'uploadContainer requires 2 args'
- sys.exit(1)
- pp.pprint(client.uploadContainer(args[0],args[1],))
-
-elif cmd == 'deleteFiles':
- if len(args) != 1:
- print 'deleteFiles requires 1 args'
- sys.exit(1)
- pp.pprint(client.deleteFiles(eval(args[0]),))
-
-elif cmd == 'deletePackages':
- if len(args) != 1:
- print 'deletePackages requires 1 args'
- sys.exit(1)
- pp.pprint(client.deletePackages(eval(args[0]),))
-
-elif cmd == 'pushToQueue':
- if len(args) != 1:
- print 'pushToQueue requires 1 args'
- sys.exit(1)
- pp.pprint(client.pushToQueue(eval(args[0]),))
-
-elif cmd == 'pullFromQueue':
- if len(args) != 1:
- print 'pullFromQueue requires 1 args'
- sys.exit(1)
- pp.pprint(client.pullFromQueue(eval(args[0]),))
-
-elif cmd == 'restartPackage':
- if len(args) != 1:
- print 'restartPackage requires 1 args'
- sys.exit(1)
- pp.pprint(client.restartPackage(eval(args[0]),))
-
-elif cmd == 'restartFile':
- if len(args) != 1:
- print 'restartFile requires 1 args'
- sys.exit(1)
- pp.pprint(client.restartFile(eval(args[0]),))
-
-elif cmd == 'recheckPackage':
- if len(args) != 1:
- print 'recheckPackage requires 1 args'
- sys.exit(1)
- pp.pprint(client.recheckPackage(eval(args[0]),))
-
-elif cmd == 'stopAllDownloads':
- if len(args) != 0:
- print 'stopAllDownloads requires 0 args'
- sys.exit(1)
- pp.pprint(client.stopAllDownloads())
-
-elif cmd == 'stopDownloads':
- if len(args) != 1:
- print 'stopDownloads requires 1 args'
- sys.exit(1)
- pp.pprint(client.stopDownloads(eval(args[0]),))
-
-elif cmd == 'setPackageName':
- if len(args) != 2:
- print 'setPackageName requires 2 args'
- sys.exit(1)
- pp.pprint(client.setPackageName(eval(args[0]),args[1],))
-
-elif cmd == 'movePackage':
- if len(args) != 2:
- print 'movePackage requires 2 args'
- sys.exit(1)
- pp.pprint(client.movePackage(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'moveFiles':
- if len(args) != 2:
- print 'moveFiles requires 2 args'
- sys.exit(1)
- pp.pprint(client.moveFiles(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'orderPackage':
- if len(args) != 2:
- print 'orderPackage requires 2 args'
- sys.exit(1)
- pp.pprint(client.orderPackage(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'orderFile':
- if len(args) != 2:
- print 'orderFile requires 2 args'
- sys.exit(1)
- pp.pprint(client.orderFile(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'setPackageData':
- if len(args) != 2:
- print 'setPackageData requires 2 args'
- sys.exit(1)
- pp.pprint(client.setPackageData(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'deleteFinished':
- if len(args) != 0:
- print 'deleteFinished requires 0 args'
- sys.exit(1)
- pp.pprint(client.deleteFinished())
-
-elif cmd == 'restartFailed':
- if len(args) != 0:
- print 'restartFailed requires 0 args'
- sys.exit(1)
- pp.pprint(client.restartFailed())
-
-elif cmd == 'getEvents':
- if len(args) != 1:
- print 'getEvents requires 1 args'
- sys.exit(1)
- pp.pprint(client.getEvents(args[0],))
-
-elif cmd == 'getAccounts':
- if len(args) != 1:
- print 'getAccounts requires 1 args'
- sys.exit(1)
- pp.pprint(client.getAccounts(eval(args[0]),))
-
-elif cmd == 'getAccountTypes':
- if len(args) != 0:
- print 'getAccountTypes requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAccountTypes())
-
-elif cmd == 'updateAccount':
- if len(args) != 4:
- print 'updateAccount requires 4 args'
- sys.exit(1)
- pp.pprint(client.updateAccount(eval(args[0]),args[1],args[2],eval(args[3]),))
-
-elif cmd == 'removeAccount':
- if len(args) != 2:
- print 'removeAccount requires 2 args'
- sys.exit(1)
- pp.pprint(client.removeAccount(eval(args[0]),args[1],))
-
-elif cmd == 'login':
- if len(args) != 2:
- print 'login requires 2 args'
- sys.exit(1)
- pp.pprint(client.login(args[0],args[1],))
-
-elif cmd == 'getUserData':
- if len(args) != 2:
- print 'getUserData requires 2 args'
- sys.exit(1)
- pp.pprint(client.getUserData(args[0],args[1],))
-
-elif cmd == 'getAllUserData':
- if len(args) != 0:
- print 'getAllUserData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAllUserData())
-
-elif cmd == 'getServices':
- if len(args) != 0:
- print 'getServices requires 0 args'
- sys.exit(1)
- pp.pprint(client.getServices())
-
-elif cmd == 'hasService':
- if len(args) != 2:
- print 'hasService requires 2 args'
- sys.exit(1)
- pp.pprint(client.hasService(eval(args[0]),args[1],))
-
-elif cmd == 'call':
- if len(args) != 1:
- print 'call requires 1 args'
- sys.exit(1)
- pp.pprint(client.call(eval(args[0]),))
-
-elif cmd == 'getAllInfo':
- if len(args) != 0:
- print 'getAllInfo requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAllInfo())
-
-elif cmd == 'getInfoByPlugin':
- if len(args) != 1:
- print 'getInfoByPlugin requires 1 args'
- sys.exit(1)
- pp.pprint(client.getInfoByPlugin(eval(args[0]),))
-
-elif cmd == 'isCaptchaWaiting':
- if len(args) != 0:
- print 'isCaptchaWaiting requires 0 args'
- sys.exit(1)
- pp.pprint(client.isCaptchaWaiting())
-
-elif cmd == 'getCaptchaTask':
- if len(args) != 1:
- print 'getCaptchaTask requires 1 args'
- sys.exit(1)
- pp.pprint(client.getCaptchaTask(eval(args[0]),))
-
-elif cmd == 'getCaptchaTaskStatus':
- if len(args) != 1:
- print 'getCaptchaTaskStatus requires 1 args'
- sys.exit(1)
- pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),))
-
-elif cmd == 'setCaptchaResult':
- if len(args) != 2:
- print 'setCaptchaResult requires 2 args'
- sys.exit(1)
- pp.pprint(client.setCaptchaResult(eval(args[0]),args[1],))
-
-else:
- print 'Unrecognized method %s' % cmd
- sys.exit(1)
-
-transport.close()
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
deleted file mode 100644
index 78a42f16a..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
+++ /dev/null
@@ -1,5534 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-from ttypes import *
-from thrift.Thrift import TProcessor
-from thrift.protocol.TBase import TBase, TExceptionBase
-
-
-class Iface(object):
- def getConfigValue(self, category, option, section):
- """
- Parameters:
- - category
- - option
- - section
- """
- pass
-
- def setConfigValue(self, category, option, value, section):
- """
- Parameters:
- - category
- - option
- - value
- - section
- """
- pass
-
- def getConfig(self, ):
- pass
-
- def getPluginConfig(self, ):
- pass
-
- def pauseServer(self, ):
- pass
-
- def unpauseServer(self, ):
- pass
-
- def togglePause(self, ):
- pass
-
- def statusServer(self, ):
- pass
-
- def freeSpace(self, ):
- pass
-
- def getServerVersion(self, ):
- pass
-
- def kill(self, ):
- pass
-
- def restart(self, ):
- pass
-
- def getLog(self, offset):
- """
- Parameters:
- - offset
- """
- pass
-
- def isTimeDownload(self, ):
- pass
-
- def isTimeReconnect(self, ):
- pass
-
- def toggleReconnect(self, ):
- pass
-
- def generatePackages(self, links):
- """
- Parameters:
- - links
- """
- pass
-
- def checkURLs(self, urls):
- """
- Parameters:
- - urls
- """
- pass
-
- def parseURLs(self, html, url):
- """
- Parameters:
- - html
- - url
- """
- pass
-
- def checkOnlineStatus(self, urls):
- """
- Parameters:
- - urls
- """
- pass
-
- def checkOnlineStatusContainer(self, urls, filename, data):
- """
- Parameters:
- - urls
- - filename
- - data
- """
- pass
-
- def pollResults(self, rid):
- """
- Parameters:
- - rid
- """
- pass
-
- def statusDownloads(self, ):
- pass
-
- def getPackageData(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def getPackageInfo(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def getFileData(self, fid):
- """
- Parameters:
- - fid
- """
- pass
-
- def getQueue(self, ):
- pass
-
- def getCollector(self, ):
- pass
-
- def getQueueData(self, ):
- pass
-
- def getCollectorData(self, ):
- pass
-
- def getPackageOrder(self, destination):
- """
- Parameters:
- - destination
- """
- pass
-
- def getFileOrder(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def generateAndAddPackages(self, links, dest):
- """
- Parameters:
- - links
- - dest
- """
- pass
-
- def addPackage(self, name, links, dest):
- """
- Parameters:
- - name
- - links
- - dest
- """
- pass
-
- def addFiles(self, pid, links):
- """
- Parameters:
- - pid
- - links
- """
- pass
-
- def uploadContainer(self, filename, data):
- """
- Parameters:
- - filename
- - data
- """
- pass
-
- def deleteFiles(self, fids):
- """
- Parameters:
- - fids
- """
- pass
-
- def deletePackages(self, pids):
- """
- Parameters:
- - pids
- """
- pass
-
- def pushToQueue(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def pullFromQueue(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def restartPackage(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def restartFile(self, fid):
- """
- Parameters:
- - fid
- """
- pass
-
- def recheckPackage(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def stopAllDownloads(self, ):
- pass
-
- def stopDownloads(self, fids):
- """
- Parameters:
- - fids
- """
- pass
-
- def setPackageName(self, pid, name):
- """
- Parameters:
- - pid
- - name
- """
- pass
-
- def movePackage(self, destination, pid):
- """
- Parameters:
- - destination
- - pid
- """
- pass
-
- def moveFiles(self, fids, pid):
- """
- Parameters:
- - fids
- - pid
- """
- pass
-
- def orderPackage(self, pid, position):
- """
- Parameters:
- - pid
- - position
- """
- pass
-
- def orderFile(self, fid, position):
- """
- Parameters:
- - fid
- - position
- """
- pass
-
- def setPackageData(self, pid, data):
- """
- Parameters:
- - pid
- - data
- """
- pass
-
- def deleteFinished(self, ):
- pass
-
- def restartFailed(self, ):
- pass
-
- def getEvents(self, uuid):
- """
- Parameters:
- - uuid
- """
- pass
-
- def getAccounts(self, refresh):
- """
- Parameters:
- - refresh
- """
- pass
-
- def getAccountTypes(self, ):
- pass
-
- def updateAccount(self, plugin, account, password, options):
- """
- Parameters:
- - plugin
- - account
- - password
- - options
- """
- pass
-
- def removeAccount(self, plugin, account):
- """
- Parameters:
- - plugin
- - account
- """
- pass
-
- def login(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- pass
-
- def getUserData(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- pass
-
- def getAllUserData(self, ):
- pass
-
- def getServices(self, ):
- pass
-
- def hasService(self, plugin, func):
- """
- Parameters:
- - plugin
- - func
- """
- pass
-
- def call(self, info):
- """
- Parameters:
- - info
- """
- pass
-
- def getAllInfo(self, ):
- pass
-
- def getInfoByPlugin(self, plugin):
- """
- Parameters:
- - plugin
- """
- pass
-
- def isCaptchaWaiting(self, ):
- pass
-
- def getCaptchaTask(self, exclusive):
- """
- Parameters:
- - exclusive
- """
- pass
-
- def getCaptchaTaskStatus(self, tid):
- """
- Parameters:
- - tid
- """
- pass
-
- def setCaptchaResult(self, tid, result):
- """
- Parameters:
- - tid
- - result
- """
- pass
-
-
-class Client(Iface):
- def __init__(self, iprot, oprot=None):
- self._iprot = self._oprot = iprot
- if oprot is not None:
- self._oprot = oprot
- self._seqid = 0
-
- def getConfigValue(self, category, option, section):
- """
- Parameters:
- - category
- - option
- - section
- """
- self.send_getConfigValue(category, option, section)
- return self.recv_getConfigValue()
-
- def send_getConfigValue(self, category, option, section):
- self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid)
- args = getConfigValue_args()
- args.category = category
- args.option = option
- args.section = section
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getConfigValue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getConfigValue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result");
-
- def setConfigValue(self, category, option, value, section):
- """
- Parameters:
- - category
- - option
- - value
- - section
- """
- self.send_setConfigValue(category, option, value, section)
- self.recv_setConfigValue()
-
- def send_setConfigValue(self, category, option, value, section):
- self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid)
- args = setConfigValue_args()
- args.category = category
- args.option = option
- args.value = value
- args.section = section
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setConfigValue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setConfigValue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getConfig(self, ):
- self.send_getConfig()
- return self.recv_getConfig()
-
- def send_getConfig(self, ):
- self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid)
- args = getConfig_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getConfig(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getConfig_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result");
-
- def getPluginConfig(self, ):
- self.send_getPluginConfig()
- return self.recv_getPluginConfig()
-
- def send_getPluginConfig(self, ):
- self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid)
- args = getPluginConfig_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPluginConfig(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPluginConfig_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result");
-
- def pauseServer(self, ):
- self.send_pauseServer()
- self.recv_pauseServer()
-
- def send_pauseServer(self, ):
- self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid)
- args = pauseServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pauseServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pauseServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def unpauseServer(self, ):
- self.send_unpauseServer()
- self.recv_unpauseServer()
-
- def send_unpauseServer(self, ):
- self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid)
- args = unpauseServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_unpauseServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = unpauseServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def togglePause(self, ):
- self.send_togglePause()
- return self.recv_togglePause()
-
- def send_togglePause(self, ):
- self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid)
- args = togglePause_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_togglePause(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = togglePause_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result");
-
- def statusServer(self, ):
- self.send_statusServer()
- return self.recv_statusServer()
-
- def send_statusServer(self, ):
- self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid)
- args = statusServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_statusServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = statusServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result");
-
- def freeSpace(self, ):
- self.send_freeSpace()
- return self.recv_freeSpace()
-
- def send_freeSpace(self, ):
- self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid)
- args = freeSpace_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_freeSpace(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = freeSpace_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result");
-
- def getServerVersion(self, ):
- self.send_getServerVersion()
- return self.recv_getServerVersion()
-
- def send_getServerVersion(self, ):
- self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid)
- args = getServerVersion_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getServerVersion(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getServerVersion_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result");
-
- def kill(self, ):
- self.send_kill()
- self.recv_kill()
-
- def send_kill(self, ):
- self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid)
- args = kill_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_kill(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = kill_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restart(self, ):
- self.send_restart()
- self.recv_restart()
-
- def send_restart(self, ):
- self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid)
- args = restart_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restart(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restart_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getLog(self, offset):
- """
- Parameters:
- - offset
- """
- self.send_getLog(offset)
- return self.recv_getLog()
-
- def send_getLog(self, offset):
- self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid)
- args = getLog_args()
- args.offset = offset
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getLog(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getLog_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result");
-
- def isTimeDownload(self, ):
- self.send_isTimeDownload()
- return self.recv_isTimeDownload()
-
- def send_isTimeDownload(self, ):
- self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid)
- args = isTimeDownload_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isTimeDownload(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isTimeDownload_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result");
-
- def isTimeReconnect(self, ):
- self.send_isTimeReconnect()
- return self.recv_isTimeReconnect()
-
- def send_isTimeReconnect(self, ):
- self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid)
- args = isTimeReconnect_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isTimeReconnect(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isTimeReconnect_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result");
-
- def toggleReconnect(self, ):
- self.send_toggleReconnect()
- return self.recv_toggleReconnect()
-
- def send_toggleReconnect(self, ):
- self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid)
- args = toggleReconnect_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_toggleReconnect(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = toggleReconnect_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result");
-
- def generatePackages(self, links):
- """
- Parameters:
- - links
- """
- self.send_generatePackages(links)
- return self.recv_generatePackages()
-
- def send_generatePackages(self, links):
- self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid)
- args = generatePackages_args()
- args.links = links
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_generatePackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = generatePackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result");
-
- def checkURLs(self, urls):
- """
- Parameters:
- - urls
- """
- self.send_checkURLs(urls)
- return self.recv_checkURLs()
-
- def send_checkURLs(self, urls):
- self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid)
- args = checkURLs_args()
- args.urls = urls
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkURLs(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkURLs_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result");
-
- def parseURLs(self, html, url):
- """
- Parameters:
- - html
- - url
- """
- self.send_parseURLs(html, url)
- return self.recv_parseURLs()
-
- def send_parseURLs(self, html, url):
- self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid)
- args = parseURLs_args()
- args.html = html
- args.url = url
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_parseURLs(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = parseURLs_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result");
-
- def checkOnlineStatus(self, urls):
- """
- Parameters:
- - urls
- """
- self.send_checkOnlineStatus(urls)
- return self.recv_checkOnlineStatus()
-
- def send_checkOnlineStatus(self, urls):
- self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid)
- args = checkOnlineStatus_args()
- args.urls = urls
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkOnlineStatus(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkOnlineStatus_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result");
-
- def checkOnlineStatusContainer(self, urls, filename, data):
- """
- Parameters:
- - urls
- - filename
- - data
- """
- self.send_checkOnlineStatusContainer(urls, filename, data)
- return self.recv_checkOnlineStatusContainer()
-
- def send_checkOnlineStatusContainer(self, urls, filename, data):
- self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid)
- args = checkOnlineStatusContainer_args()
- args.urls = urls
- args.filename = filename
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkOnlineStatusContainer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkOnlineStatusContainer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result");
-
- def pollResults(self, rid):
- """
- Parameters:
- - rid
- """
- self.send_pollResults(rid)
- return self.recv_pollResults()
-
- def send_pollResults(self, rid):
- self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid)
- args = pollResults_args()
- args.rid = rid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pollResults(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pollResults_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result");
-
- def statusDownloads(self, ):
- self.send_statusDownloads()
- return self.recv_statusDownloads()
-
- def send_statusDownloads(self, ):
- self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid)
- args = statusDownloads_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_statusDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = statusDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result");
-
- def getPackageData(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getPackageData(pid)
- return self.recv_getPackageData()
-
- def send_getPackageData(self, pid):
- self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid)
- args = getPackageData_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result");
-
- def getPackageInfo(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getPackageInfo(pid)
- return self.recv_getPackageInfo()
-
- def send_getPackageInfo(self, pid):
- self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid)
- args = getPackageInfo_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageInfo(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageInfo_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result");
-
- def getFileData(self, fid):
- """
- Parameters:
- - fid
- """
- self.send_getFileData(fid)
- return self.recv_getFileData()
-
- def send_getFileData(self, fid):
- self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid)
- args = getFileData_args()
- args.fid = fid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getFileData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getFileData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result");
-
- def getQueue(self, ):
- self.send_getQueue()
- return self.recv_getQueue()
-
- def send_getQueue(self, ):
- self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid)
- args = getQueue_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result");
-
- def getCollector(self, ):
- self.send_getCollector()
- return self.recv_getCollector()
-
- def send_getCollector(self, ):
- self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid)
- args = getCollector_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCollector(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCollector_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result");
-
- def getQueueData(self, ):
- self.send_getQueueData()
- return self.recv_getQueueData()
-
- def send_getQueueData(self, ):
- self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid)
- args = getQueueData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getQueueData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getQueueData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result");
-
- def getCollectorData(self, ):
- self.send_getCollectorData()
- return self.recv_getCollectorData()
-
- def send_getCollectorData(self, ):
- self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid)
- args = getCollectorData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCollectorData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCollectorData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result");
-
- def getPackageOrder(self, destination):
- """
- Parameters:
- - destination
- """
- self.send_getPackageOrder(destination)
- return self.recv_getPackageOrder()
-
- def send_getPackageOrder(self, destination):
- self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid)
- args = getPackageOrder_args()
- args.destination = destination
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageOrder(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageOrder_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result");
-
- def getFileOrder(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getFileOrder(pid)
- return self.recv_getFileOrder()
-
- def send_getFileOrder(self, pid):
- self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid)
- args = getFileOrder_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getFileOrder(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getFileOrder_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result");
-
- def generateAndAddPackages(self, links, dest):
- """
- Parameters:
- - links
- - dest
- """
- self.send_generateAndAddPackages(links, dest)
- return self.recv_generateAndAddPackages()
-
- def send_generateAndAddPackages(self, links, dest):
- self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
- args = generateAndAddPackages_args()
- args.links = links
- args.dest = dest
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_generateAndAddPackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = generateAndAddPackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result");
-
- def addPackage(self, name, links, dest):
- """
- Parameters:
- - name
- - links
- - dest
- """
- self.send_addPackage(name, links, dest)
- return self.recv_addPackage()
-
- def send_addPackage(self, name, links, dest):
- self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
- args = addPackage_args()
- args.name = name
- args.links = links
- args.dest = dest
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_addPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = addPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result");
-
- def addFiles(self, pid, links):
- """
- Parameters:
- - pid
- - links
- """
- self.send_addFiles(pid, links)
- self.recv_addFiles()
-
- def send_addFiles(self, pid, links):
- self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid)
- args = addFiles_args()
- args.pid = pid
- args.links = links
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_addFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = addFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def uploadContainer(self, filename, data):
- """
- Parameters:
- - filename
- - data
- """
- self.send_uploadContainer(filename, data)
- self.recv_uploadContainer()
-
- def send_uploadContainer(self, filename, data):
- self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid)
- args = uploadContainer_args()
- args.filename = filename
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_uploadContainer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = uploadContainer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def deleteFiles(self, fids):
- """
- Parameters:
- - fids
- """
- self.send_deleteFiles(fids)
- self.recv_deleteFiles()
-
- def send_deleteFiles(self, fids):
- self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid)
- args = deleteFiles_args()
- args.fids = fids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deleteFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deleteFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def deletePackages(self, pids):
- """
- Parameters:
- - pids
- """
- self.send_deletePackages(pids)
- self.recv_deletePackages()
-
- def send_deletePackages(self, pids):
- self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid)
- args = deletePackages_args()
- args.pids = pids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deletePackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deletePackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def pushToQueue(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_pushToQueue(pid)
- self.recv_pushToQueue()
-
- def send_pushToQueue(self, pid):
- self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid)
- args = pushToQueue_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pushToQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pushToQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def pullFromQueue(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_pullFromQueue(pid)
- self.recv_pullFromQueue()
-
- def send_pullFromQueue(self, pid):
- self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid)
- args = pullFromQueue_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pullFromQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pullFromQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restartPackage(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_restartPackage(pid)
- self.recv_restartPackage()
-
- def send_restartPackage(self, pid):
- self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid)
- args = restartPackage_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restartFile(self, fid):
- """
- Parameters:
- - fid
- """
- self.send_restartFile(fid)
- self.recv_restartFile()
-
- def send_restartFile(self, fid):
- self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid)
- args = restartFile_args()
- args.fid = fid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartFile(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartFile_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def recheckPackage(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_recheckPackage(pid)
- self.recv_recheckPackage()
-
- def send_recheckPackage(self, pid):
- self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid)
- args = recheckPackage_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_recheckPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = recheckPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def stopAllDownloads(self, ):
- self.send_stopAllDownloads()
- self.recv_stopAllDownloads()
-
- def send_stopAllDownloads(self, ):
- self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid)
- args = stopAllDownloads_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_stopAllDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = stopAllDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def stopDownloads(self, fids):
- """
- Parameters:
- - fids
- """
- self.send_stopDownloads(fids)
- self.recv_stopDownloads()
-
- def send_stopDownloads(self, fids):
- self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid)
- args = stopDownloads_args()
- args.fids = fids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_stopDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = stopDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def setPackageName(self, pid, name):
- """
- Parameters:
- - pid
- - name
- """
- self.send_setPackageName(pid, name)
- self.recv_setPackageName()
-
- def send_setPackageName(self, pid, name):
- self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid)
- args = setPackageName_args()
- args.pid = pid
- args.name = name
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setPackageName(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setPackageName_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def movePackage(self, destination, pid):
- """
- Parameters:
- - destination
- - pid
- """
- self.send_movePackage(destination, pid)
- self.recv_movePackage()
-
- def send_movePackage(self, destination, pid):
- self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid)
- args = movePackage_args()
- args.destination = destination
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_movePackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = movePackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def moveFiles(self, fids, pid):
- """
- Parameters:
- - fids
- - pid
- """
- self.send_moveFiles(fids, pid)
- self.recv_moveFiles()
-
- def send_moveFiles(self, fids, pid):
- self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid)
- args = moveFiles_args()
- args.fids = fids
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_moveFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = moveFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def orderPackage(self, pid, position):
- """
- Parameters:
- - pid
- - position
- """
- self.send_orderPackage(pid, position)
- self.recv_orderPackage()
-
- def send_orderPackage(self, pid, position):
- self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid)
- args = orderPackage_args()
- args.pid = pid
- args.position = position
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_orderPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = orderPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def orderFile(self, fid, position):
- """
- Parameters:
- - fid
- - position
- """
- self.send_orderFile(fid, position)
- self.recv_orderFile()
-
- def send_orderFile(self, fid, position):
- self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid)
- args = orderFile_args()
- args.fid = fid
- args.position = position
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_orderFile(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = orderFile_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def setPackageData(self, pid, data):
- """
- Parameters:
- - pid
- - data
- """
- self.send_setPackageData(pid, data)
- self.recv_setPackageData()
-
- def send_setPackageData(self, pid, data):
- self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid)
- args = setPackageData_args()
- args.pid = pid
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setPackageData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setPackageData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.e is not None:
- raise result.e
- return
-
- def deleteFinished(self, ):
- self.send_deleteFinished()
- return self.recv_deleteFinished()
-
- def send_deleteFinished(self, ):
- self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid)
- args = deleteFinished_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deleteFinished(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deleteFinished_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result");
-
- def restartFailed(self, ):
- self.send_restartFailed()
- self.recv_restartFailed()
-
- def send_restartFailed(self, ):
- self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid)
- args = restartFailed_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartFailed(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartFailed_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getEvents(self, uuid):
- """
- Parameters:
- - uuid
- """
- self.send_getEvents(uuid)
- return self.recv_getEvents()
-
- def send_getEvents(self, uuid):
- self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid)
- args = getEvents_args()
- args.uuid = uuid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getEvents(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getEvents_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result");
-
- def getAccounts(self, refresh):
- """
- Parameters:
- - refresh
- """
- self.send_getAccounts(refresh)
- return self.recv_getAccounts()
-
- def send_getAccounts(self, refresh):
- self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid)
- args = getAccounts_args()
- args.refresh = refresh
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAccounts(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAccounts_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result");
-
- def getAccountTypes(self, ):
- self.send_getAccountTypes()
- return self.recv_getAccountTypes()
-
- def send_getAccountTypes(self, ):
- self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid)
- args = getAccountTypes_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAccountTypes(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAccountTypes_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result");
-
- def updateAccount(self, plugin, account, password, options):
- """
- Parameters:
- - plugin
- - account
- - password
- - options
- """
- self.send_updateAccount(plugin, account, password, options)
- self.recv_updateAccount()
-
- def send_updateAccount(self, plugin, account, password, options):
- self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid)
- args = updateAccount_args()
- args.plugin = plugin
- args.account = account
- args.password = password
- args.options = options
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_updateAccount(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = updateAccount_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def removeAccount(self, plugin, account):
- """
- Parameters:
- - plugin
- - account
- """
- self.send_removeAccount(plugin, account)
- self.recv_removeAccount()
-
- def send_removeAccount(self, plugin, account):
- self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid)
- args = removeAccount_args()
- args.plugin = plugin
- args.account = account
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_removeAccount(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = removeAccount_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def login(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- self.send_login(username, password)
- return self.recv_login()
-
- def send_login(self, username, password):
- self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid)
- args = login_args()
- args.username = username
- args.password = password
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_login(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = login_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result");
-
- def getUserData(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- self.send_getUserData(username, password)
- return self.recv_getUserData()
-
- def send_getUserData(self, username, password):
- self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid)
- args = getUserData_args()
- args.username = username
- args.password = password
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getUserData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getUserData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result");
-
- def getAllUserData(self, ):
- self.send_getAllUserData()
- return self.recv_getAllUserData()
-
- def send_getAllUserData(self, ):
- self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid)
- args = getAllUserData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAllUserData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAllUserData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result");
-
- def getServices(self, ):
- self.send_getServices()
- return self.recv_getServices()
-
- def send_getServices(self, ):
- self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid)
- args = getServices_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getServices(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getServices_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result");
-
- def hasService(self, plugin, func):
- """
- Parameters:
- - plugin
- - func
- """
- self.send_hasService(plugin, func)
- return self.recv_hasService()
-
- def send_hasService(self, plugin, func):
- self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid)
- args = hasService_args()
- args.plugin = plugin
- args.func = func
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_hasService(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = hasService_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result");
-
- def call(self, info):
- """
- Parameters:
- - info
- """
- self.send_call(info)
- return self.recv_call()
-
- def send_call(self, info):
- self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid)
- args = call_args()
- args.info = info
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_call(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = call_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.ex is not None:
- raise result.ex
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result");
-
- def getAllInfo(self, ):
- self.send_getAllInfo()
- return self.recv_getAllInfo()
-
- def send_getAllInfo(self, ):
- self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid)
- args = getAllInfo_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAllInfo(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAllInfo_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result");
-
- def getInfoByPlugin(self, plugin):
- """
- Parameters:
- - plugin
- """
- self.send_getInfoByPlugin(plugin)
- return self.recv_getInfoByPlugin()
-
- def send_getInfoByPlugin(self, plugin):
- self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid)
- args = getInfoByPlugin_args()
- args.plugin = plugin
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getInfoByPlugin(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getInfoByPlugin_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result");
-
- def isCaptchaWaiting(self, ):
- self.send_isCaptchaWaiting()
- return self.recv_isCaptchaWaiting()
-
- def send_isCaptchaWaiting(self, ):
- self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid)
- args = isCaptchaWaiting_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isCaptchaWaiting(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isCaptchaWaiting_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result");
-
- def getCaptchaTask(self, exclusive):
- """
- Parameters:
- - exclusive
- """
- self.send_getCaptchaTask(exclusive)
- return self.recv_getCaptchaTask()
-
- def send_getCaptchaTask(self, exclusive):
- self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid)
- args = getCaptchaTask_args()
- args.exclusive = exclusive
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCaptchaTask(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCaptchaTask_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result");
-
- def getCaptchaTaskStatus(self, tid):
- """
- Parameters:
- - tid
- """
- self.send_getCaptchaTaskStatus(tid)
- return self.recv_getCaptchaTaskStatus()
-
- def send_getCaptchaTaskStatus(self, tid):
- self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid)
- args = getCaptchaTaskStatus_args()
- args.tid = tid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCaptchaTaskStatus(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCaptchaTaskStatus_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result");
-
- def setCaptchaResult(self, tid, result):
- """
- Parameters:
- - tid
- - result
- """
- self.send_setCaptchaResult(tid, result)
- self.recv_setCaptchaResult()
-
- def send_setCaptchaResult(self, tid, result):
- self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid)
- args = setCaptchaResult_args()
- args.tid = tid
- args.result = result
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setCaptchaResult(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setCaptchaResult_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
-
-class Processor(Iface, TProcessor):
- def __init__(self, handler):
- self._handler = handler
- self._processMap = {}
- self._processMap["getConfigValue"] = Processor.process_getConfigValue
- self._processMap["setConfigValue"] = Processor.process_setConfigValue
- self._processMap["getConfig"] = Processor.process_getConfig
- self._processMap["getPluginConfig"] = Processor.process_getPluginConfig
- self._processMap["pauseServer"] = Processor.process_pauseServer
- self._processMap["unpauseServer"] = Processor.process_unpauseServer
- self._processMap["togglePause"] = Processor.process_togglePause
- self._processMap["statusServer"] = Processor.process_statusServer
- self._processMap["freeSpace"] = Processor.process_freeSpace
- self._processMap["getServerVersion"] = Processor.process_getServerVersion
- self._processMap["kill"] = Processor.process_kill
- self._processMap["restart"] = Processor.process_restart
- self._processMap["getLog"] = Processor.process_getLog
- self._processMap["isTimeDownload"] = Processor.process_isTimeDownload
- self._processMap["isTimeReconnect"] = Processor.process_isTimeReconnect
- self._processMap["toggleReconnect"] = Processor.process_toggleReconnect
- self._processMap["generatePackages"] = Processor.process_generatePackages
- self._processMap["checkURLs"] = Processor.process_checkURLs
- self._processMap["parseURLs"] = Processor.process_parseURLs
- self._processMap["checkOnlineStatus"] = Processor.process_checkOnlineStatus
- self._processMap["checkOnlineStatusContainer"] = Processor.process_checkOnlineStatusContainer
- self._processMap["pollResults"] = Processor.process_pollResults
- self._processMap["statusDownloads"] = Processor.process_statusDownloads
- self._processMap["getPackageData"] = Processor.process_getPackageData
- self._processMap["getPackageInfo"] = Processor.process_getPackageInfo
- self._processMap["getFileData"] = Processor.process_getFileData
- self._processMap["getQueue"] = Processor.process_getQueue
- self._processMap["getCollector"] = Processor.process_getCollector
- self._processMap["getQueueData"] = Processor.process_getQueueData
- self._processMap["getCollectorData"] = Processor.process_getCollectorData
- self._processMap["getPackageOrder"] = Processor.process_getPackageOrder
- self._processMap["getFileOrder"] = Processor.process_getFileOrder
- self._processMap["generateAndAddPackages"] = Processor.process_generateAndAddPackages
- self._processMap["addPackage"] = Processor.process_addPackage
- self._processMap["addFiles"] = Processor.process_addFiles
- self._processMap["uploadContainer"] = Processor.process_uploadContainer
- self._processMap["deleteFiles"] = Processor.process_deleteFiles
- self._processMap["deletePackages"] = Processor.process_deletePackages
- self._processMap["pushToQueue"] = Processor.process_pushToQueue
- self._processMap["pullFromQueue"] = Processor.process_pullFromQueue
- self._processMap["restartPackage"] = Processor.process_restartPackage
- self._processMap["restartFile"] = Processor.process_restartFile
- self._processMap["recheckPackage"] = Processor.process_recheckPackage
- self._processMap["stopAllDownloads"] = Processor.process_stopAllDownloads
- self._processMap["stopDownloads"] = Processor.process_stopDownloads
- self._processMap["setPackageName"] = Processor.process_setPackageName
- self._processMap["movePackage"] = Processor.process_movePackage
- self._processMap["moveFiles"] = Processor.process_moveFiles
- self._processMap["orderPackage"] = Processor.process_orderPackage
- self._processMap["orderFile"] = Processor.process_orderFile
- self._processMap["setPackageData"] = Processor.process_setPackageData
- self._processMap["deleteFinished"] = Processor.process_deleteFinished
- self._processMap["restartFailed"] = Processor.process_restartFailed
- self._processMap["getEvents"] = Processor.process_getEvents
- self._processMap["getAccounts"] = Processor.process_getAccounts
- self._processMap["getAccountTypes"] = Processor.process_getAccountTypes
- self._processMap["updateAccount"] = Processor.process_updateAccount
- self._processMap["removeAccount"] = Processor.process_removeAccount
- self._processMap["login"] = Processor.process_login
- self._processMap["getUserData"] = Processor.process_getUserData
- self._processMap["getAllUserData"] = Processor.process_getAllUserData
- self._processMap["getServices"] = Processor.process_getServices
- self._processMap["hasService"] = Processor.process_hasService
- self._processMap["call"] = Processor.process_call
- self._processMap["getAllInfo"] = Processor.process_getAllInfo
- self._processMap["getInfoByPlugin"] = Processor.process_getInfoByPlugin
- self._processMap["isCaptchaWaiting"] = Processor.process_isCaptchaWaiting
- self._processMap["getCaptchaTask"] = Processor.process_getCaptchaTask
- self._processMap["getCaptchaTaskStatus"] = Processor.process_getCaptchaTaskStatus
- self._processMap["setCaptchaResult"] = Processor.process_setCaptchaResult
-
- def process(self, iprot, oprot):
- (name, type, seqid) = iprot.readMessageBegin()
- if name not in self._processMap:
- iprot.skip(TType.STRUCT)
- iprot.readMessageEnd()
- x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
- oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
- else:
- self._processMap[name](self, seqid, iprot, oprot)
- return True
-
- def process_getConfigValue(self, seqid, iprot, oprot):
- args = getConfigValue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getConfigValue_result()
- result.success = self._handler.getConfigValue(args.category, args.option, args.section)
- oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setConfigValue(self, seqid, iprot, oprot):
- args = setConfigValue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setConfigValue_result()
- self._handler.setConfigValue(args.category, args.option, args.value, args.section)
- oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getConfig(self, seqid, iprot, oprot):
- args = getConfig_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getConfig_result()
- result.success = self._handler.getConfig()
- oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPluginConfig(self, seqid, iprot, oprot):
- args = getPluginConfig_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPluginConfig_result()
- result.success = self._handler.getPluginConfig()
- oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pauseServer(self, seqid, iprot, oprot):
- args = pauseServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pauseServer_result()
- self._handler.pauseServer()
- oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_unpauseServer(self, seqid, iprot, oprot):
- args = unpauseServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = unpauseServer_result()
- self._handler.unpauseServer()
- oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_togglePause(self, seqid, iprot, oprot):
- args = togglePause_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = togglePause_result()
- result.success = self._handler.togglePause()
- oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_statusServer(self, seqid, iprot, oprot):
- args = statusServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = statusServer_result()
- result.success = self._handler.statusServer()
- oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_freeSpace(self, seqid, iprot, oprot):
- args = freeSpace_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = freeSpace_result()
- result.success = self._handler.freeSpace()
- oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getServerVersion(self, seqid, iprot, oprot):
- args = getServerVersion_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getServerVersion_result()
- result.success = self._handler.getServerVersion()
- oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_kill(self, seqid, iprot, oprot):
- args = kill_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = kill_result()
- self._handler.kill()
- oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restart(self, seqid, iprot, oprot):
- args = restart_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restart_result()
- self._handler.restart()
- oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getLog(self, seqid, iprot, oprot):
- args = getLog_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getLog_result()
- result.success = self._handler.getLog(args.offset)
- oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isTimeDownload(self, seqid, iprot, oprot):
- args = isTimeDownload_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isTimeDownload_result()
- result.success = self._handler.isTimeDownload()
- oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isTimeReconnect(self, seqid, iprot, oprot):
- args = isTimeReconnect_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isTimeReconnect_result()
- result.success = self._handler.isTimeReconnect()
- oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_toggleReconnect(self, seqid, iprot, oprot):
- args = toggleReconnect_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = toggleReconnect_result()
- result.success = self._handler.toggleReconnect()
- oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_generatePackages(self, seqid, iprot, oprot):
- args = generatePackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = generatePackages_result()
- result.success = self._handler.generatePackages(args.links)
- oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkURLs(self, seqid, iprot, oprot):
- args = checkURLs_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkURLs_result()
- result.success = self._handler.checkURLs(args.urls)
- oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_parseURLs(self, seqid, iprot, oprot):
- args = parseURLs_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = parseURLs_result()
- result.success = self._handler.parseURLs(args.html, args.url)
- oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkOnlineStatus(self, seqid, iprot, oprot):
- args = checkOnlineStatus_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkOnlineStatus_result()
- result.success = self._handler.checkOnlineStatus(args.urls)
- oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkOnlineStatusContainer(self, seqid, iprot, oprot):
- args = checkOnlineStatusContainer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkOnlineStatusContainer_result()
- result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data)
- oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pollResults(self, seqid, iprot, oprot):
- args = pollResults_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pollResults_result()
- result.success = self._handler.pollResults(args.rid)
- oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_statusDownloads(self, seqid, iprot, oprot):
- args = statusDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = statusDownloads_result()
- result.success = self._handler.statusDownloads()
- oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageData(self, seqid, iprot, oprot):
- args = getPackageData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageData_result()
- try:
- result.success = self._handler.getPackageData(args.pid)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageInfo(self, seqid, iprot, oprot):
- args = getPackageInfo_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageInfo_result()
- try:
- result.success = self._handler.getPackageInfo(args.pid)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getFileData(self, seqid, iprot, oprot):
- args = getFileData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getFileData_result()
- try:
- result.success = self._handler.getFileData(args.fid)
- except FileDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getQueue(self, seqid, iprot, oprot):
- args = getQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getQueue_result()
- result.success = self._handler.getQueue()
- oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCollector(self, seqid, iprot, oprot):
- args = getCollector_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCollector_result()
- result.success = self._handler.getCollector()
- oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getQueueData(self, seqid, iprot, oprot):
- args = getQueueData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getQueueData_result()
- result.success = self._handler.getQueueData()
- oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCollectorData(self, seqid, iprot, oprot):
- args = getCollectorData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCollectorData_result()
- result.success = self._handler.getCollectorData()
- oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageOrder(self, seqid, iprot, oprot):
- args = getPackageOrder_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageOrder_result()
- result.success = self._handler.getPackageOrder(args.destination)
- oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getFileOrder(self, seqid, iprot, oprot):
- args = getFileOrder_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getFileOrder_result()
- result.success = self._handler.getFileOrder(args.pid)
- oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_generateAndAddPackages(self, seqid, iprot, oprot):
- args = generateAndAddPackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = generateAndAddPackages_result()
- result.success = self._handler.generateAndAddPackages(args.links, args.dest)
- oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_addPackage(self, seqid, iprot, oprot):
- args = addPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = addPackage_result()
- result.success = self._handler.addPackage(args.name, args.links, args.dest)
- oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_addFiles(self, seqid, iprot, oprot):
- args = addFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = addFiles_result()
- self._handler.addFiles(args.pid, args.links)
- oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_uploadContainer(self, seqid, iprot, oprot):
- args = uploadContainer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = uploadContainer_result()
- self._handler.uploadContainer(args.filename, args.data)
- oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deleteFiles(self, seqid, iprot, oprot):
- args = deleteFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deleteFiles_result()
- self._handler.deleteFiles(args.fids)
- oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deletePackages(self, seqid, iprot, oprot):
- args = deletePackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deletePackages_result()
- self._handler.deletePackages(args.pids)
- oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pushToQueue(self, seqid, iprot, oprot):
- args = pushToQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pushToQueue_result()
- self._handler.pushToQueue(args.pid)
- oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pullFromQueue(self, seqid, iprot, oprot):
- args = pullFromQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pullFromQueue_result()
- self._handler.pullFromQueue(args.pid)
- oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartPackage(self, seqid, iprot, oprot):
- args = restartPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartPackage_result()
- self._handler.restartPackage(args.pid)
- oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartFile(self, seqid, iprot, oprot):
- args = restartFile_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartFile_result()
- self._handler.restartFile(args.fid)
- oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_recheckPackage(self, seqid, iprot, oprot):
- args = recheckPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = recheckPackage_result()
- self._handler.recheckPackage(args.pid)
- oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_stopAllDownloads(self, seqid, iprot, oprot):
- args = stopAllDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = stopAllDownloads_result()
- self._handler.stopAllDownloads()
- oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_stopDownloads(self, seqid, iprot, oprot):
- args = stopDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = stopDownloads_result()
- self._handler.stopDownloads(args.fids)
- oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setPackageName(self, seqid, iprot, oprot):
- args = setPackageName_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setPackageName_result()
- self._handler.setPackageName(args.pid, args.name)
- oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_movePackage(self, seqid, iprot, oprot):
- args = movePackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = movePackage_result()
- self._handler.movePackage(args.destination, args.pid)
- oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_moveFiles(self, seqid, iprot, oprot):
- args = moveFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = moveFiles_result()
- self._handler.moveFiles(args.fids, args.pid)
- oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_orderPackage(self, seqid, iprot, oprot):
- args = orderPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = orderPackage_result()
- self._handler.orderPackage(args.pid, args.position)
- oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_orderFile(self, seqid, iprot, oprot):
- args = orderFile_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = orderFile_result()
- self._handler.orderFile(args.fid, args.position)
- oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setPackageData(self, seqid, iprot, oprot):
- args = setPackageData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setPackageData_result()
- try:
- self._handler.setPackageData(args.pid, args.data)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deleteFinished(self, seqid, iprot, oprot):
- args = deleteFinished_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deleteFinished_result()
- result.success = self._handler.deleteFinished()
- oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartFailed(self, seqid, iprot, oprot):
- args = restartFailed_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartFailed_result()
- self._handler.restartFailed()
- oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getEvents(self, seqid, iprot, oprot):
- args = getEvents_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getEvents_result()
- result.success = self._handler.getEvents(args.uuid)
- oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAccounts(self, seqid, iprot, oprot):
- args = getAccounts_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAccounts_result()
- result.success = self._handler.getAccounts(args.refresh)
- oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAccountTypes(self, seqid, iprot, oprot):
- args = getAccountTypes_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAccountTypes_result()
- result.success = self._handler.getAccountTypes()
- oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_updateAccount(self, seqid, iprot, oprot):
- args = updateAccount_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = updateAccount_result()
- self._handler.updateAccount(args.plugin, args.account, args.password, args.options)
- oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_removeAccount(self, seqid, iprot, oprot):
- args = removeAccount_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = removeAccount_result()
- self._handler.removeAccount(args.plugin, args.account)
- oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_login(self, seqid, iprot, oprot):
- args = login_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = login_result()
- result.success = self._handler.login(args.username, args.password)
- oprot.writeMessageBegin("login", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getUserData(self, seqid, iprot, oprot):
- args = getUserData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getUserData_result()
- result.success = self._handler.getUserData(args.username, args.password)
- oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAllUserData(self, seqid, iprot, oprot):
- args = getAllUserData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAllUserData_result()
- result.success = self._handler.getAllUserData()
- oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getServices(self, seqid, iprot, oprot):
- args = getServices_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getServices_result()
- result.success = self._handler.getServices()
- oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_hasService(self, seqid, iprot, oprot):
- args = hasService_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = hasService_result()
- result.success = self._handler.hasService(args.plugin, args.func)
- oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_call(self, seqid, iprot, oprot):
- args = call_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = call_result()
- try:
- result.success = self._handler.call(args.info)
- except ServiceDoesNotExists, ex:
- result.ex = ex
- except ServiceException, e:
- result.e = e
- oprot.writeMessageBegin("call", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAllInfo(self, seqid, iprot, oprot):
- args = getAllInfo_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAllInfo_result()
- result.success = self._handler.getAllInfo()
- oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getInfoByPlugin(self, seqid, iprot, oprot):
- args = getInfoByPlugin_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getInfoByPlugin_result()
- result.success = self._handler.getInfoByPlugin(args.plugin)
- oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isCaptchaWaiting(self, seqid, iprot, oprot):
- args = isCaptchaWaiting_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isCaptchaWaiting_result()
- result.success = self._handler.isCaptchaWaiting()
- oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCaptchaTask(self, seqid, iprot, oprot):
- args = getCaptchaTask_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCaptchaTask_result()
- result.success = self._handler.getCaptchaTask(args.exclusive)
- oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCaptchaTaskStatus(self, seqid, iprot, oprot):
- args = getCaptchaTaskStatus_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCaptchaTaskStatus_result()
- result.success = self._handler.getCaptchaTaskStatus(args.tid)
- oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setCaptchaResult(self, seqid, iprot, oprot):
- args = setCaptchaResult_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setCaptchaResult_result()
- self._handler.setCaptchaResult(args.tid, args.result)
- oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
-
-# HELPER FUNCTIONS AND STRUCTURES
-
-class getConfigValue_args(TBase):
- """
- Attributes:
- - category
- - option
- - section
- """
-
- __slots__ = [
- 'category',
- 'option',
- 'section',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
- (2, TType.STRING, 'option', None, None, ), # 2
- (3, TType.STRING, 'section', None, None, ), # 3
- )
-
- def __init__(self, category=None, option=None, section=None,):
- self.category = category
- self.option = option
- self.section = section
-
-
-class getConfigValue_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class setConfigValue_args(TBase):
- """
- Attributes:
- - category
- - option
- - value
- - section
- """
-
- __slots__ = [
- 'category',
- 'option',
- 'value',
- 'section',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
- (2, TType.STRING, 'option', None, None, ), # 2
- (3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'section', None, None, ), # 4
- )
-
- def __init__(self, category=None, option=None, value=None, section=None,):
- self.category = category
- self.option = option
- self.value = value
- self.section = section
-
-
-class setConfigValue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getConfig_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getConfig_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPluginConfig_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getPluginConfig_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class pauseServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pauseServer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class unpauseServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class unpauseServer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class togglePause_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class togglePause_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class statusServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class statusServer_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class freeSpace_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class freeSpace_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.I64, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getServerVersion_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getServerVersion_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class kill_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class kill_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restart_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restart_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getLog_args(TBase):
- """
- Attributes:
- - offset
- """
-
- __slots__ = [
- 'offset',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'offset', None, None, ), # 1
- )
-
- def __init__(self, offset=None,):
- self.offset = offset
-
-
-class getLog_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isTimeDownload_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isTimeDownload_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isTimeReconnect_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isTimeReconnect_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class toggleReconnect_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class toggleReconnect_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class generatePackages_args(TBase):
- """
- Attributes:
- - links
- """
-
- __slots__ = [
- 'links',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, links=None,):
- self.links = links
-
-
-class generatePackages_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkURLs_args(TBase):
- """
- Attributes:
- - urls
- """
-
- __slots__ = [
- 'urls',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, urls=None,):
- self.urls = urls
-
-
-class checkURLs_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class parseURLs_args(TBase):
- """
- Attributes:
- - html
- - url
- """
-
- __slots__ = [
- 'html',
- 'url',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'html', None, None, ), # 1
- (2, TType.STRING, 'url', None, None, ), # 2
- )
-
- def __init__(self, html=None, url=None,):
- self.html = html
- self.url = url
-
-
-class parseURLs_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkOnlineStatus_args(TBase):
- """
- Attributes:
- - urls
- """
-
- __slots__ = [
- 'urls',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, urls=None,):
- self.urls = urls
-
-
-class checkOnlineStatus_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkOnlineStatusContainer_args(TBase):
- """
- Attributes:
- - urls
- - filename
- - data
- """
-
- __slots__ = [
- 'urls',
- 'filename',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- (2, TType.STRING, 'filename', None, None, ), # 2
- (3, TType.STRING, 'data', None, None, ), # 3
- )
-
- def __init__(self, urls=None, filename=None, data=None,):
- self.urls = urls
- self.filename = filename
- self.data = data
-
-
-class checkOnlineStatusContainer_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class pollResults_args(TBase):
- """
- Attributes:
- - rid
- """
-
- __slots__ = [
- 'rid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'rid', None, None, ), # 1
- )
-
- def __init__(self, rid=None,):
- self.rid = rid
-
-
-class pollResults_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class statusDownloads_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class statusDownloads_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(DownloadInfo, DownloadInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPackageData_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getPackageData_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getPackageInfo_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getPackageInfo_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getFileData_args(TBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
-
-class getFileData_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getQueue_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getQueue_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCollector_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getCollector_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getQueueData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getQueueData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCollectorData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getCollectorData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPackageOrder_args(TBase):
- """
- Attributes:
- - destination
- """
-
- __slots__ = [
- 'destination',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
- )
-
- def __init__(self, destination=None,):
- self.destination = destination
-
-
-class getPackageOrder_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getFileOrder_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getFileOrder_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class generateAndAddPackages_args(TBase):
- """
- Attributes:
- - links
- - dest
- """
-
- __slots__ = [
- 'links',
- 'dest',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
- (2, TType.I32, 'dest', None, None, ), # 2
- )
-
- def __init__(self, links=None, dest=None,):
- self.links = links
- self.dest = dest
-
-
-class generateAndAddPackages_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class addPackage_args(TBase):
- """
- Attributes:
- - name
- - links
- - dest
- """
-
- __slots__ = [
- 'name',
- 'links',
- 'dest',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
- (3, TType.I32, 'dest', None, None, ), # 3
- )
-
- def __init__(self, name=None, links=None, dest=None,):
- self.name = name
- self.links = links
- self.dest = dest
-
-
-class addPackage_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.I32, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class addFiles_args(TBase):
- """
- Attributes:
- - pid
- - links
- """
-
- __slots__ = [
- 'pid',
- 'links',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
- )
-
- def __init__(self, pid=None, links=None,):
- self.pid = pid
- self.links = links
-
-
-class addFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class uploadContainer_args(TBase):
- """
- Attributes:
- - filename
- - data
- """
-
- __slots__ = [
- 'filename',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'filename', None, None, ), # 1
- (2, TType.STRING, 'data', None, None, ), # 2
- )
-
- def __init__(self, filename=None, data=None,):
- self.filename = filename
- self.data = data
-
-
-class uploadContainer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deleteFiles_args(TBase):
- """
- Attributes:
- - fids
- """
-
- __slots__ = [
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, fids=None,):
- self.fids = fids
-
-
-class deleteFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deletePackages_args(TBase):
- """
- Attributes:
- - pids
- """
-
- __slots__ = [
- 'pids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'pids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, pids=None,):
- self.pids = pids
-
-
-class deletePackages_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pushToQueue_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class pushToQueue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pullFromQueue_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class pullFromQueue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartPackage_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class restartPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartFile_args(TBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
-
-class restartFile_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class recheckPackage_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class recheckPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopAllDownloads_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopAllDownloads_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopDownloads_args(TBase):
- """
- Attributes:
- - fids
- """
-
- __slots__ = [
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, fids=None,):
- self.fids = fids
-
-
-class stopDownloads_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class setPackageName_args(TBase):
- """
- Attributes:
- - pid
- - name
- """
-
- __slots__ = [
- 'pid',
- 'name',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- )
-
- def __init__(self, pid=None, name=None,):
- self.pid = pid
- self.name = name
-
-
-class setPackageName_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class movePackage_args(TBase):
- """
- Attributes:
- - destination
- - pid
- """
-
- __slots__ = [
- 'destination',
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
- (2, TType.I32, 'pid', None, None, ), # 2
- )
-
- def __init__(self, destination=None, pid=None,):
- self.destination = destination
- self.pid = pid
-
-
-class movePackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class moveFiles_args(TBase):
- """
- Attributes:
- - fids
- - pid
- """
-
- __slots__ = [
- 'fids',
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- (2, TType.I32, 'pid', None, None, ), # 2
- )
-
- def __init__(self, fids=None, pid=None,):
- self.fids = fids
- self.pid = pid
-
-
-class moveFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class orderPackage_args(TBase):
- """
- Attributes:
- - pid
- - position
- """
-
- __slots__ = [
- 'pid',
- 'position',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.I16, 'position', None, None, ), # 2
- )
-
- def __init__(self, pid=None, position=None,):
- self.pid = pid
- self.position = position
-
-
-class orderPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class orderFile_args(TBase):
- """
- Attributes:
- - fid
- - position
- """
-
- __slots__ = [
- 'fid',
- 'position',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.I16, 'position', None, None, ), # 2
- )
-
- def __init__(self, fid=None, position=None,):
- self.fid = fid
- self.position = position
-
-
-class orderFile_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class setPackageData_args(TBase):
- """
- Attributes:
- - pid
- - data
- """
-
- __slots__ = [
- 'pid',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.MAP, 'data', (TType.STRING,None,TType.STRING,None), None, ), # 2
- )
-
- def __init__(self, pid=None, data=None,):
- self.pid = pid
- self.data = data
-
-
-class setPackageData_result(TBase):
- """
- Attributes:
- - e
- """
-
- __slots__ = [
- 'e',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, e=None,):
- self.e = e
-
-
-class deleteFinished_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deleteFinished_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class restartFailed_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartFailed_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getEvents_args(TBase):
- """
- Attributes:
- - uuid
- """
-
- __slots__ = [
- 'uuid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'uuid', None, None, ), # 1
- )
-
- def __init__(self, uuid=None,):
- self.uuid = uuid
-
-
-class getEvents_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(EventInfo, EventInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAccounts_args(TBase):
- """
- Attributes:
- - refresh
- """
-
- __slots__ = [
- 'refresh',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'refresh', None, None, ), # 1
- )
-
- def __init__(self, refresh=None,):
- self.refresh = refresh
-
-
-class getAccounts_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(AccountInfo, AccountInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAccountTypes_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAccountTypes_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class updateAccount_args(TBase):
- """
- Attributes:
- - plugin
- - account
- - password
- - options
- """
-
- __slots__ = [
- 'plugin',
- 'account',
- 'password',
- 'options',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'account', None, None, ), # 2
- (3, TType.STRING, 'password', None, None, ), # 3
- (4, TType.MAP, 'options', (TType.STRING,None,TType.STRING,None), None, ), # 4
- )
-
- def __init__(self, plugin=None, account=None, password=None, options=None,):
- self.plugin = plugin
- self.account = account
- self.password = password
- self.options = options
-
-
-class updateAccount_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class removeAccount_args(TBase):
- """
- Attributes:
- - plugin
- - account
- """
-
- __slots__ = [
- 'plugin',
- 'account',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'account', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, account=None,):
- self.plugin = plugin
- self.account = account
-
-
-class removeAccount_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class login_args(TBase):
- """
- Attributes:
- - username
- - password
- """
-
- __slots__ = [
- 'username',
- 'password',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'username', None, None, ), # 1
- (2, TType.STRING, 'password', None, None, ), # 2
- )
-
- def __init__(self, username=None, password=None,):
- self.username = username
- self.password = password
-
-
-class login_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getUserData_args(TBase):
- """
- Attributes:
- - username
- - password
- """
-
- __slots__ = [
- 'username',
- 'password',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'username', None, None, ), # 1
- (2, TType.STRING, 'password', None, None, ), # 2
- )
-
- def __init__(self, username=None, password=None,):
- self.username = username
- self.password = password
-
-
-class getUserData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAllUserData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAllUserData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(UserData, UserData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getServices_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getServices_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class hasService_args(TBase):
- """
- Attributes:
- - plugin
- - func
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, func=None,):
- self.plugin = plugin
- self.func = func
-
-
-class hasService_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class call_args(TBase):
- """
- Attributes:
- - info
- """
-
- __slots__ = [
- 'info',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None, ), # 1
- )
-
- def __init__(self, info=None,):
- self.info = info
-
-
-class call_result(TBase):
- """
- Attributes:
- - success
- - ex
- - e
- """
-
- __slots__ = [
- 'success',
- 'ex',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None, ), # 1
- (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None, ), # 2
- )
-
- def __init__(self, success=None, ex=None, e=None,):
- self.success = success
- self.ex = ex
- self.e = e
-
-
-class getAllInfo_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAllInfo_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getInfoByPlugin_args(TBase):
- """
- Attributes:
- - plugin
- """
-
- __slots__ = [
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- )
-
- def __init__(self, plugin=None,):
- self.plugin = plugin
-
-
-class getInfoByPlugin_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isCaptchaWaiting_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isCaptchaWaiting_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCaptchaTask_args(TBase):
- """
- Attributes:
- - exclusive
- """
-
- __slots__ = [
- 'exclusive',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'exclusive', None, None, ), # 1
- )
-
- def __init__(self, exclusive=None,):
- self.exclusive = exclusive
-
-
-class getCaptchaTask_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCaptchaTaskStatus_args(TBase):
- """
- Attributes:
- - tid
- """
-
- __slots__ = [
- 'tid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
- )
-
- def __init__(self, tid=None,):
- self.tid = tid
-
-
-class getCaptchaTaskStatus_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class setCaptchaResult_args(TBase):
- """
- Attributes:
- - tid
- - result
- """
-
- __slots__ = [
- 'tid',
- 'result',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
- (2, TType.STRING, 'result', None, None, ), # 2
- )
-
- def __init__(self, tid=None, result=None,):
- self.tid = tid
- self.result = result
-
-
-class setCaptchaResult_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
diff --git a/module/remote/thriftbackend/thriftgen/pyload/__init__.py b/module/remote/thriftbackend/thriftgen/pyload/__init__.py
deleted file mode 100644
index ce7f52598..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['ttypes', 'constants', 'Pyload']
diff --git a/module/remote/thriftbackend/thriftgen/pyload/constants.py b/module/remote/thriftbackend/thriftgen/pyload/constants.py
deleted file mode 100644
index f8960dc63..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/constants.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-from ttypes import *
-
diff --git a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
deleted file mode 100644
index 1299b515d..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
+++ /dev/null
@@ -1,835 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-
-from thrift.protocol.TBase import TBase, TExceptionBase
-
-
-class DownloadStatus(TBase):
- Finished = 0
- Offline = 1
- Online = 2
- Queued = 3
- Skipped = 4
- Waiting = 5
- TempOffline = 6
- Starting = 7
- Failed = 8
- Aborted = 9
- Decrypting = 10
- Custom = 11
- Downloading = 12
- Processing = 13
- Unknown = 14
-
- _VALUES_TO_NAMES = {
- 0: "Finished",
- 1: "Offline",
- 2: "Online",
- 3: "Queued",
- 4: "Skipped",
- 5: "Waiting",
- 6: "TempOffline",
- 7: "Starting",
- 8: "Failed",
- 9: "Aborted",
- 10: "Decrypting",
- 11: "Custom",
- 12: "Downloading",
- 13: "Processing",
- 14: "Unknown",
- }
-
- _NAMES_TO_VALUES = {
- "Finished": 0,
- "Offline": 1,
- "Online": 2,
- "Queued": 3,
- "Skipped": 4,
- "Waiting": 5,
- "TempOffline": 6,
- "Starting": 7,
- "Failed": 8,
- "Aborted": 9,
- "Decrypting": 10,
- "Custom": 11,
- "Downloading": 12,
- "Processing": 13,
- "Unknown": 14,
- }
-
-class Destination(TBase):
- Collector = 0
- Queue = 1
-
- _VALUES_TO_NAMES = {
- 0: "Collector",
- 1: "Queue",
- }
-
- _NAMES_TO_VALUES = {
- "Collector": 0,
- "Queue": 1,
- }
-
-class ElementType(TBase):
- Package = 0
- File = 1
-
- _VALUES_TO_NAMES = {
- 0: "Package",
- 1: "File",
- }
-
- _NAMES_TO_VALUES = {
- "Package": 0,
- "File": 1,
- }
-
-class Input(TBase):
- NONE = 0
- TEXT = 1
- TEXTBOX = 2
- PASSWORD = 3
- BOOL = 4
- CLICK = 5
- CHOICE = 6
- MULTIPLE = 7
- LIST = 8
- TABLE = 9
-
- _VALUES_TO_NAMES = {
- 0: "NONE",
- 1: "TEXT",
- 2: "TEXTBOX",
- 3: "PASSWORD",
- 4: "BOOL",
- 5: "CLICK",
- 6: "CHOICE",
- 7: "MULTIPLE",
- 8: "LIST",
- 9: "TABLE",
- }
-
- _NAMES_TO_VALUES = {
- "NONE": 0,
- "TEXT": 1,
- "TEXTBOX": 2,
- "PASSWORD": 3,
- "BOOL": 4,
- "CLICK": 5,
- "CHOICE": 6,
- "MULTIPLE": 7,
- "LIST": 8,
- "TABLE": 9,
- }
-
-class Output(TBase):
- CAPTCHA = 1
- QUESTION = 2
- NOTIFICATION = 4
-
- _VALUES_TO_NAMES = {
- 1: "CAPTCHA",
- 2: "QUESTION",
- 4: "NOTIFICATION",
- }
-
- _NAMES_TO_VALUES = {
- "CAPTCHA": 1,
- "QUESTION": 2,
- "NOTIFICATION": 4,
- }
-
-
-class DownloadInfo(TBase):
- """
- Attributes:
- - fid
- - name
- - speed
- - eta
- - format_eta
- - bleft
- - size
- - format_size
- - percent
- - status
- - statusmsg
- - format_wait
- - wait_until
- - packageID
- - packageName
- - plugin
- """
-
- __slots__ = [
- 'fid',
- 'name',
- 'speed',
- 'eta',
- 'format_eta',
- 'bleft',
- 'size',
- 'format_size',
- 'percent',
- 'status',
- 'statusmsg',
- 'format_wait',
- 'wait_until',
- 'packageID',
- 'packageName',
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- (3, TType.I64, 'speed', None, None, ), # 3
- (4, TType.I32, 'eta', None, None, ), # 4
- (5, TType.STRING, 'format_eta', None, None, ), # 5
- (6, TType.I64, 'bleft', None, None, ), # 6
- (7, TType.I64, 'size', None, None, ), # 7
- (8, TType.STRING, 'format_size', None, None, ), # 8
- (9, TType.BYTE, 'percent', None, None, ), # 9
- (10, TType.I32, 'status', None, None, ), # 10
- (11, TType.STRING, 'statusmsg', None, None, ), # 11
- (12, TType.STRING, 'format_wait', None, None, ), # 12
- (13, TType.I64, 'wait_until', None, None, ), # 13
- (14, TType.I32, 'packageID', None, None, ), # 14
- (15, TType.STRING, 'packageName', None, None, ), # 15
- (16, TType.STRING, 'plugin', None, None, ), # 16
- )
-
- def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,):
- self.fid = fid
- self.name = name
- self.speed = speed
- self.eta = eta
- self.format_eta = format_eta
- self.bleft = bleft
- self.size = size
- self.format_size = format_size
- self.percent = percent
- self.status = status
- self.statusmsg = statusmsg
- self.format_wait = format_wait
- self.wait_until = wait_until
- self.packageID = packageID
- self.packageName = packageName
- self.plugin = plugin
-
-
-class ServerStatus(TBase):
- """
- Attributes:
- - pause
- - active
- - queue
- - total
- - speed
- - download
- - reconnect
- """
-
- __slots__ = [
- 'pause',
- 'active',
- 'queue',
- 'total',
- 'speed',
- 'download',
- 'reconnect',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'pause', None, None, ), # 1
- (2, TType.I16, 'active', None, None, ), # 2
- (3, TType.I16, 'queue', None, None, ), # 3
- (4, TType.I16, 'total', None, None, ), # 4
- (5, TType.I64, 'speed', None, None, ), # 5
- (6, TType.BOOL, 'download', None, None, ), # 6
- (7, TType.BOOL, 'reconnect', None, None, ), # 7
- )
-
- def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,):
- self.pause = pause
- self.active = active
- self.queue = queue
- self.total = total
- self.speed = speed
- self.download = download
- self.reconnect = reconnect
-
-
-class ConfigItem(TBase):
- """
- Attributes:
- - name
- - description
- - value
- - type
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'value',
- 'type',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'type', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, value=None, type=None,):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
-
-
-class ConfigSection(TBase):
- """
- Attributes:
- - name
- - description
- - items
- - outline
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'items',
- 'outline',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 3
- (4, TType.STRING, 'outline', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, items=None, outline=None,):
- self.name = name
- self.description = description
- self.items = items
- self.outline = outline
-
-
-class FileData(TBase):
- """
- Attributes:
- - fid
- - url
- - name
- - plugin
- - size
- - format_size
- - status
- - statusmsg
- - packageID
- - error
- - order
- """
-
- __slots__ = [
- 'fid',
- 'url',
- 'name',
- 'plugin',
- 'size',
- 'format_size',
- 'status',
- 'statusmsg',
- 'packageID',
- 'error',
- 'order',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.STRING, 'url', None, None, ), # 2
- (3, TType.STRING, 'name', None, None, ), # 3
- (4, TType.STRING, 'plugin', None, None, ), # 4
- (5, TType.I64, 'size', None, None, ), # 5
- (6, TType.STRING, 'format_size', None, None, ), # 6
- (7, TType.I32, 'status', None, None, ), # 7
- (8, TType.STRING, 'statusmsg', None, None, ), # 8
- (9, TType.I32, 'packageID', None, None, ), # 9
- (10, TType.STRING, 'error', None, None, ), # 10
- (11, TType.I16, 'order', None, None, ), # 11
- )
-
- def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,):
- self.fid = fid
- self.url = url
- self.name = name
- self.plugin = plugin
- self.size = size
- self.format_size = format_size
- self.status = status
- self.statusmsg = statusmsg
- self.packageID = packageID
- self.error = error
- self.order = order
-
-
-class PackageData(TBase):
- """
- Attributes:
- - pid
- - name
- - folder
- - site
- - password
- - dest
- - order
- - linksdone
- - sizedone
- - sizetotal
- - linkstotal
- - links
- - fids
- """
-
- __slots__ = [
- 'pid',
- 'name',
- 'folder',
- 'site',
- 'password',
- 'dest',
- 'order',
- 'linksdone',
- 'sizedone',
- 'sizetotal',
- 'linkstotal',
- 'links',
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- (3, TType.STRING, 'folder', None, None, ), # 3
- (4, TType.STRING, 'site', None, None, ), # 4
- (5, TType.STRING, 'password', None, None, ), # 5
- (6, TType.I32, 'dest', None, None, ), # 6
- (7, TType.I16, 'order', None, None, ), # 7
- (8, TType.I16, 'linksdone', None, None, ), # 8
- (9, TType.I64, 'sizedone', None, None, ), # 9
- (10, TType.I64, 'sizetotal', None, None, ), # 10
- (11, TType.I16, 'linkstotal', None, None, ), # 11
- (12, TType.LIST, 'links', (TType.STRUCT,(FileData, FileData.thrift_spec)), None, ), # 12
- (13, TType.LIST, 'fids', (TType.I32,None), None, ), # 13
- )
-
- def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,):
- self.pid = pid
- self.name = name
- self.folder = folder
- self.site = site
- self.password = password
- self.dest = dest
- self.order = order
- self.linksdone = linksdone
- self.sizedone = sizedone
- self.sizetotal = sizetotal
- self.linkstotal = linkstotal
- self.links = links
- self.fids = fids
-
-
-class InteractionTask(TBase):
- """
- Attributes:
- - iid
- - input
- - structure
- - preset
- - output
- - data
- - title
- - description
- - plugin
- """
-
- __slots__ = [
- 'iid',
- 'input',
- 'structure',
- 'preset',
- 'output',
- 'data',
- 'title',
- 'description',
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'iid', None, None, ), # 1
- (2, TType.I32, 'input', None, None, ), # 2
- (3, TType.LIST, 'structure', (TType.STRING,None), None, ), # 3
- (4, TType.LIST, 'preset', (TType.STRING,None), None, ), # 4
- (5, TType.I32, 'output', None, None, ), # 5
- (6, TType.LIST, 'data', (TType.STRING,None), None, ), # 6
- (7, TType.STRING, 'title', None, None, ), # 7
- (8, TType.STRING, 'description', None, None, ), # 8
- (9, TType.STRING, 'plugin', None, None, ), # 9
- )
-
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,):
- self.iid = iid
- self.input = input
- self.structure = structure
- self.preset = preset
- self.output = output
- self.data = data
- self.title = title
- self.description = description
- self.plugin = plugin
-
-
-class CaptchaTask(TBase):
- """
- Attributes:
- - tid
- - data
- - type
- - resultType
- """
-
- __slots__ = [
- 'tid',
- 'data',
- 'type',
- 'resultType',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I16, 'tid', None, None, ), # 1
- (2, TType.STRING, 'data', None, None, ), # 2
- (3, TType.STRING, 'type', None, None, ), # 3
- (4, TType.STRING, 'resultType', None, None, ), # 4
- )
-
- def __init__(self, tid=None, data=None, type=None, resultType=None,):
- self.tid = tid
- self.data = data
- self.type = type
- self.resultType = resultType
-
-
-class EventInfo(TBase):
- """
- Attributes:
- - eventname
- - id
- - type
- - destination
- """
-
- __slots__ = [
- 'eventname',
- 'id',
- 'type',
- 'destination',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'eventname', None, None, ), # 1
- (2, TType.I32, 'id', None, None, ), # 2
- (3, TType.I32, 'type', None, None, ), # 3
- (4, TType.I32, 'destination', None, None, ), # 4
- )
-
- def __init__(self, eventname=None, id=None, type=None, destination=None,):
- self.eventname = eventname
- self.id = id
- self.type = type
- self.destination = destination
-
-
-class UserData(TBase):
- """
- Attributes:
- - name
- - email
- - role
- - permission
- - templateName
- """
-
- __slots__ = [
- 'name',
- 'email',
- 'role',
- 'permission',
- 'templateName',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'email', None, None, ), # 2
- (3, TType.I32, 'role', None, None, ), # 3
- (4, TType.I32, 'permission', None, None, ), # 4
- (5, TType.STRING, 'templateName', None, None, ), # 5
- )
-
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,):
- self.name = name
- self.email = email
- self.role = role
- self.permission = permission
- self.templateName = templateName
-
-
-class AccountInfo(TBase):
- """
- Attributes:
- - validuntil
- - login
- - options
- - valid
- - trafficleft
- - maxtraffic
- - premium
- - type
- """
-
- __slots__ = [
- 'validuntil',
- 'login',
- 'options',
- 'valid',
- 'trafficleft',
- 'maxtraffic',
- 'premium',
- 'type',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I64, 'validuntil', None, None, ), # 1
- (2, TType.STRING, 'login', None, None, ), # 2
- (3, TType.MAP, 'options', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 3
- (4, TType.BOOL, 'valid', None, None, ), # 4
- (5, TType.I64, 'trafficleft', None, None, ), # 5
- (6, TType.I64, 'maxtraffic', None, None, ), # 6
- (7, TType.BOOL, 'premium', None, None, ), # 7
- (8, TType.STRING, 'type', None, None, ), # 8
- )
-
- def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,):
- self.validuntil = validuntil
- self.login = login
- self.options = options
- self.valid = valid
- self.trafficleft = trafficleft
- self.maxtraffic = maxtraffic
- self.premium = premium
- self.type = type
-
-
-class ServiceCall(TBase):
- """
- Attributes:
- - plugin
- - func
- - arguments
- - parseArguments
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- 'arguments',
- 'parseArguments',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- (3, TType.LIST, 'arguments', (TType.STRING,None), None, ), # 3
- (4, TType.BOOL, 'parseArguments', None, None, ), # 4
- )
-
- def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,):
- self.plugin = plugin
- self.func = func
- self.arguments = arguments
- self.parseArguments = parseArguments
-
-
-class OnlineStatus(TBase):
- """
- Attributes:
- - name
- - plugin
- - packagename
- - status
- - size
- """
-
- __slots__ = [
- 'name',
- 'plugin',
- 'packagename',
- 'status',
- 'size',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'plugin', None, None, ), # 2
- (3, TType.STRING, 'packagename', None, None, ), # 3
- (4, TType.I32, 'status', None, None, ), # 4
- (5, TType.I64, 'size', None, None, ), # 5
- )
-
- def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,):
- self.name = name
- self.plugin = plugin
- self.packagename = packagename
- self.status = status
- self.size = size
-
-
-class OnlineCheck(TBase):
- """
- Attributes:
- - rid
- - data
- """
-
- __slots__ = [
- 'rid',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'rid', None, None, ), # 1
- (2, TType.MAP, 'data', (TType.STRING,None,TType.STRUCT,(OnlineStatus, OnlineStatus.thrift_spec)), None, ), # 2
- )
-
- def __init__(self, rid=None, data=None,):
- self.rid = rid
- self.data = data
-
-
-class PackageDoesNotExists(TExceptionBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
- def __str__(self):
- return repr(self)
-
-
-class FileDoesNotExists(TExceptionBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
- def __str__(self):
- return repr(self)
-
-
-class ServiceDoesNotExists(TExceptionBase):
- """
- Attributes:
- - plugin
- - func
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, func=None,):
- self.plugin = plugin
- self.func = func
-
- def __str__(self):
- return repr(self)
-
-
-class ServiceException(TExceptionBase):
- """
- Attributes:
- - msg
- """
-
- __slots__ = [
- 'msg',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'msg', None, None, ), # 1
- )
-
- def __init__(self, msg=None,):
- self.msg = msg
-
- def __str__(self):
- return repr(self)
-
diff --git a/module/setup.py b/module/setup.py
deleted file mode 100644
index 42b24859f..000000000
--- a/module/setup.py
+++ /dev/null
@@ -1,514 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from getpass import getpass
-import module.common.pylgettext as gettext
-import os
-from os import makedirs
-from os.path import abspath
-from os.path import dirname
-from os.path import exists
-from os.path import join
-from subprocess import PIPE
-from subprocess import call
-import sys
-from sys import exit
-from module.utils import get_console_encoding
-
-class Setup():
- """
- pyLoads initial setup configuration assistent
- """
-
- def __init__(self, path, config):
- self.path = path
- self.config = config
- self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
-
- def start(self):
- langs = self.config.getMetaData("general", "language")["type"].split(";")
- lang = self.ask(u"Choose your Language / WÀhle deine Sprache", "en", langs)
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"), languages=[lang, "en"], fallback=True)
- translation.install(True)
-
- #Input shorthand for yes
- self.yes = _("y")
- #Input shorthand for no
- self.no = _("n")
-
- # print ""
- # print _("Would you like to configure pyLoad via Webinterface?")
- # print _("You need a Browser and a connection to this PC for it.")
- # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True)
- # if viaweb:
- # try:
- # from module.web import ServerThread
- # ServerThread.setup = self
- # from module.web import webinterface
- # webinterface.run_simple()
- # return False
- # except Exception, e:
- # print "Setup failed with this error: ", e
- # print "Falling back to commandline setup."
-
-
- print ""
- print _("Welcome to the pyLoad Configuration Assistent.")
- print _("It will check your system and make a basic setup in order to run pyLoad.")
- print ""
- print _("The value in brackets [] always is the default value,")
- print _("in case you don't want to change it or you are unsure what to choose, just hit enter.")
- print _(
- "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore.")
- print _("If you have any problems with this assistent hit STRG-C,")
- print _("to abort and don't let him start with pyLoadCore automatically anymore.")
- print ""
- print _("When you are ready for system check, hit enter.")
- raw_input()
-
- basic, ssl, captcha, gui, web, js = self.system_check()
- print ""
-
- if not basic:
- print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.")
- print _("Please correct this and re-run pyLoad.")
- print _("Setup will now close.")
- raw_input()
- return False
-
- raw_input(_("System check finished, hit enter to see your status report."))
- print ""
- print _("## Status ##")
- print ""
-
- avail = []
- if self.check_module("Crypto"): avail.append(_("container decrypting"))
- if ssl: avail.append(_("ssl connection"))
- if captcha: avail.append(_("automatic captcha decryption"))
- if gui: avail.append(_("GUI"))
- if web: avail.append(_("Webinterface"))
- if js: avail.append(_("extended Click'N'Load"))
-
- string = ""
-
- for av in avail:
- string += ", " + av
-
- print _("Features available:") + string[1:]
- print ""
-
- if len(avail) < 5:
- print _("Featues missing: ")
- print
-
- if not self.check_module("Crypto"):
- print _("no py-crypto available")
- print _("You need this if you want to decrypt container files.")
- print ""
-
- if not ssl:
- print _("no SSL available")
- print _("This is needed if you want to establish a secure connection to core or webinterface.")
- print _("If you only want to access locally to pyLoad ssl is not usefull.")
- print ""
-
- if not captcha:
- print _("no Captcha Recognition available")
- print _("Only needed for some hosters and as freeuser.")
- print ""
-
- if not gui:
- print _("Gui not available")
- print _("The Graphical User Interface.")
- print ""
-
- if not js:
- print _("no JavaScript engine found")
- print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino")
-
- print _("You can abort the setup now and fix some dependicies if you want.")
-
- con = self.ask(_("Continue with setup?"), self.yes, bool=True)
-
- if not con:
- return False
-
- print ""
- print _("Do you want to change the config path? Current is %s") % abspath("")
- print _(
- "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.")
- path = self.ask(_("Change config path?"), self.no, bool=True)
- if path:
- self.conf_path()
- #calls exit when changed
-
- print ""
- print _("Do you want to configure login data and basic settings?")
- print _("This is recommend for first run.")
- con = self.ask(_("Make basic setup?"), self.yes, bool=True)
-
- if con:
- self.conf_basic()
-
- if ssl:
- print ""
- print _("Do you want to configure ssl?")
- ssl = self.ask(_("Configure ssl?"), self.no, bool=True)
- if ssl:
- self.conf_ssl()
-
- if web:
- print ""
- print _("Do you want to configure webinterface?")
- web = self.ask(_("Configure webinterface?"), self.yes, bool=True)
- if web:
- self.conf_web()
-
- print ""
- print _("Setup finished successfully.")
- print _("Hit enter to exit and restart pyLoad")
- raw_input()
- return True
-
- def system_check(self):
- """ make a systemcheck and return the results"""
- print _("## System Check ##")
-
- if sys.version_info[:2] > (2, 7):
- print _("Your python version is to new, Please use Python 2.6/2.7")
- python = False
- elif sys.version_info[:2] < (2, 5):
- print _("Your python version is to old, Please use at least Python 2.5")
- python = False
- else:
- print _("Python Version: OK")
- python = True
-
- curl = self.check_module("pycurl")
- self.print_dep("pycurl", curl)
-
- sqlite = self.check_module("sqlite3")
- self.print_dep("sqlite3", sqlite)
-
- basic = python and curl and sqlite
-
- print ""
-
- crypto = self.check_module("Crypto")
- self.print_dep("pycrypto", crypto)
-
- ssl = self.check_module("OpenSSL")
- self.print_dep("py-OpenSSL", ssl)
-
- print ""
-
- pil = self.check_module("Image")
- self.print_dep("py-imaging", pil)
-
- if os.name == "nt":
- tesser = self.check_prog([join(pypath, "tesseract", "tesseract.exe"), "-v"])
- else:
- tesser = self.check_prog(["tesseract", "-v"])
-
- self.print_dep("tesseract", tesser)
-
- captcha = pil and tesser
-
- print ""
-
- gui = self.check_module("PyQt4")
- self.print_dep("PyQt4", gui)
-
- print ""
- jinja = True
-
- try:
- import jinja2
-
- v = jinja2.__version__
- if v and "unknown" not in v:
- if not v.startswith("2.5") and not v.startswith("2.6"):
- print _("Your installed jinja2 version %s seems too old.") % jinja2.__version__
- print _("You can safely continue but if the webinterface is not working,")
- print _("please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary.")
- print
- jinja = False
- except:
- pass
-
- self.print_dep("jinja2", jinja)
- beaker = self.check_module("beaker")
- self.print_dep("beaker", beaker)
-
- web = sqlite and beaker
-
- from module.common import JsEngine
-
- js = True if JsEngine.ENGINE else False
- self.print_dep(_("JS engine"), js)
-
- return basic, ssl, captcha, gui, web, js
-
- def conf_basic(self):
- print ""
- print _("## Basic Setup ##")
-
- print ""
- print _("The following logindata is valid for CLI, GUI and webinterface.")
-
- from module.database import DatabaseBackend
-
- db = DatabaseBackend(None)
- db.setup()
- username = self.ask(_("Username"), "User")
- password = self.ask("", "", password=True)
- db.addUser(username, password)
- db.shutdown()
-
- print ""
- print _("External clients (GUI, CLI or other) need remote access to work over the network.")
- print _("However, if you only want to use the webinterface you may disable it to save ram.")
- self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.yes, bool=True)
-
- print ""
- langs = self.config.getMetaData("general", "language")
- self.config["general"]["language"] = self.ask(_("Language"), "en", langs["type"].split(";"))
-
- self.config["general"]["download_folder"] = self.ask(_("Downloadfolder"), "Downloads")
- self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3")
- #print _("You should disable checksum proofing, if you have low hardware requirements.")
- #self.config["general"]["checksum"] = self.ask(_("Proof checksum?"), "y", bool=True)
-
- reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True)
- self.config["reconnect"]["activated"] = reconnect
- if reconnect:
- self.config["reconnect"]["method"] = self.ask(_("Reconnect script location"), "./reconnect.sh")
-
-
- def conf_web(self):
- print ""
- print _("## Webinterface Setup ##")
-
- print ""
- self.config["webinterface"]["activated"] = self.ask(_("Activate webinterface?"), self.yes, bool=True)
- print ""
- print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.")
- self.config["webinterface"]["host"] = self.ask(_("Address"), "0.0.0.0")
- self.config["webinterface"]["port"] = self.ask(_("Port"), "8000")
- print ""
- print _("pyLoad offers several server backends, now following a short explanation.")
- print "builtin:", _("Default server, best choice if you dont know which one to choose.")
- print "threaded:", _("This server offers SSL and is a good alternative to builtin.")
- print "fastcgi:", _(
- "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job.")
- print "lightweight:", _("Very fast alternative written in C, requires libev and linux knowlegde.")
- print "\t", _("Get it from here: https://github.com/jonashaag/bjoern, compile it")
- print "\t", _("and copy bjoern.so to module/lib")
-
- print
- print _(
- "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface")
- print _("come back here and change the builtin server to the threaded one here.")
-
- self.config["webinterface"]["server"] = self.ask(_("Server"), "builtin",
- ["builtin", "threaded", "fastcgi", "lightweight"])
-
- def conf_ssl(self):
- print ""
- print _("## SSL Setup ##")
- print ""
- print _("Execute these commands from pyLoad config folder to make ssl certificates:")
- print ""
- print "openssl genrsa -out ssl.key 1024"
- print "openssl req -new -key ssl.key -out ssl.csr"
- print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt "
- print ""
- print _("If you're done and everything went fine, you can activate ssl now.")
-
- self.config["ssl"]["activated"] = self.ask(_("Activate SSL?"), self.yes, bool=True)
-
- def set_user(self):
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"),
- languages=[self.config["general"]["language"], "en"], fallback=True)
- translation.install(True)
-
- from module.database import DatabaseBackend
-
- db = DatabaseBackend(None)
- db.setup()
-
- noaction = True
- try:
- while True:
- print _("Select action")
- print _("1 - Create/Edit user")
- print _("2 - List users")
- print _("3 - Remove user")
- print _("4 - Quit")
- action = raw_input("[1]/2/3/4: ")
- if not action in ("1", "2", "3", "4"):
- continue
- elif action == "1":
- print ""
- username = self.ask(_("Username"), "User")
- password = self.ask("", "", password=True)
- db.addUser(username, password)
- noaction = False
- elif action == "2":
- print ""
- print _("Users")
- print "-----"
- users = db.listUsers()
- noaction = False
- for user in users:
- print user
- print "-----"
- print ""
- elif action == "3":
- print ""
- username = self.ask(_("Username"), "")
- if username:
- db.removeUser(username)
- noaction = False
- elif action == "4":
- break
- finally:
- if not noaction:
- db.shutdown()
-
- def conf_path(self, trans=False):
- if trans:
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"),
- languages=[self.config["general"]["language"], "en"], fallback=True)
- translation.install(True)
-
- print _("Setting new configpath, current configuration will not be transfered!")
- path = self.ask(_("Configpath"), abspath(""))
- try:
- path = join(pypath, path)
- if not exists(path):
- makedirs(path)
- f = open(join(pypath, "module", "config", "configdir"), "wb")
- f.write(path)
- f.close()
- print _("Configpath changed, setup will now close, please restart to go on.")
- print _("Press Enter to exit.")
- raw_input()
- exit()
- except Exception, e:
- print _("Setting config path failed: %s") % str(e)
-
- def print_dep(self, name, value):
- """Print Status of dependency"""
- if value:
- print _("%s: OK") % name
- else:
- print _("%s: missing") % name
-
-
- def check_module(self, module):
- try:
- __import__(module)
- return True
- except:
- return False
-
- def check_prog(self, command):
- pipe = PIPE
- try:
- call(command, stdout=pipe, stderr=pipe)
- return True
- except:
- return False
-
- def ask(self, qst, default, answers=[], bool=False, password=False):
- """produce one line to asking for input"""
- if answers:
- info = "("
-
- for i, answer in enumerate(answers):
- info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer)
-
- info += ")"
- elif bool:
- if default == self.yes:
- info = "([%s]/%s)" % (self.yes, self.no)
- else:
- info = "(%s/[%s])" % (self.yes, self.no)
- else:
- info = "[%s]" % default
-
- if password:
- p1 = True
- p2 = False
- while p1 != p2:
- # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS)
- sys.stdout.write(_("Password: "))
- p1 = getpass("")
-
- if len(p1) < 4:
- print _("Password too short. Use at least 4 symbols.")
- continue
-
- sys.stdout.write(_("Password (again): "))
- p2 = getpass("")
-
- if p1 == p2:
- return p1
- else:
- print _("Passwords did not match.")
-
- while True:
- try:
- input = raw_input(qst + " %s: " % info)
- except KeyboardInterrupt:
- print "\nSetup interrupted"
- exit()
-
- input = input.decode(self.stdin_encoding)
-
- if input.strip() == "":
- input = default
-
- if bool:
- # yes, true,t are inputs for booleans with value true
- if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]:
- return True
- # no, false,f are inputs for booleans with value false
- elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]:
- return False
- else:
- print _("Invalid Input")
- continue
-
- if not answers:
- return input
-
- else:
- if input in answers:
- return input
- else:
- print _("Invalid Input")
-
-
-if __name__ == "__main__":
- test = Setup(join(abspath(dirname(__file__)), ".."), None)
- test.start()
diff --git a/module/unescape.py b/module/unescape.py
deleted file mode 100644
index d8999e077..000000000
--- a/module/unescape.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from module.utils import html_unescape
-#deprecated
-unescape = html_unescape \ No newline at end of file
diff --git a/module/utils.py b/module/utils.py
deleted file mode 100644
index 8748b7693..000000000
--- a/module/utils.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# -*- coding: utf-8 -*-
-
-""" Store all usefull functions here """
-
-import os
-import sys
-import time
-import re
-from os.path import join
-from string import maketrans
-from htmlentitydefs import name2codepoint
-
-def chmod(*args):
- try:
- os.chmod(*args)
- except:
- pass
-
-
-def decode(string):
- """ decode string with utf if possible """
- try:
- return string.decode("utf8", "replace")
- except:
- return string
-
-
-def remove_chars(string, repl):
- """ removes all chars in repl from string"""
- if type(string) == str:
- return string.translate(maketrans("", ""), repl)
- elif type(string) == unicode:
- return string.translate(dict([(ord(s), None) for s in repl]))
-
-
-def save_path(name):
- #remove some chars
- if os.name == 'nt':
- return remove_chars(name, '/\\?%*:|"<>')
- else:
- return remove_chars(name, '/\\"')
-
-
-def save_join(*args):
- """ joins a path, encoding aware """
- return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args]))
-
-
-# File System Encoding functions:
-# Use fs_encode before accesing files on disk, it will encode the string properly
-
-if sys.getfilesystemencoding().startswith('ANSI'):
- def fs_encode(string):
- try:
- string = string.encode('utf-8')
- finally:
- return string
-
- fs_decode = decode #decode utf8
-
-else:
- fs_encode = fs_decode = lambda x: x # do nothing
-
-def get_console_encoding(enc):
- if os.name == "nt":
- if enc == "cp65001": # aka UTF-8
- print "WARNING: Windows codepage 65001 is not supported."
- enc = "cp850"
- else:
- enc = "utf8"
-
- return enc
-
-def compare_time(start, end):
- start = map(int, start)
- end = map(int, end)
-
- if start == end: return True
-
- now = list(time.localtime()[3:5])
- if start < now < end: return True
- elif start > end and (now > start or now < end): return True
- elif start < now > end < start: return True
- else: return False
-
-
-def formatSize(size):
- """formats size of bytes"""
- size = int(size)
- steps = 0
- sizes = ["B", "KiB", "MiB", "GiB", "TiB"]
- while size > 1000:
- size /= 1024.0
- steps += 1
- return "%.2f %s" % (size, sizes[steps])
-
-
-def formatSpeed(speed):
- return formatSize(speed) + "/s"
-
-
-def freeSpace(folder):
- if os.name == "nt":
- import ctypes
-
- free_bytes = ctypes.c_ulonglong(0)
- ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
- return free_bytes.value
- else:
- from os import statvfs
-
- s = statvfs(folder)
- return s.f_bsize * s.f_bavail
-
-
-def uniqify(seq, idfun=None):
-# order preserving
- if idfun is None:
- def idfun(x): return x
- seen = {}
- result = []
- for item in seq:
- marker = idfun(item)
- # in old Python versions:
- # if seen.has_key(marker)
- # but in new ones:
- if marker in seen: continue
- seen[marker] = 1
- result.append(item)
- return result
-
-
-def parseFileSize(string, unit=None): #returns bytes
- if not unit:
- m = re.match(r"(\d*[\.,]?\d+)(.*)", string.strip().lower())
- if m:
- traffic = float(m.group(1).replace(",", "."))
- unit = m.group(2)
- else:
- return 0
- else:
- if isinstance(string, basestring):
- traffic = float(string.replace(",", "."))
- else:
- traffic = string
-
- #ignore case
- unit = unit.lower().strip()
-
- if unit in ("gb", "gig", "gbyte", "gigabyte", "gib", "g"):
- traffic *= 1 << 30
- elif unit in ("mb", "mbyte", "megabyte", "mib", "m"):
- traffic *= 1 << 20
- elif unit in ("kb", "kib", "kilobyte", "kbyte", "k"):
- traffic *= 1 << 10
-
- return traffic
-
-
-def lock(func):
- def new(*args):
- #print "Handler: %s args: %s" % (func,args[1:])
- args[0].lock.acquire()
- try:
- return func(*args)
- finally:
- args[0].lock.release()
-
- return new
-
-
-def fixup(m):
- text = m.group(0)
- if text[:2] == "&#":
- # character reference
- try:
- if text[:3] == "&#x":
- return unichr(int(text[3:-1], 16))
- else:
- return unichr(int(text[2:-1]))
- except ValueError:
- pass
- else:
- # named entity
- try:
- name = text[1:-1]
- text = unichr(name2codepoint[name])
- except KeyError:
- pass
-
- return text # leave as is
-
-
-def html_unescape(text):
- """Removes HTML or XML character references and entities from a text string"""
- return re.sub("&#?\w+;", fixup, text)
-
-if __name__ == "__main__":
- print freeSpace(".")
-
- print remove_chars("ab'cdgdsf''ds'", "'ghd")
diff --git a/module/web/ServerThread.py b/module/web/ServerThread.py
deleted file mode 100644
index 84667e5f6..000000000
--- a/module/web/ServerThread.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-from __future__ import with_statement
-from os.path import exists
-
-import os
-import threading
-import logging
-
-core = None
-setup = None
-log = logging.getLogger("log")
-
-class WebServer(threading.Thread):
- def __init__(self, pycore):
- global core
- threading.Thread.__init__(self)
- self.core = pycore
- core = pycore
- self.running = True
- self.server = pycore.config['webinterface']['server']
- self.https = pycore.config['webinterface']['https']
- self.cert = pycore.config["ssl"]["cert"]
- self.key = pycore.config["ssl"]["key"]
- self.host = pycore.config['webinterface']['host']
- self.port = pycore.config['webinterface']['port']
-
- self.setDaemon(True)
-
- def run(self):
- import webinterface
- global webinterface
-
- if self.https:
- if not exists(self.cert) or not exists(self.key):
- log.warning(_("SSL certificates not found."))
- self.https = False
-
- if self.server in ("lighttpd", "nginx"):
- log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server)
- log.warning(_("You can use the threaded server which offers good performance and ssl,"))
- log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server)
- log.warning(_("sample configs are located in the module/web/servers directory"))
- self.server = "builtin"
-
- if self.server == "fastcgi":
- try:
- import flup
- except:
- log.warning(_("Can't use %(server)s, python-flup is not installed!") % {
- "server": self.server})
- self.server = "builtin"
- elif self.server == "lightweight":
- try:
- import bjoern
- except Exception, e:
- log.error(_("Error importing lightweight server: %s") % e)
- log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern"))
- log.warning(_("Copy the boern.so to module/lib folder or use setup.py install"))
- log.warning(_("Of course you need to be familiar with linux and know how to compile software"))
- self.server = "builtin"
-
- if os.name == "nt":
- self.core.log.info(_("Server set to threaded, due to known performance problems on windows."))
- self.core.config['webinterface']['server'] = "threaded"
- self.server = "threaded"
-
-
- if self.server == "fastcgi":
- self.start_fcgi()
- elif self.server == "threaded":
- self.start_threaded()
- elif self.server == "lightweight":
- self.start_lightweight()
- else:
- self.start_builtin()
-
- def start_builtin(self):
-
- if self.https:
- log.warning(_("This server offers no SSL, please consider using threaded instead"))
-
- self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_simple(host=self.host, port=self.port)
-
- def start_threaded(self):
- if self.https:
- self.core.log.info(_("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- else:
- self.cert = ""
- self.key = ""
- self.core.log.info(_("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
-
- webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key)
-
- def start_fcgi(self):
-
- self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_fcgi(host=self.host, port=self.port)
-
-
- def start_lightweight(self):
- if self.https:
- log.warning(_("This server offers no SSL, please consider using threaded instead"))
-
- self.core.log.info(_("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_lightweight(host=self.host, port=self.port)
-
- def quit(self):
- self.running = False
diff --git a/module/web/__init__.py b/module/web/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/web/__init__.py
+++ /dev/null
diff --git a/module/web/api_app.py b/module/web/api_app.py
deleted file mode 100644
index 1629c1677..000000000
--- a/module/web/api_app.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from urllib import unquote
-from itertools import chain
-from traceback import format_exc, print_exc
-
-from bottle import route, request, response, HTTPError
-
-from utils import toDict, set_session
-from webinterface import PYLOAD
-
-from module.common.json_layer import json
-from module.lib.SafeEval import const_eval as literal_eval
-from module.Api import BaseObject
-
-# json encoder that accepts TBase objects
-class TBaseEncoder(json.JSONEncoder):
-
- def default(self, o):
- if isinstance(o, BaseObject):
- return toDict(o)
- return json.JSONEncoder.default(self, o)
-
-
-# accepting positional arguments, as well as kwargs via post and get
-
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#")
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#", method="POST")
-def call_api(func, args=""):
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- s = request.environ.get('beaker.session')
- if 'session' in request.POST:
- s = s.get_by_id(request.POST['session'])
-
- if not s or not s.get("authenticated", False):
- return HTTPError(403, json.dumps("Forbidden"))
-
- if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}):
- return HTTPError(401, json.dumps("Unauthorized"))
-
- args = args.split("/")[1:]
- kwargs = {}
-
- for x, y in chain(request.GET.iteritems(), request.POST.iteritems()):
- if x == "session": continue
- kwargs[x] = unquote(y)
-
- try:
- return callApi(func, *args, **kwargs)
- except Exception, e:
- print_exc()
- return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()}))
-
-
-def callApi(func, *args, **kwargs):
- if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
- print "Invalid API call", func
- return HTTPError(404, json.dumps("Not Found"))
-
- result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args],
- **dict([(x, literal_eval(y)) for x, y in kwargs.iteritems()]))
-
- # null is invalid json response
- if result is None: result = True
-
- return json.dumps(result, cls=TBaseEncoder)
-
-
-#post -> username, password
-@route("/api/login", method="POST")
-def login():
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- user = request.forms.get("username")
- password = request.forms.get("password")
-
- info = PYLOAD.checkAuth(user, password)
-
- if not info:
- return json.dumps(False)
-
- s = set_session(request, info)
-
- # get the session id by dirty way, documentations seems wrong
- try:
- sid = s._headers["cookie_out"].split("=")[1].split(";")[0]
- return json.dumps(sid)
- except:
- return json.dumps(True)
-
-
-@route("/api/logout")
-def logout():
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- s = request.environ.get('beaker.session')
- s.delete()
diff --git a/module/web/cnl_app.py b/module/web/cnl_app.py
deleted file mode 100644
index d8f7c1180..000000000
--- a/module/web/cnl_app.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-from os.path import join
-import re
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-from bottle import route, request, HTTPError
-from webinterface import PYLOAD, DL_ROOT, JS
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-
-def local_check(function):
- def _view(*args, **kwargs):
- if request.environ.get('REMOTE_ADDR', "0") in ('127.0.0.1', 'localhost') \
- or request.environ.get('HTTP_HOST','0') == '127.0.0.1:9666':
- return function(*args, **kwargs)
- else:
- return HTTPError(403, "Forbidden")
-
- return _view
-
-
-@route("/flash")
-@route("/flash/:id")
-@route("/flash", method="POST")
-@local_check
-def flash(id="0"):
- return "JDownloader\r\n"
-
-@route("/flash/add", method="POST")
-@local_check
-def add(request):
- package = request.POST.get('referer', None)
- urls = filter(lambda x: x != "", request.POST['urls'].split("\n"))
-
- if package:
- PYLOAD.addPackage(package, urls, 0)
- else:
- PYLOAD.generateAndAddPackages(urls, 0)
-
- return ""
-
-@route("/flash/addcrypted", method="POST")
-@local_check
-def addcrypted():
-
- package = request.forms.get('referer', 'ClickAndLoad Package')
- dlc = request.forms['crypted'].replace(" ", "+")
-
- dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc")
- dlc_file = open(dlc_path, "wb")
- dlc_file.write(dlc)
- dlc_file.close()
-
- try:
- PYLOAD.addPackage(package, [dlc_path], 0)
- except:
- return HTTPError()
- else:
- return "success\r\n"
-
-@route("/flash/addcrypted2", method="POST")
-@local_check
-def addcrypted2():
-
- package = request.forms.get("source", None)
- crypted = request.forms["crypted"]
- jk = request.forms["jk"]
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- if JS:
- jk = "%s f()" % jk
- jk = JS.eval(jk)
-
- else:
- try:
- jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1]
- except:
- ## Test for some known js functions to decode
- if jk.find("dec") > -1 and jk.find("org") > -1:
- org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1]
- jk = list(org)
- jk.reverse()
- jk = "".join(jk)
- else:
- print "Could not decrypt key, please install py-spidermonkey or ossp-js"
-
- try:
- Key = unhexlify(jk)
- except:
- print "Could not decrypt key, please install py-spidermonkey or ossp-js"
- return "failed"
-
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- try:
- if package:
- PYLOAD.addPackage(package, result, 0)
- else:
- PYLOAD.generateAndAddPackages(result, 0)
- except:
- return "failed can't add"
- else:
- return "success\r\n"
-
-@route("/flashgot_pyload")
-@route("/flashgot_pyload", method="POST")
-@route("/flashgot")
-@route("/flashgot", method="POST")
-@local_check
-def flashgot():
- if request.environ['HTTP_REFERER'] != "http://localhost:9666/flashgot" and request.environ['HTTP_REFERER'] != "http://127.0.0.1:9666/flashgot":
- return HTTPError()
-
- autostart = int(request.forms.get('autostart', 0))
- package = request.forms.get('package', None)
- urls = filter(lambda x: x != "", request.forms['urls'].split("\n"))
- folder = request.forms.get('dir', None)
-
- if package:
- PYLOAD.addPackage(package, urls, autostart)
- else:
- PYLOAD.generateAndAddPackages(urls, autostart)
-
- return ""
-
-@route("/crossdomain.xml")
-@local_check
-def crossdomain():
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
-
-@route("/flash/checkSupportForUrl")
-@local_check
-def checksupport():
-
- url = request.GET.get("url")
- res = PYLOAD.checkURLs([url])
- supported = (not res[0][1] is None)
-
- return str(supported).lower()
-
-@route("/jdcheck.js")
-@local_check
-def jdcheck():
- rep = "jdownloader=true;\n"
- rep += "var version='9.581;'"
- return rep
diff --git a/module/web/filters.py b/module/web/filters.py
deleted file mode 100644
index 13b8345fc..000000000
--- a/module/web/filters.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
-from os.path import abspath, commonprefix, join
-
-quotechar = "::/"
-
-try:
- from os.path import relpath
-except:
- from posixpath import curdir, sep, pardir
- def relpath(path, start=curdir):
- """Return a relative version of a path"""
- if not path:
- raise ValueError("no path specified")
- start_list = abspath(start).split(sep)
- path_list = abspath(path).split(sep)
- # Work out how much of the filepath is shared by start and path.
- i = len(commonprefix([start_list, path_list]))
- rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
- if not rel_list:
- return curdir
- return join(*rel_list)
-
-
-def quotepath(path):
- try:
- return path.replace("../", quotechar)
- except AttributeError:
- return path
- except:
- return ""
-
-def unquotepath(path):
- try:
- return path.replace(quotechar, "../")
- except AttributeError:
- return path
- except:
- return ""
-
-def path_make_absolute(path):
- p = os.path.abspath(path)
- if p[-1] == os.path.sep:
- return p
- else:
- return p + os.path.sep
-
-def path_make_relative(path):
- p = relpath(path)
- if p[-1] == os.path.sep:
- return p
- else:
- return p + os.path.sep
-
-def truncate(value, n):
- if (n - len(value)) < 3:
- return value[:n]+"..."
- return value
-
-def date(date, format):
- return date \ No newline at end of file
diff --git a/module/web/json_app.py b/module/web/json_app.py
deleted file mode 100644
index f3626405c..000000000
--- a/module/web/json_app.py
+++ /dev/null
@@ -1,312 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from os.path import join
-from traceback import print_exc
-from shutil import copyfileobj
-
-from bottle import route, request, HTTPError
-
-from webinterface import PYLOAD
-
-from utils import login_required, render_to_response, toDict
-
-from module.utils import decode, formatSize
-
-
-def format_time(seconds):
- seconds = int(seconds)
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
-
-def get_sort_key(item):
- return item["order"]
-
-
-@route("/json/status")
-@route("/json/status", method="POST")
-@login_required('LIST')
-def status():
- try:
- status = toDict(PYLOAD.statusServer())
- status['captcha'] = PYLOAD.isCaptchaWaiting()
- return status
- except:
- return HTTPError()
-
-
-@route("/json/links")
-@route("/json/links", method="POST")
-@login_required('LIST')
-def links():
- try:
- links = [toDict(x) for x in PYLOAD.statusDownloads()]
- ids = []
- for link in links:
- ids.append(link['fid'])
-
- if link['status'] == 12:
- link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed']))
- elif link['status'] == 5:
- link['percent'] = 0
- link['size'] = 0
- link['bleft'] = 0
- link['info'] = _("waiting %s") % link['format_wait']
- else:
- link['info'] = ""
-
- data = {'links': links, 'ids': ids}
- return data
- except Exception, e:
- print_exc()
- return HTTPError()
-
-
-@route("/json/packages")
-@login_required('LIST')
-def packages():
- print "/json/packages"
- try:
- data = PYLOAD.getQueue()
-
- for package in data:
- package['links'] = []
- for file in PYLOAD.get_package_files(package['id']):
- package['links'].append(PYLOAD.get_file_info(file))
-
- return data
-
- except:
- return HTTPError()
-
-
-@route("/json/package/<id:int>")
-@login_required('LIST')
-def package(id):
- try:
- data = toDict(PYLOAD.getPackageData(id))
- data["links"] = [toDict(x) for x in data["links"]]
-
- for pyfile in data["links"]:
- if pyfile["status"] == 0:
- pyfile["icon"] = "status_finished.png"
- elif pyfile["status"] in (2, 3):
- pyfile["icon"] = "status_queue.png"
- elif pyfile["status"] in (9, 1):
- pyfile["icon"] = "status_offline.png"
- elif pyfile["status"] == 5:
- pyfile["icon"] = "status_waiting.png"
- elif pyfile["status"] == 8:
- pyfile["icon"] = "status_failed.png"
- elif pyfile["status"] == 4:
- pyfile["icon"] = "arrow_right.png"
- elif pyfile["status"] in (11, 13):
- pyfile["icon"] = "status_proc.png"
- else:
- pyfile["icon"] = "status_downloading.png"
-
- tmp = data["links"]
- tmp.sort(key=get_sort_key)
- data["links"] = tmp
- return data
-
- except:
- print_exc()
- return HTTPError()
-
-
-@route("/json/package_order/:ids")
-@login_required('ADD')
-def package_order(ids):
- try:
- pid, pos = ids.split("|")
- PYLOAD.orderPackage(int(pid), int(pos))
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/abort_link/<id:int>")
-@login_required('DELETE')
-def abort_link(id):
- try:
- PYLOAD.stopDownloads([id])
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/link_order/:ids")
-@login_required('ADD')
-def link_order(ids):
- try:
- pid, pos = ids.split("|")
- PYLOAD.orderFile(int(pid), int(pos))
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/add_package")
-@route("/json/add_package", method="POST")
-@login_required('ADD')
-def add_package():
- name = request.forms.get("add_name", "New Package").strip()
- queue = int(request.forms['add_dest'])
- links = decode(request.forms['add_links'])
- links = links.split("\n")
- pw = request.forms.get("add_password", "").strip("\n\r")
-
- try:
- f = request.files['add_file']
-
- if not name or name == "New Package":
- name = f.name
-
- fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename)
- destination = open(fpath, 'wb')
- copyfileobj(f.file, destination)
- destination.close()
- links.insert(0, fpath)
- except:
- pass
-
- name = name.decode("utf8", "ignore")
-
- links = map(lambda x: x.strip(), links)
- links = filter(lambda x: x != "", links)
-
- pack = PYLOAD.addPackage(name, links, queue)
- if pw:
- pw = pw.decode("utf8", "ignore")
- data = {"password": pw}
- PYLOAD.setPackageData(pack, data)
-
-
-@route("/json/move_package/<dest:int>/<id:int>")
-@login_required('MODIFY')
-def move_package(dest, id):
- try:
- PYLOAD.movePackage(dest, id)
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/edit_package", method="POST")
-@login_required('MODIFY')
-def edit_package():
- try:
- id = int(request.forms.get("pack_id"))
- data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"),
- "folder": request.forms.get("pack_folder").decode("utf8", "ignore"),
- "password": request.forms.get("pack_pws").decode("utf8", "ignore")}
-
- PYLOAD.setPackageData(id, data)
- return {"response": "success"}
-
- except:
- return HTTPError()
-
-
-@route("/json/set_captcha")
-@route("/json/set_captcha", method="POST")
-@login_required('ADD')
-def set_captcha():
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- try:
- PYLOAD.setCaptchaResult(request.forms["cap_id"], request.forms["cap_result"])
- except:
- pass
-
- task = PYLOAD.getCaptchaTask()
-
- if task.tid >= 0:
- src = "data:image/%s;base64,%s" % (task.type, task.data)
-
- return {'captcha': True, 'id': task.tid, 'src': src, 'result_type' : task.resultType}
- else:
- return {'captcha': False}
-
-
-@route("/json/load_config/:category/:section")
-@login_required("SETTINGS")
-def load_config(category, section):
- conf = None
- if category == "general":
- conf = PYLOAD.getConfigDict()
- elif category == "plugin":
- conf = PYLOAD.getPluginConfigDict()
-
- for key, option in conf[section].iteritems():
- if key in ("desc","outline"): continue
-
- if ";" in option["type"]:
- option["list"] = option["type"].split(";")
-
- option["value"] = decode(option["value"])
-
- return render_to_response("settings_item.html", {"skey": section, "section": conf[section]})
-
-
-@route("/json/save_config/:category", method="POST")
-@login_required("SETTINGS")
-def save_config(category):
- for key, value in request.POST.iteritems():
- try:
- section, option = key.split("|")
- except:
- continue
-
- if category == "general": category = "core"
-
- PYLOAD.setConfigValue(section, option, decode(value), category)
-
-
-@route("/json/add_account", method="POST")
-@login_required("ACCOUNTS")
-def add_account():
- login = request.POST["account_login"]
- password = request.POST["account_password"]
- type = request.POST["account_type"]
-
- PYLOAD.updateAccount(type, login, password)
-
-
-@route("/json/update_accounts", method="POST")
-@login_required("ACCOUNTS")
-def update_accounts():
- deleted = [] #dont update deleted accs or they will be created again
-
- for name, value in request.POST.iteritems():
- value = value.strip()
- if not value: continue
-
- tmp, user = name.split(";")
- plugin, action = tmp.split("|")
-
- if (plugin, user) in deleted: continue
-
- if action == "password":
- PYLOAD.updateAccount(plugin, user, value)
- elif action == "time" and "-" in value:
- PYLOAD.updateAccount(plugin, user, options={"time": [value]})
- elif action == "limitdl" and value.isdigit():
- PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]})
- elif action == "delete":
- deleted.append((plugin,user))
- PYLOAD.removeAccount(plugin, user)
-
-@route("/json/change_password", method="POST")
-def change_password():
-
- user = request.POST["user_login"]
- oldpw = request.POST["login_current_password"]
- newpw = request.POST["login_new_password"]
-
- if not PYLOAD.changePassword(user, oldpw, newpw):
- print "Wrong password"
- return HTTPError()
diff --git a/module/web/media/default/css/MooDialog.css b/module/web/media/default/css/MooDialog.css
deleted file mode 100644
index 48c9166ad..000000000
--- a/module/web/media/default/css/MooDialog.css
+++ /dev/null
@@ -1,92 +0,0 @@
-/* Created by Arian Stolwijk <http://www.aryweb.nl> */
-
-.MooDialog {
-/* position: fixed;*/
- margin: 0 auto 0 -350px;
- width:600px;
- padding:14px;
- left:50%;
- top: 100px;
-
- position: absolute;
- left: 50%;
- z-index: 50000;
-
- background: #eef5f8;
- color: black;
- border-radius: 7px;
- -moz-border-radius: 7px;
- -webkit-border-radius: 7px;
- border-radius: 7px;
- -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
- -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
- box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
-}
-
-.MooDialogTitle {
- padding-top: 30px;
-}
-
-.MooDialog .title {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- padding: 3px 20px;
- background: #b7c4dc;
- border-bottom: 1px solid #a1aec5;
- font-weight: bold;
- text-shadow: 1px 1px 0 #fff;
- color: black;
- border-radius: 7px;
- -moz-border-radius: 7px;
- -webkit-border-radius: 7px;
-}
-
-.MooDialog .close {
- background: url(/media/img/dialog-close.png) no-repeat;
- width: 16px;
- height: 16px;
- display: block;
- cursor: pointer;
- top: -5px;
- left: -5px;
- position: absolute;
-}
-
-.MooDialog .buttons {
- text-align: right;
- margin: 0;
- padding: 0;
- border: 0;
- background: none;
-}
-
-.MooDialog .iframe {
- width: 100%;
- height: 100%;
-}
-
-.MooDialog .textInput {
- width: 200px;
- float: left;
-}
-
-.MooDialog .MooDialogAlert,
-.MooDialog .MooDialogConfirm,
-.MooDialog .MooDialogPrompt,
-.MooDialog .MooDialogError {
- background: url(/media/img/dialog-warning.png) no-repeat;
- padding-left: 40px;
- min-height: 40px;
-}
-
-.MooDialog .MooDialogConfirm,
-.MooDialog .MooDialogPromt {
- background: url(/media/img/dialog-question.png) no-repeat;
-}
-
-.MooDialog .MooDialogError {
- background: url(/media/img/dialog-error.png) no-repeat;
-}
-
diff --git a/module/web/media/default/css/default.css b/module/web/media/default/css/default.css
deleted file mode 100644
index 116f9725a..000000000
--- a/module/web/media/default/css/default.css
+++ /dev/null
@@ -1,908 +0,0 @@
-.hidden {
- display:none;
-}
-.leftalign {
- text-align:left;
-}
-.centeralign {
- text-align:center;
-}
-.rightalign {
- text-align:right;
-}
-
-
-.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
- background-color:#000080;
- color:#fff !important;
- text-decoration:none;
- padding:0 0.2em;
- margin:0.1em 0.2em;
- border:none !important;
-}
-.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
- background-color:#808080;
- color:#fff !important;
- text-decoration:none;
- padding:0 0.2em;
- margin:0.1em 0.2em;
- border:none !important;
-}
-
-.dokuwiki div.plugin_translation ul li a:hover img {
- opacity:1.0;
- height:15px;
-}
-
-body {
- margin:0;
- padding:0;
- background-color:white;
- color:black;
- font-size:12px;
- font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
- font-family:sans-serif;
- font-size:99, 96%;
- font-size-adjust:none;
- font-style:normal;
- font-variant:normal;
- font-weight:normal;
- line-height:normal;
-}
-hr {
- border-width:0;
- border-bottom:1px #aaa dotted;
-}
-img {
- border:none;
-}
-form {
- margin:0px;
- padding:0px;
- border:none;
- display:inline;
- background:transparent;
-}
-ul li {
- margin:5px;
-}
-textarea {
- font-family:monospace;
-}
-table {
- margin:0.5em 0;
- border-collapse:collapse;
-}
-td {
- padding:0.25em;
- border:1pt solid #ADB9CC;
-}
-a {
- color:#3465a4;
- text-decoration:none;
-}
-a:hover {
- text-decoration:underline;
-}
-
-option {
- border:0 none #fff;
-}
-strong.highlight {
- background-color:#fc9;
- padding:1pt;
-}
-#pagebottom {
- clear:both;
-}
-hr {
- height:1px;
- color:#c0c0c0;
- background-color:#c0c0c0;
- border:none;
- margin:.2em 0 .2em 0;
-}
-
-.invisible {
- margin:0px;
- border:0px;
- padding:0px;
- height:0px;
- visibility:hidden;
-}
-.left {
- float:left !important;
-}
-.right {
- float:right !important;
-}
-.center {
- text-align:center;
-}
-div#body-wrapper {
- padding:40px 40px 10px 40px;
- font-size:127%;
-}
-div#content {
- margin-top:-20px;
- padding:0;
- font-size:14px;
- color:black;
- line-height:1.5em;
-}
-h1, h2, h3, h4, h5, h6 {
- background:transparent none repeat scroll 0 0;
- border-bottom:1px solid #aaa;
- color:black;
- font-weight:normal;
- margin:0;
- padding:0;
- padding-bottom:0.17em;
- padding-top:0.5em;
-}
-h1 {
- font-size:188%;
- line-height:1.2em;
- margin-bottom:0.1em;
- padding-bottom:0;
-}
-h2 {
- font-size:150%;
-}
-h3, h4, h5, h6 {
- border-bottom:none;
- font-weight:bold;
-}
-h3 {
- font-size:132%;
-}
-h4 {
- font-size:116%;
-}
-h5 {
- font-size:100%;
-}
-h6 {
- font-size:80%;
-}
-ul#page-actions, ul#page-actions-more {
- float:right;
- margin:10px 10px 0 10px;
- padding:6px;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- white-space: nowrap;
- border-radius:5px;
- -moz-border-radius:5px;
-}
-ul#user-actions {
- padding:5px;
- margin:0;
- display:inline;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- -moz-border-radius:3px;
- border-radius:3px;
-}
-ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
- display:inline;
-}
-ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
- text-decoration:none;
- color:black;
- display:inline;
- margin:0 3px;
- padding:2px 0px 2px 18px;
-}
-ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
- /*text-decoration:underline;*/
-}
-/***************************/
-ul#page-actions2 {
- float:left;
- margin:10px 10px 0 10px;
- padding:6px;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- border-radius:5px;
- -moz-border-radius:5px;
-}
-ul#user-actions2 {
- padding:5px;
- margin:0;
- display:inline;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- border-radius:3px;
- -moz-border-radius:3px;
-}
-ul#page-actions2 li, ul#user-actions2 li {
- display:inline;
-}
-ul#page-actions2 a, ul#user-actions2 a {
- text-decoration:none;
- color:black;
- display:inline;
- margin:0 3px;
- padding:2px 0px 2px 18px;
-}
-ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
-ul#page-actions-more a:hover, ul#page-actions-more a:focus{
- color: #4e7bb4;
-}
-/****************************/
-.hidden {
- display:none;
-}
-
-a.action.index {
- background:transparent url(/media/default/img/wiki-tools-index.png) 0px 1px no-repeat;
-}
-a.action.recent {
- background:transparent url(/media/default/img/wiki-tools-recent.png) 0px 1px no-repeat;
-}
-a.logout {
- background:transparent url(/media/default/img/user-actions-logout.png) 0px 1px no-repeat;
-}
-
-a.info {
- background:transparent url(/media/default/img/user-info.png) 0px 1px no-repeat;
-}
-
-a.admin {
- background:transparent url(/media/default/img/user-actions-admin.png) 0px 1px no-repeat;
-}
-a.profile {
- background:transparent url(/media/default/img/user-actions-profile.png) 0px 1px no-repeat;
-}
-a.create, a.edit {
- background:transparent url(/media/default/img/page-tools-edit.png) 0px 1px no-repeat;
-}
-a.source, a.show {
- background:transparent url(/media/default/img/page-tools-source.png) 0px 1px no-repeat;
-}
-a.revisions {
- background:transparent url(/media/default/img/page-tools-revisions.png) 0px 1px no-repeat;
-}
-a.subscribe, a.unsubscribe {
- background:transparent url(/media/default/img/page-tools-subscribe.png) 0px 1px no-repeat;
-}
-a.backlink {
- background:transparent url(/media/default/img/page-tools-backlinks.png) 0px 1px no-repeat;
-}
-a.play {
- background:transparent url(/media/default/img/control_play.png) 0px 1px no-repeat;
-}
-.time {
- background:transparent url(/media/default/img/status_None.png) 0px 1px no-repeat;
- padding: 2px 0px 2px 18px;
- margin: 0px 3px;
-}
-.reconnect {
- background:transparent url(/media/default/img/reconnect.png) 0px 1px no-repeat;
- padding: 2px 0px 2px 18px;
- margin: 0px 3px;
-}
-a.play:hover {
- background:transparent url(/media/default/img/control_play_blue.png) 0px 1px no-repeat;
-}
-a.cancel {
- background:transparent url(/media/default/img/control_cancel.png) 0px 1px no-repeat;
-}
-a.cancel:hover {
- background:transparent url(/media/default/img/control_cancel_blue.png) 0px 1px no-repeat;
-}
-a.pause {
- background:transparent url(/media/default/img/control_pause.png) 0px 1px no-repeat;
-}
-a.pause:hover {
- background:transparent url(/media/default/img/control_pause_blue.png) 0px 1px no-repeat;
- font-weight: bold;
-}
-a.stop {
- background:transparent url(/media/default/img/control_stop.png) 0px 1px no-repeat;
-}
-a.stop:hover {
- background:transparent url(/media/default/img/control_stop_blue.png) 0px 1px no-repeat;
-}
-a.add {
- background:transparent url(/media/default/img/control_add.png) 0px 1px no-repeat;
-}
-a.add:hover {
- background:transparent url(/media/default/img/control_add_blue.png) 0px 1px no-repeat;
-}
-a.cog {
- background:transparent url(/media/default/img/cog.png) 0px 1px no-repeat;
-}
-#head-panel {
- background:#525252 url(/media/default/img/head_bg1.png) bottom left repeat-x;
-}
-#head-panel h1 {
- display:none;
- margin:0;
- text-decoration:none;
- padding-top:0.8em;
- padding-left:3.3em;
- font-size:2.6em;
- color:#eeeeec;
-}
-#head-panel #head-logo {
- float:left;
- margin:5px 0 -15px 5px;
- padding:0;
- overflow:visible;
-}
-#head-menu {
- background:transparent url(/media/default/img/tabs-border-bottom.png) 0 100% repeat-x;
- width:100%;
- float:left;
- margin:0;
- padding:0;
- padding-top:0.8em;
-}
-#head-menu ul {
- list-style:none;
- margin:0 1em 0 2em;
-}
-#head-menu ul li {
- float:left;
- margin:0;
- margin-left:0.3em;
- font-size:14px;
- margin-bottom:4px;
-}
-#head-menu ul li.selected, #head-menu ul li:hover {
- margin-bottom:0px;
-}
-#head-menu ul li a img {
- height:22px;
- width:22px;
- vertical-align:middle;
-}
-#head-menu ul li a, #head-menu ul li a:link {
- float:left;
- text-decoration:none;
- color:#555;
- background:#eaeaea url(/media/default/img/tab-background.png) 0 100% repeat-x;
- padding:3px 7px 3px 7px;
- border:2px solid #ccc;
- border-bottom:0px solid transparent;
- padding-bottom:3px;
- -moz-border-radius:5px;
- border-radius:5px;
-}
-#head-menu ul li a:hover, #head-menu ul li a:focus {
- color:#111;
- padding-bottom:7px;
- border-bottom:0px none transparent;
- outline:none;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
- -moz-border-radius-bottomright:0px;
- -moz-border-radius-bottomleft:0px;
-}
-#head-menu ul li a:focus {
- margin-bottom:-4px;
-}
-#head-menu ul li.selected a {
- color:#3566A5;
- background:#fff;
- padding-bottom:7px;
- border-bottom:0px none transparent;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
- -moz-border-radius-bottomright:0px;
- -moz-border-radius-bottomleft:0px;
-}
-#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
- color:#111;
-}
-div#head-search-and-login {
- float:right;
- margin:0 1em 0 0;
- background-color:#222;
- padding:7px 7px 5px 5px;
- color:white;
- white-space: nowrap;
- border-bottom-left-radius: 6px;
- border-bottom-right-radius: 6px;
- -moz-border-radius-bottomright:6px;
- -moz-border-radius-bottomleft:6px;
-}
-div#head-search-and-login form {
- display:inline;
- padding:0 3px;
-}
-div#head-search-and-login form input {
- border:2px solid #888;
- background:#eee;
- font-size:14px;
- padding:2px;
- border-radius:3px;
- -moz-border-radius:3px;
-}
-div#head-search-and-login form input:focus {
- background:#fff;
-}
-#head-search {
- font-size:14px;
-}
-#head-username, #head-password {
- width:80px;
- font-size:14px;
-}
-#pageinfo {
- clear:both;
- color:#888;
- padding:0.6em 0;
- margin:0;
-}
-#foot {
- font-style:normal;
- color:#888;
- text-align:center;
-}
-#foot a {
- color:#aaf;
-}
-#foot img {
- vertical-align:middle;
-}
-div.toc {
- border:1px dotted #888;
- background:#f0f0f0;
- margin:1em 0 1em 1em;
- float:right;
- font-size:95%;
-}
-div.toc .tocheader {
- font-weight:bold;
- margin:0.5em 1em;
-}
-div.toc ol {
- margin:1em 0.5em 1em 1em;
- padding:0;
-}
-div.toc ol li {
- margin:0;
- padding:0;
- margin-left:1em;
-}
-div.toc ol ol {
- margin:0.5em 0.5em 0.5em 1em;
- padding:0;
-}
-div.recentchanges table {
- clear:both;
-}
-div#editor-help {
- font-size:90%;
- border:1px dotted #888;
- padding:0ex 1ex 1ex 1ex;
- background:#f7f6f2;
-}
-div#preview {
- margin-top:1em;
-}
-label.block {
- display:block;
- text-align:right;
- font-weight:bold;
-}
-label.simple {
- display:block;
- text-align:left;
- font-weight:normal;
-}
-label.block input.edit {
- width:50%;
-}
-/*fieldset {
- width:300px;
- text-align:center;
- padding:0.5em;
- margin:auto;
-}
-*/
-div.editor {
- margin:0 0 0 0;
-}
-table {
- margin:0.5em 0;
- border-collapse:collapse;
-}
-td {
- padding:0.25em;
- border:1pt solid #ADB9CC;
-}
-td p {
- margin:0;
- padding:0;
-}
-.u {
- text-decoration:underline;
-}
-.footnotes ul {
- padding:0 2em;
- margin:0 0 1em;
-}
-.footnotes li {
- list-style:none;
-}
-.userpref table, .userpref td {
- border:none;
-}
-#message {
- clear:both;
- padding:5px 10px;
- background-color:#eee;
- border-bottom:2px solid #ccc;
-}
-#message p {
- margin:5px 0;
- padding:0;
- font-weight:bold;
-}
-#message div.buttons {
- font-weight:normal;
-}
-.diff {
- width:99%;
-}
-.diff-title {
- background-color:#C0C0C0;
-}
-.searchresult dd span {
- font-weight:bold;
-}
-.boxtext {
- font-family:tahoma, arial, sans-serif;
- font-size:11px;
- color:#000;
- float:none;
- padding:3px 0 0 10px;
-}
-.statusbutton {
- width:32px;
- height:32px;
- float:left;
- margin-left:-32px;
- margin-right:5px;
- opacity:0;
- cursor:pointer
-}
-.dlsize {
- float:left;
- padding-right: 8px;
-}
-.dlspeed {
- float:left;
- padding-right: 8px;
-}
-.package {
- margin-bottom: 10px;
-}
-.packagename {
- font-weight: bold;
-}
-
-.child {
- margin-left: 20px;
-}
-.child_status {
- margin-right: 10px;
-}
-.child_secrow {
- font-size: 10px;
-}
-
-.header, .header th {
- text-align: left;
- font-weight: normal;
- background-color:#ececec;
- -moz-border-radius:5px;
- border-radius:5px;
-}
-.progress_bar {
- background: #0C0;
- height: 5px;
-
-}
-
-.queue {
- border: none
-}
-
-.queue tr td {
- border: none
-}
-
-.header, .header th{
- text-align: left;
- font-weight: normal;
-}
-
-
-.clearer
-{
- clear: both;
- height: 1px;
-}
-
-.left
-{
- float: left;
-}
-
-.right
-{
- float: right;
-}
-
-
-.setfield
-{
- display: table-cell;
-}
-
-ul.tabs li a
-{
- padding: 5px 16px 4px 15px;
- border: none;
- font-weight: bold;
-
- border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
-
-}
-
-
-#tabs span
-{
- display: none;
-}
-
-#tabs span.selected
-{
- display: inline;
-}
-
-#tabsback
-{
- background-color: #525252;
- margin: 2px 0 0;
- padding: 6px 4px 1px 4px;
-
- border-top-right-radius: 30px;
- border-top-left-radius: 3px;
- -moz-border-radius-topright: 30px;
- -moz-border-radius-topleft: 3px;
-}
-ul.tabs
-{
- list-style-type: none;
- margin:0;
- padding: 0 40px 0 0;
-}
-
-ul.tabs li
-{
- display: inline;
- margin-left: 8px;
-}
-
-
-ul.tabs li a
-{
- color: #42454a;
- background-color: #eaeaea;
- border: 1px none #c9c3ba;
- margin: 0;
- text-decoration: none;
-
- outline: 0;
-
- padding: 5px 16px 4px 15px;
- font-weight: bold;
-
- border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
-
-}
-
-ul.tabs li a.selected, ul.tabs li a:hover
-{
- color: #000;
- background-color: white;
-
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- -moz-border-radius-bottomright: 0;
- -moz-border-radius-bottomleft: 0;
-}
-
-ul.tabs li a:hover
-{
- background-color: #f1f4ee;
-}
-
-ul.tabs li a.selected
-{
- font-weight: bold;
- background-color: #525252;
- padding-bottom: 5px;
- color: white;
-}
-
-
-#tabs-body {
- position: relative;
- overflow: hidden;
-}
-
-
-span.tabContent
-{
- border: 2px solid #525252;
- margin: 0;
- padding: 0;
- padding-bottom: 10px;
-}
-
-#tabs-body > span {
- display: none;
-}
-
-#tabs-body > span.active {
- display: block;
-}
-
-.hide
-{
- display: none;
-}
-
-.settable
-{
- margin: 20px;
- border: none;
-}
-.settable td
-{
- border: none;
- margin: 0;
- padding: 5px;
-}
-
-.settable th{
- padding-bottom: 8px;
-}
-
-.settable.wide td , .settable.wide th {
- padding-left: 15px;
- padding-right: 15px;
-}
-
-
-/*settings navbar*/
-ul.nav {
- margin: -30px 0 0;
- padding: 0;
- list-style: none;
- position: absolute;
-}
-
-
-ul.nav li {
- position: relative;
- float: left;
- padding: 5px;
-}
-
-ul.nav > li a {
- background: white;
- -moz-border-radius: 4px 4px 4px 4px;
- border: 1px solid #C9C3BA;
- border-bottom: medium none;
- color: black;
-}
-
-ul.nav ul {
- position: absolute;
- top: 26px;
- left: 10px;
- margin: 0;
- padding: 0;
- list-style: none;
- border: 1px solid #AAA;
- background: #f1f1f1;
- -webkit-box-shadow: 1px 1px 5px #AAA;
- -moz-box-shadow: 1px 1px 5px #AAA;
- box-shadow: 1px 1px 5px #AAA;
- cursor: pointer;
-}
-
-ul.nav .open {
- display: block;
-}
-
-ul.nav .close {
- display: none;
-}
-
-ul.nav ul li {
- float: none;
- padding: 0;
-}
-
-ul.nav ul li a {
- width: 130px;
- background: #f1f1f1;
- padding: 3px;
- display: block;
- font-weight: normal;
-}
-
-ul.nav ul li a:hover {
- background: #CDCDCD;
-}
-
-ul.nav ul ul {
- left: 137px;
- top: 0;
-}
-
-.purr-wrapper{
- margin:10px;
-}
-
-/*Purr alert styles*/
-
-.purr-alert{
- margin-bottom:10px;
- padding:10px;
- background:#000;
- font-size:13px;
- font-weight:bold;
- color:#FFF;
- -moz-border-radius:5px;
- -webkit-border-radius:5px;
- /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
- width:300px;
-}
-.purr-alert.error{
- color:#F55;
- padding-left:30px;
- background:url(/media/default/img/error.png) no-repeat #000 7px 10px;
- width:280px;
-}
-.purr-alert.success{
- color:#5F5;
- padding-left:30px;
- background:url(/media/default/img/success.png) no-repeat #000 7px 10px;
- width:280px;
-}
-.purr-alert.notice{
- color:#99F;
- padding-left:30px;
- background:url(/media/default/img//notice.png) no-repeat #000 7px 10px;
- width:280px;
-}
-
-table.system {
- border: none;
- margin-left: 10px;
-}
-
-table.system td {
- border: none
-}
-
-table.system tr > td:first-child {
- font-weight: bold;
- padding-right: 10px;
-} \ No newline at end of file
diff --git a/module/web/media/default/css/log.css b/module/web/media/default/css/log.css
deleted file mode 100644
index 73786bfb4..000000000
--- a/module/web/media/default/css/log.css
+++ /dev/null
@@ -1,72 +0,0 @@
-
-html, body, #content
-{
- height: 100%;
-}
-#body-wrapper
-{
- height: 70%;
-}
-.logdiv
-{
- height: 90%;
- width: 100%;
- overflow: auto;
- border: 2px solid #CCC;
- outline: 1px solid #666;
- background-color: #FFE;
- margin-right: auto;
- margin-left: auto;
-}
-.logform
-{
- display: table;
- margin: 0 auto 0 auto;
- padding-top: 5px;
-}
-.logtable
-{
-
- margin: 0px;
-}
-.logtable td
-{
- border: none;
- white-space: nowrap;
-
-
- font-family: monospace;
- font-size: 16px;
- margin: 0px;
- padding: 0px 10px 0px 10px;
- line-height: 110%;
-}
-td.logline
-{
- background-color: #EEE;
- text-align:right;
- padding: 0px 5px 0px 5px;
-}
-td.loglevel
-{
- text-align:right;
-}
-.logperpage
-{
- float: right;
- padding-bottom: 8px;
-}
-.logpaginator
-{
- float: left;
- padding-top: 5px;
-}
-.logpaginator a
-{
- padding: 0px 8px 0px 8px;
-}
-.logwarn
-{
- text-align: center;
- color: red;
-} \ No newline at end of file
diff --git a/module/web/media/default/css/pathchooser.css b/module/web/media/default/css/pathchooser.css
deleted file mode 100644
index 894cc335e..000000000
--- a/module/web/media/default/css/pathchooser.css
+++ /dev/null
@@ -1,68 +0,0 @@
-table {
- width: 90%;
- border: 1px dotted #888888;
- font-family: sans-serif;
- font-size: 10pt;
-}
-
-th {
- background-color: #525252;
- color: #E0E0E0;
-}
-
-table, tr, td {
- background-color: #F0F0F0;
-}
-
-a, a:visited {
- text-decoration: none;
- font-weight: bold;
-}
-
-#paths {
- width: 90%;
- text-align: left;
-}
-
-.file_directory {
- color: #c0c0c0;
-}
-.path_directory {
- color: #3c3c3c;
-}
-.file_file {
- color: #3c3c3c;
-}
-.path_file {
- color: #c0c0c0;
-}
-
-.parentdir {
- color: #000000;
- font-size: 10pt;
-}
-.name {
- text-align: left;
-}
-.size {
- text-align: right;
-}
-.type {
- text-align: left;
-}
-.mtime {
- text-align: center;
-}
-
-.path_abs_rel {
- color: #3c3c3c;
- text-decoration: none;
- font-weight: bold;
- font-family: sans-serif;
- font-size: 10pt;
-}
-
-.path_abs_rel a {
- color: #3c3c3c;
- font-style: italic;
-}
diff --git a/module/web/media/img/favicon.ico b/module/web/media/img/favicon.ico
deleted file mode 100644
index 58b1f4b89..000000000
--- a/module/web/media/img/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/module/web/media/js/MooDialog_static.js b/module/web/media/js/MooDialog_static.js
deleted file mode 100644
index d497d3d57..000000000
--- a/module/web/media/js/MooDialog_static.js
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
----
-
-name: Overlay
-
-authors:
- - David Walsh (http://davidwalsh.name)
-
-license:
- - MIT-style license
-
-requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
-
-provides:
- - Overlay
-...
-*/
-
-var Overlay = new Class({
-
- Implements: [Options, Events],
-
- options: {
- id: 'overlay',
- color: '#000',
- duration: 500,
- opacity: 0.5,
- zIndex: 5000/*,
- onClick: $empty,
- onClose: $empty,
- onHide: $empty,
- onOpen: $empty,
- onShow: $empty
- */
- },
-
- initialize: function(container, options){
- this.setOptions(options);
- this.container = document.id(container);
-
- this.bound = {
- 'window': {
- resize: this.resize.bind(this),
- scroll: this.scroll.bind(this)
- },
- overlayClick: this.overlayClick.bind(this),
- tweenStart: this.tweenStart.bind(this),
- tweenComplete: this.tweenComplete.bind(this)
- };
-
- this.build().attach();
- },
-
- build: function(){
- this.overlay = new Element('div', {
- id: this.options.id,
- opacity: 0,
- styles: {
- position: (Browser.ie6) ? 'absolute' : 'fixed',
- background: this.options.color,
- left: 0,
- top: 0,
- 'z-index': this.options.zIndex
- }
- }).inject(this.container);
- this.tween = new Fx.Tween(this.overlay, {
- duration: this.options.duration,
- link: 'cancel',
- property: 'opacity'
- });
- this.tween.set('opacity', 0)
- return this;
- }.protect(),
-
- attach: function(){
- window.addEvents(this.bound.window);
- this.overlay.addEvent('click', this.bound.overlayClick);
- this.tween.addEvents({
- onStart: this.bound.tweenStart,
- onComplete: this.bound.tweenComplete
- });
- return this;
- },
-
- detach: function(){
- var args = Array.prototype.slice.call(arguments);
- args.each(function(item){
- if(item == 'window') window.removeEvents(this.bound.window);
- if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
- }, this);
- return this;
- },
-
- overlayClick: function(){
- this.fireEvent('click');
- return this;
- },
-
- tweenStart: function(){
- this.overlay.setStyles({
- width: '100%',
- height: this.container.getScrollSize().y
- });
- return this;
- },
-
- tweenComplete: function(){
- this.fireEvent(this.overlay.get('opacity') == this.options.opacity ? 'show' : 'hide');
- return this;
- },
-
- open: function(){
- this.fireEvent('open');
- this.tween.set('display', 'block');
- this.tween.start(this.options.opacity);
- return this;
- },
-
- close: function(){
- this.fireEvent('close');
- this.tween.start(0).chain(function(){
- this.tween.set('display', 'none');
- }.bind(this));
- return this;
- },
-
- resize: function(){
- this.fireEvent('resize');
- this.overlay.setStyle('height', this.container.getScrollSize().y);
- return this;
- },
-
- scroll: function(){
- this.fireEvent('scroll');
- if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
- return this;
- }
-
-});
-/*
----
-name: MooDialog
-description: The base class of MooDialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Core/Class, Core/Element, Core/Element.Styles, Core/Element.Event]
-provides: [MooDialog, Element.MooDialog]
-...
-*/
-
-
-var MooDialog = new Class({
-
- Implements: [Options, Events],
-
- options: {
- 'class': 'MooDialog',
- title: null,
- scroll: true, // IE
- forceScroll: false,
- useEscKey: true,
- destroyOnHide: true,
- autoOpen: true,
- closeButton: true,
- onInitialize: function(){
- this.wrapper.setStyle('display', 'none');
- },
- onBeforeOpen: function(){
- this.wrapper.setStyle('display', 'block');
- this.fireEvent('show');
- },
- onBeforeClose: function(){
- this.wrapper.setStyle('display', 'none');
- this.fireEvent('hide');
- }/*,
- onOpen: function(){},
- onClose: function(){},
- onShow: function(){},
- onHide: function(){}*/
- },
-
- initialize: function(options){
- this.setOptions(options);
- this.options.inject = this.options.inject || document.body;
- options = this.options;
-
- var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
- this.content = new Element('div.content').inject(wrapper);
- wrapper.setStyle('display', 'none');
-
- if (options.title){
- this.title = new Element('div.title').set('text', options.title).inject(wrapper);
- wrapper.addClass('MooDialogTitle');
- }
-
- if (options.closeButton){
- this.closeButton = new Element('a.close', {
- events: {click: this.close.bind(this)}
- }).inject(wrapper);
- }
-
-
- /*<ie6>*/// IE 6 scroll
- if ((options.scroll && Browser.ie6) || options.forceScroll){
- wrapper.setStyle('position', 'absolute');
- var position = wrapper.getPosition(options.inject);
- window.addEvent('scroll', function(){
- var scroll = document.getScroll();
- wrapper.setPosition({
- x: position.x + scroll.x,
- y: position.y + scroll.y
- });
- });
- }
- /*</ie6>*/
-
- if (options.useEscKey){
- // Add event for the esc key
- document.addEvent('keydown', function(e){
- if (e.key == 'esc') this.close();
- }.bind(this));
- }
-
- this.addEvent('hide', function(){
- if (options.destroyOnHide) this.destroy();
- }.bind(this));
-
- this.fireEvent('initialize', wrapper);
- },
-
- setContent: function(){
- var content = Array.from(arguments);
- if (content.length == 1) content = content[0];
-
- this.content.empty();
-
- var type = typeOf(content);
- if (['string', 'number'].contains(type)) this.content.set('text', content);
- else this.content.adopt(content);
-
- return this;
- },
-
- open: function(){
- this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
- this.opened = true;
- return this;
- },
-
- close: function(){
- this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
- this.opened = false;
- return this;
- },
-
- destroy: function(){
- this.wrapper.destroy();
- },
-
- toElement: function(){
- return this.wrapper;
- }
-
-});
-
-
-Element.implement({
-
- MooDialog: function(options){
- this.store('MooDialog',
- new MooDialog(options).setContent(this).open()
- );
- return this;
- }
-
-});
-/*
----
-name: MooDialog.Fx
-description: Overwrite the default events so the Dialogs are using Fx on open and close
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Cores/Fx.Tween, Overlay]
-provides: MooDialog.Fx
-...
-*/
-
-
-MooDialog.implement('options', {
-
- duration: 400,
- closeOnOverlayClick: true,
-
- onInitialize: function(wrapper){
- this.fx = new Fx.Tween(wrapper, {
- property: 'opacity',
- duration: this.options.duration
- }).set(0);
- this.overlay = new Overlay(this.options.inject, {
- duration: this.options.duration
- });
- if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
- },
-
- onBeforeOpen: function(wrapper){
- this.overlay.open();
- wrapper.setStyle('display', 'block');
- this.fx.start(1).chain(function(){
- this.fireEvent('show');
- }.bind(this));
- },
-
- onBeforeClose: function(wrapper){
- this.overlay.close();
- this.fx.start(0).chain(function(){
- this.fireEvent('hide');
- wrapper.setStyle('display', 'none');
- }.bind(this));
- }
-
-});
-/*
----
-name: MooDialog.Confirm
-description: Creates an Confirm Dialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: MooDialog
-provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
-...
-*/
-
-
-MooDialog.Confirm = new Class({
-
- Extends: MooDialog,
-
- options: {
- okText: 'Ok',
- cancelText: 'Cancel',
- focus: true,
- textPClass: 'MooDialogConfirm'
- },
-
- initialize: function(msg, fn, fn1, options){
- this.parent(options);
- var emptyFn = function(){},
- self = this;
-
- var buttons = [
- {fn: fn || emptyFn, txt: this.options.okText},
- {fn: fn1 || emptyFn, txt: this.options.cancelText}
- ].map(function(button){
- return new Element('input[type=button]', {
- events: {
- click: function(){
- button.fn();
- self.close();
- }
- },
- value: button.txt
- });
- });
-
- this.setContent(
- new Element('p.' + this.options.textPClass, {text: msg}),
- new Element('div.buttons').adopt(buttons)
- );
- if (this.options.autoOpen) this.open();
-
- if(this.options.focus) this.addEvent('show', function(){
- buttons[1].focus();
- });
-
- }
-});
-
-
-Element.implement({
-
- confirmLinkClick: function(msg, options){
- this.addEvent('click', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- location.href = this.get('href');
- }.bind(this), null, options)
- });
- return this;
- },
-
- confirmFormSubmit: function(msg, options){
- this.addEvent('submit', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- this.submit();
- }.bind(this), null, options)
- }.bind(this));
- return this;
- }
-
-});
diff --git a/module/web/media/js/MooDropMenu_static.js b/module/web/media/js/MooDropMenu_static.js
deleted file mode 100644
index b9cd8cc10..000000000
--- a/module/web/media/js/MooDropMenu_static.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
----
-description: This provides a simple Drop Down menu with infinit levels
-
-license: MIT-style
-
-authors:
-- Arian Stolwijk
-
-requires:
- - Core/Class.Extras
- - Core/Element.Event
- - Core/Selectors
-
-provides: [MooDropMenu, Element.MooDropMenu]
-
-...
-*/
-
-var MooDropMenu = new Class({
-
- Implements: [Options, Events],
-
- options: {
- onOpen: function(el){
- el.removeClass('close').addClass('open');
- },
- onClose: function(el){
- el.removeClass('open').addClass('close');
- },
- onInitialize: function(el){
- el.removeClass('open').addClass('close');
- },
- mouseoutDelay: 200,
- mouseoverDelay: 0,
- listSelector: 'ul',
- itemSelector: 'li'
- },
-
- initialize: function(menu, options, level){
- this.setOptions(options);
- options = this.options;
-
- var menu = this.menu = document.id(menu);
-
- menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
-
- this.fireEvent('initialize', el);
-
- var parent = el.getParent(options.itemSelector),
- timer;
-
- parent.addEvents({
-
- 'mouseenter': function(){
- parent.store('DropDownOpen', true);
-
- clearTimeout(timer);
- if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
- else this.fireEvent('open', el);
-
- }.bind(this),
-
- 'mouseleave': function(){
- parent.store('DropDownOpen', false);
-
- clearTimeout(timer);
- timer = (function(){
- if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
- }).delay(options.mouseoutDelay, this);
-
- }.bind(this)
- });
-
- }, this);
- },
-
- toElement: function(){
- return this.menu
- }
-
-});
-
-/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
-Element.implement({
- MooDropMenu: function(options){
- return this.store('MooDropMenu', new MooDropMenu(this, options));
- }
-});
diff --git a/module/web/media/js/mootools-core-1.4.1.js b/module/web/media/js/mootools-core-1.4.1.js
deleted file mode 100644
index 835b4bbe2..000000000
--- a/module/web/media/js/mootools-core-1.4.1.js
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
----
-MooTools: the javascript framework
-
-web build:
- - http://mootools.net/core/76bf47062d6c1983d66ce47ad66aa0e0
-
-packager build:
- - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
-
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-(function(){this.MooTools={version:"1.4.1",build:"d1fb25710e3c5482a219ab9dc675a4e0ad2176b6"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family){return i.$family();
-}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if(i.callee){return"arguments";
-}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
-while(s){if(s===i){return true;}s=s.parent;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];
-}f.prototype.overloadSetter=function(s){var i=this;return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);
-}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;
-return function(u){var v,t;if(s||typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);
-}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;
-}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;};};Array.from=function(i){if(i==null){return[];
-}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);return isFinite(i)?i:null;
-};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;return this;
-}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
-}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
-};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
-u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
-});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
-}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,w,u){var t=(w!=Object),A=w.prototype;if(t){w=new a(s,w);
-}for(var x=0,v=u.length;x<v;x++){var B=u[x],z=w[B],y=A[B];if(z){z.protect();}if(t&&y){delete A[B];A[B]=y.protect();}}if(t){w.implement(A);}return d;};d("String",String,["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
-Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
-}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
-}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
-return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
-while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
-}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
-y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
-v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
-});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
-b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,e){var c=[];for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&d.call(e,this[b],b,this)){c.push(this[b]);
-}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);
-for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;
-}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);return this.map(function(c){return c[a].apply(c,b);
-});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];}return d;},link:function(c){var a={};for(var e=0,b=this.length;
-e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;},append:function(a){this.push.apply(this,a);
-return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
-},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
-},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
-for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
-}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
-}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";
-}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});String.implement({test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
-},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:String(this).indexOf(a)>-1;},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");
-},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
-});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
-});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
-},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
-return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
-}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
-return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
-}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
-};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
-b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
-},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
-g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
-if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
-},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
-for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
-}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
-}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
-}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
-}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
-}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
-break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var k=this.document;var i=k.window=this;
-var b=1;this.$uid=(i.ActiveXObject)?function(e){return(e.uid||(e.uid=[b++]))[0];}:function(e){return e.uid||(e.uid=b++);};$uid(i);$uid(k);var a=navigator.userAgent.toLowerCase(),c=navigator.platform.toLowerCase(),j=a.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],f=j[1]=="ie"&&k.documentMode;
-var o=this.Browser={extend:Function.prototype.extend,name:(j[1]=="version")?j[3]:j[1],version:f||parseFloat((j[1]=="opera"&&j[4])?j[4]:j[2]),Platform:{name:a.match(/ip(?:ad|od|hone)/)?"ios":(a.match(/(?:webos|android)/)||c.match(/mac|win|linux/)||["other"])[0]},Features:{xpath:!!(k.evaluate),air:!!(i.runtime),query:!!(k.querySelector),json:!!(i.JSON)},Plugins:{}};
-o[o.name]=true;o[o.name+parseInt(o.version,10)]=true;o.Platform[o.Platform.name]=true;o.Request=(function(){var q=function(){return new XMLHttpRequest();
-};var p=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");};return Function.attempt(function(){q();
-return q;},function(){p();return p;},function(){e();return e;});})();o.Features.xhr=!!(o.Request);var h=(Function.attempt(function(){return navigator.plugins["Shockwave Flash"].description;
-},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);o.Plugins.Flash={version:Number(h[0]||"0."+h[1])||0,build:Number(h[2])||0};
-o.exec=function(p){if(!p){return p;}if(i.execScript){i.execScript(p);}else{var e=k.createElement("script");e.setAttribute("type","text/javascript");e.text=p;
-k.head.appendChild(e);k.head.removeChild(e);}return p;};String.implement("stripScripts",function(p){var e="";var q=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(r,s){e+=s+"\n";
-return"";});if(p===true){o.exec(e);}else{if(typeOf(p)=="function"){p(e,q);}}return q;});o.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
-this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,p){i[e]=p;});this.Document=k.$constructor=new Type("Document",function(){});
-k.$family=Function.from("document").hide();Document.mirror(function(e,p){k[e]=p;});k.html=k.documentElement;if(!k.head){k.head=k.getElementsByTagName("head")[0];
-}if(k.execCommand){try{k.execCommand("BackgroundImageCache",false,true);}catch(g){}}if(this.attachEvent&&!this.addEventListener){var d=function(){this.detachEvent("onunload",d);
-k.head=k.html=k.window=null;};this.attachEvent("onunload",d);}var m=Array.from;try{m(k.html.childNodes);}catch(g){Array.from=function(p){if(typeof p!="string"&&Type.isEnumerable(p)&&typeOf(p)!="array"){var e=p.length,q=new Array(e);
-while(e--){q[e]=p[e];}return q;}return m(p);};var l=Array.prototype,n=l.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var p=l[e];
-Array[e]=function(q){return p.apply(Array.from(q),n.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
-}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
-var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
-this.key=b[d];if(i=="keydown"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
-}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
-this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
-if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
-while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
-this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
-this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
-},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
-}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
-})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
-}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
-g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
-}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
-};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
-break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
-}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
-return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
-}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
-return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
-for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
-return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
-return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
-this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
-},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
-}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
-},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
-}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
-if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
-(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
-var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
-return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
-}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
-o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
-};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
-function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
-if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
-}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
-}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
-}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
-break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
-case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
-};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
-};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
-k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
-};k.setDocument=function(x){var u=x.nodeType;if(u==9){}else{if(u){x=x.ownerDocument;}else{if(x.navigator){x=x.document;}else{return;}}}if(this.document===x){return;
-}this.document=x;var z=x.documentElement,v=this.getUIDXML(z),p=m[v],B;if(p){for(B in p){this[B]=p[B];}return;}p=m[v]={};p.root=z;p.isXMLDocument=this.isXML(x);
-p.brokenStarGEBTN=p.starSelectsClosedQSA=p.idGetsName=p.brokenMixedCaseQSA=p.brokenGEBCN=p.brokenCheckedQSA=p.brokenEmptyAttributeQSA=p.isHTMLDocument=p.nativeMatchesSelector=false;
-var n,o,y,r,s;var t,c="slick_uniqueid";var A=x.createElement("div");var q=x.body||x.getElementsByTagName("body")[0]||z;q.appendChild(A);try{A.innerHTML='<a id="'+c+'"></a>';
-p.isHTMLDocument=!!x.getElementById(c);}catch(w){}if(p.isHTMLDocument){A.style.display="none";A.appendChild(x.createComment(""));o=(A.getElementsByTagName("*").length>1);
-try{A.innerHTML="foo</foo>";t=A.getElementsByTagName("*");n=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");}catch(w){}p.brokenStarGEBTN=o||n;try{A.innerHTML='<a name="'+c+'"></a><b id="'+c+'"></b>';
-p.idGetsName=x.getElementById(c)===A.firstChild;}catch(w){}if(A.getElementsByClassName){try{A.innerHTML='<a class="f"></a><a class="b"></a>';A.getElementsByClassName("b").length;
-A.firstChild.className="b";r=(A.getElementsByClassName("b").length!=2);}catch(w){}try{A.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(A.getElementsByClassName("a").length!=2);
-}catch(w){}p.brokenGEBCN=r||y;}if(A.querySelectorAll){try{A.innerHTML="foo</foo>";t=A.querySelectorAll("*");p.starSelectsClosedQSA=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");
-}catch(w){}try{A.innerHTML='<a class="MiX"></a>';p.brokenMixedCaseQSA=!A.querySelectorAll(".MiX").length;}catch(w){}try{A.innerHTML='<select><option selected="selected">a</option></select>';
-p.brokenCheckedQSA=(A.querySelectorAll(":checked").length==0);}catch(w){}try{A.innerHTML='<a class=""></a>';p.brokenEmptyAttributeQSA=(A.querySelectorAll('[class*=""]').length!=0);
-}catch(w){}}try{A.innerHTML='<form action="s"><input id="action"/></form>';s=(A.firstChild.getAttribute("action")!="s");}catch(w){}p.nativeMatchesSelector=z.matchesSelector||z.mozMatchesSelector||z.webkitMatchesSelector;
-if(p.nativeMatchesSelector){try{p.nativeMatchesSelector.call(z,":slick");p.nativeMatchesSelector=null;}catch(w){}}}try{z.slick_expando=1;delete z.slick_expando;
-p.getUID=this.getUIDHTML;}catch(w){p.getUID=this.getUIDXML;}q.removeChild(A);A=t=q=null;p.getAttribute=(p.isHTMLDocument&&s)?function(E,C){var F=this.attributeGetters[C];
-if(F){return F.call(E);}var D=E.getAttributeNode(C);return(D)?D.nodeValue:null;}:function(D,C){var E=this.attributeGetters[C];return(E)?E.call(D):D.getAttribute(C);
-};p.hasAttribute=(z&&this.isNativeCode(z.hasAttribute))?function(D,C){return D.hasAttribute(C);}:function(D,C){D=D.getAttributeNode(C);return !!(D&&(D.specified||D.nodeValue));
-};p.contains=(z&&this.isNativeCode(z.contains))?function(C,D){return C.contains(D);}:(z&&z.compareDocumentPosition)?function(C,D){return C===D||!!(C.compareDocumentPosition(D)&16);
-}:function(C,D){if(D){do{if(D===C){return true;}}while((D=D.parentNode));}return false;};p.documentSorter=(z.compareDocumentPosition)?function(D,C){if(!D.compareDocumentPosition||!C.compareDocumentPosition){return 0;
-}return D.compareDocumentPosition(C)&4?-1:D===C?0:1;}:("sourceIndex" in z)?function(D,C){if(!D.sourceIndex||!C.sourceIndex){return 0;}return D.sourceIndex-C.sourceIndex;
-}:(x.createRange)?function(F,D){if(!F.ownerDocument||!D.ownerDocument){return 0;}var E=F.ownerDocument.createRange(),C=D.ownerDocument.createRange();E.setStart(F,0);
-E.setEnd(F,0);C.setStart(D,0);C.setEnd(D,0);return E.compareBoundaryPoints(Range.START_TO_END,C);}:null;z=null;for(B in p){this[B]=p[B];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};
-k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);
-if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);
-simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;}E=U.getElementsByTagName(v);if(s){return E[0]||null;
-}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;}A=U.getElementById(v);
-if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
-}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
-if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
-for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
-}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
-}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
-}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
-A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
-}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
-return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
-if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
-if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
-this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
-}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
-}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
-if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
-}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
-if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
-return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
-}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
-}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
-if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
-}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
-k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
-}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
-if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
-}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
-}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
-if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=o.getAttribute("class")||o.className;
-if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
-}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
-if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
-if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
-}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
-if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
-}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
-}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
-if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
-this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
-}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
-break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
-return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
-},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+c+1);
-},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
-while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
-}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
-}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
-},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"class":function(){return this.getAttribute("class")||this.className;
-},"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");
-},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;
-},type:function(){return this.getAttribute("type");},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;
-}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});e.version="1.1.6";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);
-};e.contains=function(c,n){k.setDocument(c);return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);
-return k.hasAttribute(n,c);};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
-return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
-};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
-return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
-var Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];
-b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;f<c;f++){a=d[f];if(g[a.key]!=null){continue;
-}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
-}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;
-}var b={};b[a]=function(){var h=[],e=arguments,j=true;for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");
-}return(j)?new Elements(h):h;};Elements.implement(b);});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$family":Function.from("element").hide()};
-Element.mirror(function(a,b){Element.Prototype[a]=b;});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
-}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
-b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
-for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
-}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
-b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
-c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
-for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
-b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var g=Array.prototype.splice,b={"0":0,"1":1,length:2};
-g.call(b,1,1);if(b[1]==1){Elements.implement("splice",function(){var h=this.length;var e=g.apply(this,arguments);while(h>=this.length){delete this[h--];
-}return e;}.protect());}Elements.implement(Array.prototype);Array.mirror(Elements);var f;try{var a=document.createElement("<input name=x>");f=(a.name=="x");
-}catch(c){}var d=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,h){if(h&&h.checked!=null){h.defaultChecked=h.checked;
-}if(f&&h){e="<"+e;if(h.name){e+=' name="'+d(h.name)+'"';}if(h.type){e+=' type="'+d(h.type)+'"';}e+=">";delete h.name;delete h.type;}return this.id(this.createElement(e)).set(h);
-}});})();Document.implement({newTextNode:function(a){return this.createTextNode(a);},getDocument:function(){return this;},getWindow:function(){return this.window;
-},id:(function(){var a={string:function(d,c,b){d=Slick.find(b,"#"+d.replace(/(\W)/g,"\\$1"));return(d)?a.element(d,c):null;},element:function(b,c){$uid(b);
-if(!c&&!b.$family&&!(/^(?:object|embed)$/i).test(b.tagName)){Object.append(b,Element.Prototype);}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d);
-}return null;}};a.textnode=a.whitespace=a.window=a.document=function(b){return b;};return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=typeOf(c);
-return(a[b])?a[b](c,e,d||document):null;};})()});if(window.$==null){Window.implement("$",function(a,b){return document.id(a,b,this.document);});}Window.implement({getDocument:function(){return this.document;
-},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(a){return Slick.search(this,a,new Elements);},getElement:function(a){return document.id(Slick.find(this,a));
-}});var contains={contains:function(a){return Slick.contains(this,a);}};if(!document.contains){Document.implement(contains);}if(!document.createElement("div").contains){Element.implement(contains);
-}var injectCombinator=function(d,c){if(!d){return c;}d=Object.clone(Slick.parse(d));var b=d.expressions;for(var a=b.length;a--;){b[a][0].combinator=c;}return d;
-};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(a,b){Element.implement(b,function(c){return this.getElement(injectCombinator(c,a));
-});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(a,b){Element.implement(b,function(c){return this.getElements(injectCombinator(c,a));
-});});Element.implement({getFirst:function(a){return document.id(Slick.search(this,injectCombinator(a,">"))[0]);},getLast:function(a){return document.id(Slick.search(this,injectCombinator(a,">")).getLast());
-},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(a){return document.id(Slick.find(this,"#"+(""+a).replace(/(\W)/g,"\\$1")));
-},match:function(a){return !a||Slick.match(this,a);}});if(window.$$==null){Window.implement("$$",function(a){if(arguments.length==1){if(typeof a=="string"){return Slick.search(this.document,a,new Elements);
-}else{if(Type.isEnumerable(a)){return new Elements(a);}}}return new Elements(arguments);});}(function(){var b={before:function(n,m){var o=m.parentNode;
-if(o){o.insertBefore(n,m);}},after:function(n,m){var o=m.parentNode;if(o){o.insertBefore(n,m.nextSibling);}},bottom:function(n,m){m.appendChild(n);},top:function(n,m){m.insertBefore(n,m.firstChild);
-}};b.inside=b.bottom;var k={},d={};var i={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","readOnly","rowSpan","tabIndex","useMap"],function(m){i[m.toLowerCase()]=m;
-});Object.append(i,{html:"innerHTML",text:(function(){var m=document.createElement("div");return(m.textContent==null)?"innerText":"textContent";})()});
-Object.forEach(i,function(n,m){d[m]=function(o,p){o[n]=p;};k[m]=function(o){return o[n];};});var a=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
-var h={};Array.forEach(a,function(m){var n=m.toLowerCase();h[n]=m;d[n]=function(o,p){o[m]=!!p;};k[n]=function(o){return !!o[m];};});Object.append(d,{"class":function(m,n){("className" in m)?m.className=n:m.setAttribute("class",n);
-},"for":function(m,n){("htmlFor" in m)?m.htmlFor=n:m.setAttribute("for",n);},style:function(m,n){(m.style)?m.style.cssText=n:m.setAttribute("style",n);
-}});Element.implement({setProperty:function(n,o){var m=n.toLowerCase();if(o==null){if(!h[m]){this.removeAttribute(n);return this;}o=false;}var p=d[m];if(p){p(this,o);
-}else{this.setAttribute(n,o);}return this;},setProperties:function(m){for(var n in m){this.setProperty(n,m[n]);}return this;},getProperty:function(o){var n=k[o.toLowerCase()];
-if(n){return n(this);}var m=Slick.getAttribute(this,o);return(!m&&!Slick.hasAttribute(this,o))?null:m;},getProperties:function(){var m=Array.from(arguments);
-return m.map(this.getProperty,this).associate(m);},removeProperty:function(m){return this.setProperty(m,null);},removeProperties:function(){Array.each(arguments,this.removeProperty,this);
-return this;},set:function(o,n){var m=Element.Properties[o];(m&&m.set)?m.set.call(this,n):this.setProperty(o,n);}.overloadSetter(),get:function(n){var m=Element.Properties[n];
-return(m&&m.get)?m.get.apply(this):this.getProperty(n);}.overloadGetter(),erase:function(n){var m=Element.Properties[n];(m&&m.erase)?m.erase.apply(this):this.removeProperty(n);
-return this;},hasClass:function(m){return this.className.clean().contains(m," ");},addClass:function(m){if(!this.hasClass(m)){this.className=(this.className+" "+m).clean();
-}return this;},removeClass:function(m){this.className=this.className.replace(new RegExp("(^|\\s)"+m+"(?:\\s|$)"),"$1");return this;},toggleClass:function(m,n){if(n==null){n=!this.hasClass(m);
-}return(n)?this.addClass(m):this.removeClass(m);},adopt:function(){var p=this,m,r=Array.flatten(arguments),q=r.length;if(q>1){p=m=document.createDocumentFragment();
-}for(var o=0;o<q;o++){var n=document.id(r[o],true);if(n){p.appendChild(n);}}if(m){this.appendChild(m);}return this;},appendText:function(n,m){return this.grab(this.getDocument().newTextNode(n),m);
-},grab:function(n,m){b[m||"bottom"](document.id(n,true),this);return this;},inject:function(n,m){b[m||"bottom"](this,document.id(n,true));return this;},replaces:function(m){m=document.id(m,true);
-m.parentNode.replaceChild(this,m);return this;},wraps:function(n,m){n=document.id(n,true);return this.replaces(n).grab(n,m);},getSelected:function(){this.selectedIndex;
-return new Elements(Array.from(this.options).filter(function(m){return m.selected;}));},toQueryString:function(){var m=[];this.getElements("input, select, textarea").each(function(o){var n=o.type;
-if(!o.name||o.disabled||n=="submit"||n=="reset"||n=="file"||n=="image"){return;}var p=(o.get("tag")=="select")?o.getSelected().map(function(q){return document.id(q).get("value");
-}):((n=="radio"||n=="checkbox")&&!o.checked)?null:o.get("value");Array.from(p).each(function(q){if(typeof q!="undefined"){m.push(encodeURIComponent(o.name)+"="+encodeURIComponent(q));
-}});});return m.join("&");}});var j={},e={};var c=function(m){return(e[m]||(e[m]={}));};var g=function(n){var m=n.uid;if(n.removeEvents){n.removeEvents();
-}if(n.clearAttributes){n.clearAttributes();}if(m!=null){delete j[m];delete e[m];}return n;};var l={input:"checked",option:"selected",textarea:"value"};
-Element.implement({destroy:function(){var m=g(this).getElementsByTagName("*");Array.each(m,g);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
-return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(r,p){r=r!==false;var w=this.cloneNode(r),o=[w],q=[this],u;
-if(r){o.append(Array.from(w.getElementsByTagName("*")));q.append(Array.from(this.getElementsByTagName("*")));}for(u=o.length;u--;){var s=o[u],v=q[u];if(!p){s.removeAttribute("id");
-}if(s.clearAttributes){s.clearAttributes();s.mergeAttributes(v);s.removeAttribute("uid");if(s.options){var z=s.options,m=v.options;for(var t=z.length;t--;
-){z[t].selected=m[t].selected;}}}var n=l[v.tagName.toLowerCase()];if(n&&v[n]){s[n]=v[n];}}if(Browser.ie){var x=w.getElementsByTagName("object"),y=this.getElementsByTagName("object");
-for(u=x.length;u--;){x[u].outerHTML=y[u].outerHTML;}}return document.id(w);}});[Element,Window,Document].invoke("implement",{addListener:function(p,o){if(p=="unload"){var m=o,n=this;
-o=function(){n.removeListener("unload",o);m();};}else{j[$uid(this)]=this;}if(this.addEventListener){this.addEventListener(p,o,!!arguments[2]);}else{this.attachEvent("on"+p,o);
-}return this;},removeListener:function(n,m){if(this.removeEventListener){this.removeEventListener(n,m,!!arguments[2]);}else{this.detachEvent("on"+n,m);
-}return this;},retrieve:function(n,m){var p=c($uid(this)),o=p[n];if(m!=null&&o==null){o=p[n]=m;}return o!=null?o:null;},store:function(n,m){var o=c($uid(this));
-o[n]=m;return this;},eliminate:function(m){var n=c($uid(this));delete n[m];return this;}});if(window.attachEvent&&!window.addEventListener){window.addListener("unload",function(){Object.each(j,g);
-if(window.CollectGarbage){CollectGarbage();}});}Element.Properties={};Element.Properties.style={set:function(m){this.style.cssText=m;},get:function(){return this.style.cssText;
-},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var s=Function.attempt(function(){var u=document.createElement("table");
-u.innerHTML="<tr><td></td></tr>";});var t=document.createElement("div");var o={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
-o.thead=o.tfoot=o.tbody;t.innerHTML="<nav></nav>";var n=t.childNodes.length==1;if(!n){var q="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),p=document.createDocumentFragment(),m=q.length;
-while(m--){p.createElement(q[m]);}p.appendChild(t);}var r={set:function(v){if(typeOf(v)=="array"){v=v.join("");}var w=(!s&&o[this.get("tag")]);if(!w&&!n){w=[0,"",""];
-}if(w){var x=t;x.innerHTML=w[1]+v+w[2];for(var u=w[0];u--;){x=x.firstChild;}this.empty().adopt(x.childNodes);}else{this.innerHTML=v;}}};r.erase=r.set;return r;
-})();var f=document.createElement("form");f.innerHTML="<select><option>s</option></select>";if(f.firstChild.value!="s"){Element.Properties.value={set:function(r){var n=this.get("tag");
-if(n!="select"){return this.setProperty("value",r);}var o=this.getElements("option");for(var p=0;p<o.length;p++){var q=o[p],m=q.getAttributeNode("value"),s=(m&&m.specified)?q.value:q.get("text");
-if(s==r){return q.selected=true;}}},get:function(){var o=this,n=o.get("tag");if(n!="select"&&n!="option"){return this.getProperty("value");}if(n=="select"&&!(o=o.getSelected()[0])){return"";
-}var m=o.getAttributeNode("value");return(m&&m.specified)?o.value:o.get("text");}};}})();(function(){var f=document.html;Element.Properties.styles={set:function(i){this.setStyles(i);
-}};var h=(f.style.opacity!=null),a=(f.style.filter!=null),g=/alpha\(opacity=([\d.]+)\)/i;var b=function(j,i){j.store("$opacity",i);j.style.visibility=i>0?"visible":"hidden";
-};var d=(h?function(j,i){j.style.opacity=i;}:(a?function(j,i){if(!j.currentStyle||!j.currentStyle.hasLayout){j.style.zoom=1;}i=(i*100).limit(0,100).round();
-i=(i==100)?"":"alpha(opacity="+i+")";var k=j.style.filter||j.getComputedStyle("filter")||"";j.style.filter=g.test(k)?k.replace(g,i):k+i;}:b));var e=(h?function(j){var i=j.style.opacity||j.getComputedStyle("opacity");
-return(i=="")?1:i.toFloat();}:(a?function(j){var k=(j.style.filter||j.getComputedStyle("filter")),i;if(k){i=k.match(g);}return(i==null||k==null)?1:(i[1]/100);
-}:function(j){var i=j.retrieve("$opacity");if(i==null){i=(j.style.visibility=="hidden"?0:1);}return i;}));var c=(f.style.cssFloat==null)?"styleFloat":"cssFloat";
-Element.implement({getComputedStyle:function(k){if(this.currentStyle){return this.currentStyle[k.camelCase()];}var j=Element.getDocument(this).defaultView,i=j?j.getComputedStyle(this,null):null;
-return(i)?i.getPropertyValue((k==c)?"float":k.hyphenate()):null;},setStyle:function(j,i){if(j=="opacity"){d(this,parseFloat(i));return this;}j=(j=="float"?c:j).camelCase();
-if(typeOf(i)!="string"){var k=(Element.Styles[j]||"@").split(" ");i=Array.from(i).map(function(m,l){if(!k[l]){return"";}return(typeOf(m)=="number")?k[l].replace("@",Math.round(m)):m;
-}).join(" ");}else{if(i==String(Number(i))){i=Math.round(i);}}this.style[j]=i;return this;},getStyle:function(o){if(o=="opacity"){return e(this);}o=(o=="float"?c:o).camelCase();
-var i=this.style[o];if(!i||o=="zIndex"){i=[];for(var n in Element.ShortStyles){if(o!=n){continue;}for(var m in Element.ShortStyles[n]){i.push(this.getStyle(m));
-}return i.join(" ");}i=this.getComputedStyle(o);}if(i){i=String(i);var k=i.match(/rgba?\([\d\s,]+\)/);if(k){i=i.replace(k[0],k[0].rgbToHex());}}if(Browser.opera||(Browser.ie&&isNaN(parseFloat(i)))){if((/^(height|width)$/).test(o)){var j=(o=="width")?["left","right"]:["top","bottom"],l=0;
-j.each(function(p){l+=this.getStyle("border-"+p+"-width").toInt()+this.getStyle("padding-"+p).toInt();},this);return this["offset"+o.capitalize()]-l+"px";
-}if(Browser.opera&&String(i).indexOf("px")!=-1){return i;}if((/^border(.+)Width|margin|padding/).test(o)){return"0px";}}return i;},setStyles:function(j){for(var i in j){this.setStyle(i,j[i]);
-}return this;},getStyles:function(){var i={};Array.flatten(arguments).each(function(j){i[j]=this.getStyle(j);},this);return i;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
-Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(o){var n=Element.ShortStyles;
-var j=Element.Styles;["margin","padding"].each(function(p){var q=p+o;n[p][q]=j[q]="@px";});var m="border"+o;n.border[m]=j[m]="@px @ rgb(@, @, @)";var l=m+"Width",i=m+"Style",k=m+"Color";
-n[m]={};n.borderWidth[l]=n[m][l]=j[l]="@px";n.borderStyle[i]=n[m][i]=j[i]="@";n.borderColor[k]=n[m][k]=j[k]="rgb(@, @, @)";});})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);
-}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;
-}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);
-}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());
-if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");
-if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];
-if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;
-},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);
-}return this;}var c=this.retrieve("events");if(!c){return this;}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);
-},this);delete c[b];}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
-}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
-}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};
-var a=function(b){var c=b.relatedTarget;if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));
-};Element.Events={mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.firefox)?"DOMMouseScroll":"mousewheel"}};
-if(!window.addEventListener){Element.NativeEvents.propertychange=2;Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";
-},condition:function(b){return !!(this.type!="radio"||this.checked);}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;
-var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover"},mouseleave:{base:"mouseout"},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
-var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
-n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
-if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
-}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
-}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
-}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
-});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
-}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(w&&w.condition){var l=q,m=w.condition;q=function(C,B){return l(C,B)&&m.call(C,B,v);
-};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);}}:function(B,C){if(!C&&B&&B.target){C=B.target;
-}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];
-if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);}delete p[u];q[m]=p;return j.call(this,r,w);}var o,v;
-if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);
-}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});})();(function(){var h=document.createElement("div"),e=document.createElement("div");
-h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);
-};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();
-}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};
-},getScroll:function(){if(a(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};
-while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}var n=(k(m,"position")=="static")?i:l;while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}try{return m.offsetParent;}catch(n){}return null;},getOffsets:function(){if(this.getBoundingClientRect&&!Browser.Platform.ios){var r=this.getBoundingClientRect(),o=document.id(this.getDocument().documentElement),q=o.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
-return{x:r.left.toInt()+t.x+((s)?0:q.x)-o.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-o.clientTop};}var n=this,m={x:0,y:0};if(a(this)){return m;}while(n&&!a(n)){m.x+=n.offsetLeft;
-m.y+=n.offsetTop;if(Browser.firefox){if(!c(n)){m.x+=b(n);m.y+=g(n);}var p=n.parentNode;if(p&&k(p,"overflow")!="visible"){m.x+=b(p);m.y+=g(p);}}else{if(n!=this&&Browser.safari){m.x+=b(n);
-m.y+=g(n);}}n=n.offsetParent;}if(Browser.firefox&&!c(this)){m.x-=b(this);m.y-=g(this);}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();
-var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();
-}var m=this.getPosition(o),n=this.getSize();var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
-},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
-},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
-return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
-return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
-}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
-}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
-},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
-},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
-},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
-this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
-this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
-}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
-},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
-return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
-this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
-this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
-d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
-}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
-this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
-},resume:function(){if((this.frame<this.frames)&&!this.isRunning()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];
-return g&&g.contains(this);}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};var e={},c={};var a=function(){var h=Date.now();
-for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);
-}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(c,d,b){b=Array.from(b);
-if(b[1]==null){b[1]=b[0];b[0]=c.getStyle(d);}var a=b.map(this.parse);return{from:a[0],to:a[1]};},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);
-return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};
-}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
-});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
-});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var c={},b=new RegExp("^"+a.escapeRegExp()+"$");
-Array.each(document.styleSheets,function(f,e){var d=f.href;if(d&&d.contains("://")&&!d.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;
-}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!b.test(j)){return;}Object.each(Element.Styles,function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;
-}l=String(k.style[i]);c[i]=((/^rgb/).test(l))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=c;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
-}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
-});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
-},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
-b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
-}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
-}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
-this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(c){var d=this.get("tween"),f,e,a;
-if(c==null){c="toggle";}switch(c){case"in":f="start";e=1;break;case"out":f="start";e=0;break;case"show":f="set";e=1;break;case"hide":f="set";e=0;break;
-case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);f="start";e=b?0:1;this.store("fade:flag",!b);a=true;break;default:f="start";e=c;
-}if(!a){this.eliminate("fade:flag");}d[f]("opacity",e);if(f=="set"||e!=0){this.setStyle("visibility",e==0?"hidden":"visible");}else{d.chain(function(){this.element.setStyle("visibility","hidden");
-});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
-}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
-b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
-},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
-for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
-for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
-return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
-return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
-a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
-var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
-}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
-},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
-return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
-},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
-});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
-this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
-this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
-}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
-}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
-}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
-},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
-},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
-return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
-}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
-}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
-o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
-break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
-j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
-}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.contains("?")?"&":"?")+String.uniqueID();
-}if(j&&e=="get"){f+=(f.contains("?")?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
-}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
-}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
-}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
-}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
-if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
-if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
-return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
-this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
-Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
-c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
-c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
-}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
-}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
-},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
-}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
-}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
-};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
-return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
-}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
-Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
-case"null":return"null";}return null;};JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure||JSON.secure){if(JSON.parse){return JSON.parse(string);
-}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");}}return eval("("+string+")");
-};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
-},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
-}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
-this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
-}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
-}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
-return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
-Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
-};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
-k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
-if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
-c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
-}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
-}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document);(function(){var Swiff=this.Swiff=new Class({Implements:Options,options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"window",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;
-},initialize:function(path,options){this.instance="Swiff_"+String.uniqueID();this.setOptions(options);options=this.options;var id=this.id=options.id||this.instance;
-var container=document.id(options.container);Swiff.CallBacks[this.instance]={};var params=options.params,vars=options.vars,callBacks=options.callBacks;
-var properties=Object.append({height:options.height,width:options.width},options.properties);var self=this;for(var callBack in callBacks){Swiff.CallBacks[this.instance][callBack]=(function(option){return function(){return option.apply(self.object,arguments);
-};})(callBacks[callBack]);vars[callBack]="Swiff.CallBacks."+this.instance+"."+callBack;}params.flashVars=Object.toQueryString(vars);if(Browser.ie){properties.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
-params.movie=path;}else{properties.type="application/x-shockwave-flash";}properties.data=path;var build='<object id="'+id+'"';for(var property in properties){build+=" "+property+'="'+properties[property]+'"';
-}build+=">";for(var param in params){if(params[param]){build+='<param name="'+param+'" value="'+params[param]+'" />';}}build+="</object>";this.object=((container)?container.empty():new Element("div")).set("html",build).firstChild;
-},replaces:function(element){element=document.id(element,true);element.parentNode.replaceChild(this.toElement(),element);return this;},inject:function(element){document.id(element,true).appendChild(this.toElement());
-return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].append(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");
-return eval(rs);};})(); \ No newline at end of file
diff --git a/module/web/media/js/mootools-more-1.4.0.1.js b/module/web/media/js/mootools-more-1.4.0.1.js
deleted file mode 100644
index f3f8e4ee1..000000000
--- a/module/web/media/js/mootools-more-1.4.0.1.js
+++ /dev/null
@@ -1,216 +0,0 @@
-// MooTools: the javascript framework.
-// Load this file's selection again by visiting: http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
-// Or build this file again with packager using: packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
-/*
----
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-MooTools.More={version:"1.4.0.1",build:"a4244edf2aa97ac8a196fc96082dd35af1abab87"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
-}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
-if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
-if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
-c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
-return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
-});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
-};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
-g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
-}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
-},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
-};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
-},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
-if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
-},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
-e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
-a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
-},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
-},setOffsetOption:function(f,d){var c={x:0,y:0},g=f.measure(function(){return document.id(this.getOffsetParent());}),e=g.getScroll();if(!g||g==f.getDocument().body){return;
-}c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};
-},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});},getPosition:function(e,d){var c={};
-d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();
-c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);
-}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;
-return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
-switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
-break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
-["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
-},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
-},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
-c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
-},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
-break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
-c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
-}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
-return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);var IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:(Browser.ie6||(Browser.firefox&&Browser.version<3&&Browser.Platform.mac))},property:"IframeShim",initialize:function(b,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var c=this.element.getStyle("zIndex").toInt();
-if(!c){c=1;var b=this.element.getStyle("position");if(b=="static"||!b){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",c);
-}c=((this.options.zIndex!=null||this.options.zIndex===0)&&c>this.options.zIndex)?this.options.zIndex:c-1;if(c<0){c=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:c,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
-var a=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",a);
-}else{a();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
-}var a=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){a.x=a.x-(this.options.margin*2);a.y=a.y-(this.options.margin*2);
-this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:a.x,height:a.y}).position({relativeTo:this.element,offset:this.options.offset});
-return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
-}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
-}});window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
-this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
-if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||this.target==document.body?"inside":"after";
-b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
-this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
-this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
-},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
-var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
-return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
-return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
-}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
-this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
-},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
-}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
-this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
-},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
-this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
-this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
-}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
-}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
-b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
-}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
-if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
-if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
-},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
-},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
-}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
-}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
-if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
-}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
-return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
-}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
-}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
-while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
-if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
-if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
-}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
-}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
-},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
-e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
-}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
-["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
-while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
-})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
-}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
-});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
-},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
-}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
-var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
-},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
-if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
-b.addEvent("onFormValidate",this.onFormValidate);this.element.validate();return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
-if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
-this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
-if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
-this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
-}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
-},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
-},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
-},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
-},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
-if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
-if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
-};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:!Browser.ie6,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
-},opacity:1,hideInputs:Browser.ie?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
-this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
-var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
-this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
-}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
-}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
-this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
-}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
-Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
-this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
-}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
-}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
-}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
-if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
-return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
-Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
-return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
-}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
-b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
-}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
-this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.ie)?"selectstart":"mousedown";if(Browser.ie&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
-Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
-this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
-return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
-}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
-}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
-}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
-if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
-}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
-}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
-this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
-}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
-}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
-}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
-}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
-if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
-a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
-this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
-b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
-}if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));
-}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();
-}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
-["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
-g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
-if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
-n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
-l+=((Browser.ie6||Browser.ie7)?0:k.top)+m.top;}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};},getDroppableCoordinates:function(c){var b=c.getCoordinates();
-if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
-var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
-}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
-}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
-this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{}},initialize:function(a,b){this.setOptions(b);
-this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
-}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
-var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
-},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
-},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
-return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
-},this));},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);
-}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
-});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
-}});}return c.inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
-if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
-this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
-},start:function(b,a){if(!this.idle||b.rightClick||["button","input","a","textarea"].contains(b.target.get("tag"))){return;}this.idle=false;this.element=a;
-this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
-this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
-}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
-},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);if(this.effect){var b=this.element.getStyles("width","height"),d=this.clone,c=d.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
-var a=function(){this.removeEvent("cancel",a);d.destroy();};this.effect.element=d;this.effect.start({top:c.top,left:c.left,width:b.width,height:b.height,opacity:0.25}).addEvent("cancel",a).chain(a);
-}else{this.clone.destroy();}this.reset();},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
-}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
-}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
-}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
-this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
-}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
-}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
-break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
-var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
-}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
-},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
-}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
-};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
-if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
-},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
-var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
-case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
-return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/module/web/media/js/package_ui.js b/module/web/media/js/package_ui.js
deleted file mode 100644
index 3ea965649..000000000
--- a/module/web/media/js/package_ui.js
+++ /dev/null
@@ -1,377 +0,0 @@
-var root = this;
-
-document.addEvent("domready", function() {
- root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
- root.load.set("opacity", 0);
-
-
- root.packageBox = new MooDialog({destroyOnHide: false});
- root.packageBox.setContent($('pack_box'));
-
- $('pack_reset').addEvent('click', function() {
- $('pack_form').reset();
- root.packageBox.close();
- });
-});
-
-function indicateLoad() {
- //$("load-indicator").reveal();
- root.load.start("opacity", 1)
-}
-
-function indicateFinish() {
- root.load.start("opacity", 0)
-}
-
-function indicateSuccess() {
- indicateFinish();
- root.notify.alert('{{_("Success")}}.', {
- 'className': 'success'
- });
-}
-
-function indicateFail() {
- indicateFinish();
- root.notify.alert('{{_("Failed")}}.', {
- 'className': 'error'
- });
-}
-
-var PackageUI = new Class({
- initialize: function(url, type) {
- this.url = url;
- this.type = type;
- this.packages = [];
- this.parsePackages();
-
- this.sorts = new Sortables($("package-list"), {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".package_drag",
- onComplete: this.saveSort.bind(this)
- });
-
- $("del_finished").addEvent("click", this.deleteFinished.bind(this));
- $("restart_failed").addEvent("click", this.restartFailed.bind(this));
-
- },
-
- parsePackages: function() {
- $("package-list").getChildren("li").each(function(ele) {
- var id = ele.getFirst().get("id").match(/[0-9]+/);
- this.packages.push(new Package(this, id, ele))
- }.bind(this))
- },
-
- loadPackages: function() {
- },
-
- deleteFinished: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/deleteFinished',
- onSuccess: function(data) {
- if (data.length > 0) {
- window.location.reload()
- } else {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- restartFailed: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/restartFailed',
- onSuccess: function(data) {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- startSort: function(ele, copy) {
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("pid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
-var Package = new Class({
- initialize: function(ui, id, ele, data) {
- this.ui = ui;
- this.id = id;
- this.linksLoaded = false;
-
- if (!ele) {
- this.createElement(data);
- } else {
- this.ele = ele;
- this.order = ele.getElements("div.order")[0].get("html");
- this.ele.store("order", this.order);
- this.ele.store("pid", this.id);
- this.parseElement();
- }
-
- var pname = this.ele.getElements(".packagename")[0];
- this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
- this.buttons.set("opacity", 0);
-
- pname.addEvent("mouseenter", function(e) {
- this.buttons.start("opacity", 1)
- }.bind(this));
-
- pname.addEvent("mouseleave", function(e) {
- this.buttons.start("opacity", 0)
- }.bind(this));
-
-
- },
-
- createElement: function() {
- alert("create")
- },
-
- parseElement: function() {
- var imgs = this.ele.getElements('img');
-
- this.name = this.ele.getElements('.name')[0];
- this.folder = this.ele.getElements('.folder')[0];
- this.password = this.ele.getElements('.password')[0];
-
- imgs[1].addEvent('click', this.deletePackage.bind(this));
- imgs[2].addEvent('click', this.restartPackage.bind(this));
- imgs[3].addEvent('click', this.editPackage.bind(this));
- imgs[4].addEvent('click', this.movePackage.bind(this));
-
- this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
-
- },
-
- loadLinks: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package/' + this.id,
- onSuccess: this.createLinks.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- createLinks: function(data) {
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.set("html", "");
- data.links.each(function(link) {
- link.id = link.fid;
- var li = new Element("li", {
- "style": {
- "margin-left": 0
- }
- });
-
- var html = "<span style='cursor: move' class='child_status sorthandle'><img src='/media/default/img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({"icon": link.icon});
- html += "<span style='font-size: 15px'>{name}</span><br /><div class='child_secrow'>".substitute({"name": link.name});
- html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({"statusmsg": link.statusmsg, "error":link.error});
- html += "<span class='child_status'>{format_size}</span>".substitute({"format_size": link.format_size});
- html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({"plugin": link.plugin});
- html += "<img title='{{_("Delete Link")}}' style='cursor: pointer;' width='10px' height='10px' src='/media/default/img/delete.png' />&nbsp;&nbsp;";
- html += "<img title='{{_("Restart Link")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='/media/default/img/arrow_refresh.png' /></div>";
-
- var div = new Element("div", {
- "id": "file_" + link.id,
- "class": "child",
- "html": html
- });
-
- li.store("order", link.order);
- li.store("lid", link.id);
-
- li.adopt(div);
- ul.adopt(li);
- });
- this.sorts = new Sortables(ul, {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".sorthandle",
- onComplete: this.saveSort.bind(this)
- });
- this.registerLinkEvents();
- this.linksLoaded = true;
- indicateFinish();
- this.toggle();
- },
-
- registerLinkEvents: function() {
- this.ele.getElements('.child').each(function(child) {
- var lid = child.get('id').match(/[0-9]+/);
- var imgs = child.getElements('.child_secrow img');
- imgs[0].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/deleteFiles/[' + this + "]",
- onSuccess: function() {
- $('file_' + this).nix()
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
-
- imgs[1].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/restartFile/' + this,
- onSuccess: function() {
- var ele = $('file_' + this);
- var imgs = ele.getElements("img");
- imgs[0].set("src", "/media/default/img/status_queue.png");
- var spans = ele.getElements(".child_status");
- spans[1].set("html", "queued");
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
- });
- },
-
- toggle: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- } else {
- if (!this.linksLoaded) {
- this.loadLinks();
- } else {
- child.reveal();
- }
- }
- },
-
-
- deletePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/deletePackages/[' + this.id + "]",
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- //hide_pack();
- event.stop();
- },
-
- restartPackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/restartPackage/' + this.id,
- onSuccess: function() {
- this.close();
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- close: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- }
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.erase("html");
- this.linksLoaded = false;
- },
-
- movePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- editPackage: function(event) {
- $("pack_form").removeEvents("submit");
- $("pack_form").addEvent("submit", this.savePackage.bind(this));
-
- $("pack_id").set("value", this.id);
- $("pack_name").set("value", this.name.get("text"));
- $("pack_folder").set("value", this.folder.get("text"));
- $("pack_pws").set("value", this.password.get("text"));
-
- root.packageBox.open();
- event.stop();
- },
-
- savePackage: function(event) {
- $("pack_form").send();
- this.name.set("text", $("pack_name").get("value"));
- this.folder.set("text", $("pack_folder").get("value"));
- this.password.set("text", $("pack_pws").get("value"));
- root.packageBox.close();
- event.stop();
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("lid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/link_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
diff --git a/module/web/media/js/purr_static.js b/module/web/media/js/purr_static.js
deleted file mode 100644
index 7e0aee949..000000000
--- a/module/web/media/js/purr_static.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
----
-script: purr.js
-
-description: Class to create growl-style popup notifications.
-
-license: MIT-style
-
-authors: [atom smith]
-
-requires:
-- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
-
-provides: [Purr, Element.alert]
-...
-*/
-
-
-var Purr = new Class({
-
- 'options': {
- 'mode': 'top',
- 'position': 'left',
- 'elementAlertClass': 'purr-element-alert',
- 'elements': {
- 'wrapper': 'div',
- 'alert': 'div',
- 'buttonWrapper': 'div',
- 'button': 'button'
- },
- 'elementOptions': {
- 'wrapper': {
- 'styles': {
- 'position': 'fixed',
- 'z-index': '9999'
- },
- 'class': 'purr-wrapper'
- },
- 'alert': {
- 'class': 'purr-alert',
- 'styles': {
- 'opacity': '.85'
- }
- },
- 'buttonWrapper': {
- 'class': 'purr-button-wrapper'
- },
- 'button': {
- 'class': 'purr-button'
- }
- },
- 'alert': {
- 'buttons': [],
- 'clickDismiss': true,
- 'hoverWait': true,
- 'hideAfter': 5000,
- 'fx': {
- 'duration': 500
- },
- 'highlightRepeat': false,
- 'highlight': { // false to disable highlighting
- 'start': '#FF0',
- 'end': false
- }
- }
- },
-
- 'Implements': [Options, Events, Chain],
-
- 'initialize': function(options){
- this.setOptions(options);
- this.createWrapper();
- return this;
- },
-
- 'bindAlert': function(){
- return this.alert.bind(this);
- },
-
- 'createWrapper': function(){
- this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
- if(this.options.mode == 'top')
- {
- this.wrapper.setStyle('top', 0);
- }
- else
- {
- this.wrapper.setStyle('bottom', 0);
- }
- document.id(document.body).grab(this.wrapper);
- this.positionWrapper(this.options.position);
- },
-
- 'positionWrapper': function(position){
- if(typeOf(position) == 'object')
- {
-
- var wrapperCoords = this.getWrapperCoords();
-
- this.wrapper.setStyles({
- 'bottom': '',
- 'left': position.x,
- 'top': position.y - wrapperCoords.height,
- 'position': 'absolute'
- });
- }
- else if(position == 'left')
- {
- this.wrapper.setStyle('left', 0);
- }
- else if(position == 'right')
- {
- this.wrapper.setStyle('right', 0);
- }
- else
- {
- this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
- }
- return this;
- },
-
- 'getWrapperCoords': function(){
- this.wrapper.setStyle('visibility', 'hidden');
- var measurer = this.alert('need something in here to measure');
- var coords = this.wrapper.getCoordinates();
- measurer.destroy();
- this.wrapper.setStyle('visibility','');
- return coords;
- },
-
- 'alert': function(msg, options){
-
- options = Object.merge({}, this.options.alert, options || {});
-
- var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
-
- if(typeOf(msg) == 'string')
- {
- alert.set('html', msg);
- }
- else if(typeOf(msg) == 'element')
- {
- alert.grab(msg);
- }
- else if(typeOf(msg) == 'array')
- {
- var alerts = [];
- msg.each(function(m){
- alerts.push(this.alert(m, options));
- }, this);
- return alerts;
- }
-
- alert.store('options', options);
-
- if(options.buttons.length > 0)
- {
- options.clickDismiss = false;
- options.hideAfter = false;
- options.hoverWait = false;
- var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
- alert.grab(buttonWrapper);
- options.buttons.each(function(button){
- if(button.text !== undefined)
- {
- var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
- callbackButton.set('html', button.text);
- if(button.callback !== undefined)
- {
- callbackButton.addEvent('click', button.callback.pass(alert));
- }
- if(button.dismiss !== undefined && button.dismiss)
- {
- callbackButton.addEvent('click', this.dismiss.pass(alert, this));
- }
- buttonWrapper.grab(callbackButton);
- }
- }, this);
- }
- if(options.className !== undefined)
- {
- alert.addClass(options.className);
- }
-
- this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
-
- var fx = Object.merge(this.options.alert.fx, options.fx);
- var alertFx = new Fx.Morph(alert, fx);
- alert.store('fx', alertFx);
- this.fadeIn(alert);
-
- if(options.highlight)
- {
- alertFx.addEvent('complete', function(){
- alert.highlight(options.highlight.start, options.highlight.end);
- if(options.highlightRepeat)
- {
- alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
- }
- });
- }
- if(options.hideAfter)
- {
- this.dismiss(alert);
- }
-
- if(options.clickDismiss)
- {
- alert.addEvent('click', function(){
- this.holdUp = false;
- this.dismiss(alert, true);
- }.bind(this));
- }
-
- if(options.hoverWait)
- {
- alert.addEvents({
- 'mouseenter': function(){
- this.holdUp = true;
- }.bind(this),
- 'mouseleave': function(){
- this.holdUp = false;
- }.bind(this)
- });
- }
-
- return alert;
- },
-
- 'fadeIn': function(alert){
- var alertFx = alert.retrieve('fx');
- alertFx.set({
- 'opacity': 0
- });
- alertFx.start({
- 'opacity': [this.options.elementOptions.alert.styles.opacity, '.9'].pick()
- });
- },
-
- 'dismiss': function(alert, now){
- now = now || false;
- var options = alert.retrieve('options');
- if(now)
- {
- this.fadeOut(alert);
- }
- else
- {
- this.fadeOut.delay(options.hideAfter, this, alert);
- }
- },
-
- 'fadeOut': function(alert){
- if(this.holdUp)
- {
- this.dismiss.delay(100, this, [alert, true]);
- return null;
- }
- var alertFx = alert.retrieve('fx');
- if(!alertFx)
- {
- return null;
- }
- var to = {
- 'opacity': 0
- };
- if(this.options.mode == 'top')
- {
- to['margin-top'] = '-'+alert.offsetHeight+'px';
- }
- else
- {
- to['margin-bottom'] = '-'+alert.offsetHeight+'px';
- }
- alertFx.start(to);
- alertFx.addEvent('complete', function(){
- alert.destroy();
- });
- }
-});
-
-Element.implement({
-
- 'alert': function(msg, options){
- var alert = this.retrieve('alert');
- if(!alert)
- {
- options = options || {
- 'mode':'top'
- };
- alert = new Purr(options);
- this.store('alert', alert);
- }
-
- var coords = this.getCoordinates();
-
- alert.alert(msg, options);
-
- alert.wrapper.setStyles({
- 'bottom': '',
- 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
- 'top': coords.top - (alert.wrapper.getHeight()),
- 'position': 'absolute'
- });
-
- }
-
-}); \ No newline at end of file
diff --git a/module/web/media/js/settings.js b/module/web/media/js/settings.js
deleted file mode 100644
index 9191fac72..000000000
--- a/module/web/media/js/settings.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/tinytab_static.js b/module/web/media/js/tinytab_static.js
deleted file mode 100644
index 6c38292f5..000000000
--- a/module/web/media/js/tinytab_static.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
----
-description: TinyTab - Tiny and simple tab handler for Mootools.
-
-license: MIT-style
-
-authors:
-- Danillo César de O. Melo
-
-requires:
-- core/1.2.4: '*'
-
-provides: TinyTab
-
-...
-*/
-(function($) {
- this.TinyTab = new Class({
- Implements: Events,
- initialize: function(tabs, contents, opt) {
- this.tabs = tabs;
- this.contents = contents;
- this.header = $("tabsback");
- this.headers = [];
- for(var i =0; i < this.tabs.length; i++){
- this.headers.push("");
- }
- if(!opt) opt = {};
- this.css = opt.selectedClass || 'selected';
- this.select(this.tabs[0]);
- tabs.each(function(el){
- el.addEvent('click',function(e){
- this.select(el);
- e.stop();
- }.bind(this));
- }.bind(this));
- },
-
- select: function(el) {
- this.tabs.removeClass(this.css);
- el.addClass(this.css);
- this.contents.setStyle('display','none');
- var index = this.tabs.indexOf(el);
- this.header.set("text", this.headers[index]);
- var content = this.contents[index];
- content.setStyle('display','block');
- this.fireEvent('change',[content,el]);
- }
- });
-})(document.id); \ No newline at end of file
diff --git a/module/web/middlewares.py b/module/web/middlewares.py
deleted file mode 100644
index e0e6c3102..000000000
--- a/module/web/middlewares.py
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import gzip
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-class StripPathMiddleware(object):
- def __init__(self, app):
- self.app = app
-
- def __call__(self, e, h):
- e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
- return self.app(e, h)
-
-
-class PrefixMiddleware(object):
- def __init__(self, app, prefix="/pyload"):
- self.app = app
- self.prefix = prefix
-
- def __call__(self, e, h):
- path = e["PATH_INFO"]
- if path.startswith(self.prefix):
- e['PATH_INFO'] = path.replace(self.prefix, "", 1)
- return self.app(e, h)
-
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-# WSGI middleware
-# Gzip-encodes the response.
-
-class GZipMiddleWare(object):
-
- def __init__(self, application, compress_level=6):
- self.application = application
- self.compress_level = int(compress_level)
-
- def __call__(self, environ, start_response):
- if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
- # nothing for us to do, so this middleware will
- # be a no-op:
- return self.application(environ, start_response)
- response = GzipResponse(start_response, self.compress_level)
- app_iter = self.application(environ,
- response.gzip_start_response)
- if app_iter is not None:
- response.finish_response(app_iter)
-
- return response.write()
-
-def header_value(headers, key):
- for header, value in headers:
- if key.lower() == header.lower():
- return value
-
-def update_header(headers, key, value):
- remove_header(headers, key)
- headers.append((key, value))
-
-def remove_header(headers, key):
- for header, value in headers:
- if key.lower() == header.lower():
- headers.remove((header, value))
- break
-
-class GzipResponse(object):
-
- def __init__(self, start_response, compress_level):
- self.start_response = start_response
- self.compress_level = compress_level
- self.buffer = StringIO()
- self.compressible = False
- self.content_length = None
- self.headers = ()
-
- def gzip_start_response(self, status, headers, exc_info=None):
- self.headers = headers
- ct = header_value(headers,'content-type')
- ce = header_value(headers,'content-encoding')
- cl = header_value(headers, 'content-length')
- if cl:
- cl = int(cl)
- else:
- cl = 201
- self.compressible = False
- if ct and (ct.startswith('text/') or ct.startswith('application/')) \
- and 'zip' not in ct and cl > 200:
- self.compressible = True
- if ce:
- self.compressible = False
- if self.compressible:
- headers.append(('content-encoding', 'gzip'))
- remove_header(headers, 'content-length')
- self.headers = headers
- self.status = status
- return self.buffer.write
-
- def write(self):
- out = self.buffer
- out.seek(0)
- s = out.getvalue()
- out.close()
- return [s]
-
- def finish_response(self, app_iter):
- if self.compressible:
- output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level,
- fileobj=self.buffer)
- else:
- output = self.buffer
- try:
- for s in app_iter:
- output.write(s)
- if self.compressible:
- output.close()
- finally:
- if hasattr(app_iter, 'close'):
- try:
- app_iter.close()
- except :
- pass
-
- content_length = self.buffer.tell()
- update_header(self.headers, "Content-Length" , str(content_length))
- self.start_response(self.status, self.headers) \ No newline at end of file
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
deleted file mode 100644
index df4a4b3d4..000000000
--- a/module/web/pyload_app.py
+++ /dev/null
@@ -1,533 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from datetime import datetime
-from operator import itemgetter, attrgetter
-
-import time
-import os
-import sys
-from os import listdir
-from os.path import isdir, isfile, join, abspath
-from sys import getfilesystemencoding
-from urllib import unquote
-
-from bottle import route, static_file, request, response, redirect, HTTPError, error
-
-from webinterface import PYLOAD, PYLOAD_DIR, PROJECT_DIR, SETUP, env
-
-from utils import render_to_response, parse_permissions, parse_userdata, \
- login_required, get_permission, set_permission, permlist, toDict, set_session
-
-from filters import relpath, unquotepath
-
-from module.utils import formatSize, save_join, fs_encode, fs_decode
-
-# Helper
-
-def pre_processor():
- s = request.environ.get('beaker.session')
- user = parse_userdata(s)
- perms = parse_permissions(s)
- status = {}
- captcha = False
- update = False
- plugins = False
- if user["is_authenticated"]:
- status = PYLOAD.statusServer()
- info = PYLOAD.getInfoByPlugin("UpdateManager")
- captcha = PYLOAD.isCaptchaWaiting()
-
- # check if update check is available
- if info:
- if info["pyload"] == "True": update = True
- if info["plugins"] == "True": plugins = True
-
-
- return {"user": user,
- 'status': status,
- 'captcha': captcha,
- 'perms': perms,
- 'url': request.url,
- 'update': update,
- 'plugins': plugins}
-
-
-def base(messages):
- return render_to_response('base.html', {'messages': messages}, [pre_processor])
-
-
-## Views
-@error(500)
-def error500(error):
- print "An error occured while processing the request."
- if error.traceback:
- print error.traceback
-
- return base(["An Error occured, please enable debug mode to get more details.", error,
- error.traceback.replace("\n", "<br>") if error.traceback else "No Traceback"])
-
-# render js
-@route("/media/js/<path:re:.+\.js>")
-def js_dynamic(path):
- response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
- time.gmtime(time.time() + 60 * 60 * 24 * 2))
- response.headers['Cache-control'] = "public"
- response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
-
- try:
- # static files are not rendered
- if "static" not in path and "mootools" not in path:
- t = env.get_template("js/%s" % path)
- return t.render()
- else:
- return static_file(path, root=join(PROJECT_DIR, "media", "js"))
- except:
- return HTTPError(404, "Not Found")
-
-@route('/media/<path:path>')
-def server_static(path):
- response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
- time.gmtime(time.time() + 60 * 60 * 24 * 7))
- response.headers['Cache-control'] = "public"
- return static_file(path, root=join(PROJECT_DIR, "media"))
-
-@route('/favicon.ico')
-def favicon():
- return static_file("favicon.ico", root=join(PROJECT_DIR, "media", "img"))
-
-
-@route('/login', method="GET")
-def login():
- if not PYLOAD and SETUP:
- redirect("/setup")
- else:
- return render_to_response("login.html", proc=[pre_processor])
-
-
-@route('/nopermission')
-def nopermission():
- return base([_("You dont have permission to access this page.")])
-
-
-@route("/login", method="POST")
-def login_post():
- user = request.forms.get("username")
- password = request.forms.get("password")
-
- info = PYLOAD.checkAuth(user, password)
-
- if not info:
- return render_to_response("login.html", {"errors": True}, [pre_processor])
-
- set_session(request, info)
- return redirect("/")
-
-
-@route("/logout")
-def logout():
- s = request.environ.get('beaker.session')
- s.delete()
- return render_to_response("logout.html", proc=[pre_processor])
-
-
-@route("/")
-@route("/home")
-@login_required("LIST")
-def home():
- try:
- res = [toDict(x) for x in PYLOAD.statusDownloads()]
- except:
- s = request.environ.get('beaker.session')
- s.delete()
- return redirect("/login")
-
- for link in res:
- if link["status"] == 12:
- link["information"] = "%s kB @ %s kB/s" % (link["size"] - link["bleft"], link["speed"])
-
- return render_to_response("home.html", {"res": res}, [pre_processor])
-
-
-@route("/queue")
-@login_required("LIST")
-def queue():
- queue = PYLOAD.getQueue()
-
- queue.sort(key=attrgetter("order"))
-
- return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor])
-
-
-@route("/collector")
-@login_required('LIST')
-def collector():
- queue = PYLOAD.getCollector()
-
- queue.sort(key=attrgetter("order"))
-
- return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor])
-
-
-@route("/downloads")
-@login_required('DOWNLOAD')
-def downloads():
- root = PYLOAD.getConfigValue("general", "download_folder")
-
- if not isdir(root):
- return base([_('Download directory not found.')])
- data = {
- 'folder': [],
- 'files': []
- }
-
- items = listdir(fs_encode(root))
-
- for item in sorted([fs_decode(x) for x in items]):
- if isdir(save_join(root, item)):
- folder = {
- 'name': item,
- 'path': item,
- 'files': []
- }
- files = listdir(save_join(root, item))
- for file in sorted([fs_decode(x) for x in files]):
- try:
- if isfile(save_join(root, item, file)):
- folder['files'].append(file)
- except:
- pass
-
- data['folder'].append(folder)
- elif isfile(join(root, item)):
- data['files'].append(item)
-
- return render_to_response('downloads.html', {'files': data}, [pre_processor])
-
-
-@route("/downloads/get/<path:re:.+>")
-@login_required("DOWNLOAD")
-def get_download(path):
- path = unquote(path).decode("utf8")
- #@TODO some files can not be downloaded
-
- root = PYLOAD.getConfigValue("general", "download_folder")
-
- path = path.replace("..", "")
- try:
- return static_file(fs_encode(path), fs_encode(root))
-
- except Exception, e:
- print e
- return HTTPError(404, "File not Found.")
-
-
-
-@route("/settings")
-@login_required('SETTINGS')
-def config():
- conf = PYLOAD.getConfig()
- plugin = PYLOAD.getPluginConfig()
-
- conf_menu = []
- plugin_menu = []
-
- for entry in sorted(conf.keys()):
- conf_menu.append((entry, conf[entry].description))
-
- for entry in sorted(plugin.keys()):
- plugin_menu.append((entry, plugin[entry].description))
-
- accs = PYLOAD.getAccounts(False)
-
- for data in accs:
- if data.trafficleft == -1:
- data.trafficleft = _("unlimited")
- elif not data.trafficleft:
- data.trafficleft = _("not available")
- else:
- data.trafficleft = formatSize(data.trafficleft * 1024)
-
- if data.validuntil == -1:
- data.validuntil = _("unlimited")
- elif not data.validuntil :
- data.validuntil = _("not available")
- else:
- t = time.localtime(data.validuntil)
- data.validuntil = time.strftime("%d.%m.%Y", t)
-
- if "time" in data.options:
- try:
- data.options["time"] = data.options["time"][0]
- except:
- data.options["time"] = "0:00-0:00"
-
- if "limitDL" in data.options:
- data.options["limitdl"] = data.options["limitDL"][0]
- else:
- data.options["limitdl"] = "0"
-
- return render_to_response('settings.html',
- {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, 'types': PYLOAD.getAccountTypes()},
- [pre_processor])
-
-
-@route("/filechooser")
-@route("/pathchooser")
-@route("/filechooser/:file#.+#")
-@route("/pathchooser/:path#.+#")
-@login_required('STATUS')
-def path(file="", path=""):
- if file:
- type = "file"
- else:
- type = "folder"
-
- path = os.path.normpath(unquotepath(path))
-
- if os.path.isfile(path):
- oldfile = path
- path = os.path.dirname(path)
- else:
- oldfile = ''
-
- abs = False
-
- if os.path.isdir(path):
- if os.path.isabs(path):
- cwd = os.path.abspath(path)
- abs = True
- else:
- cwd = relpath(path)
- else:
- cwd = os.getcwd()
-
- try:
- cwd = cwd.encode("utf8")
- except:
- pass
-
- cwd = os.path.normpath(os.path.abspath(cwd))
- parentdir = os.path.dirname(cwd)
- if not abs:
- if os.path.abspath(cwd) == "/":
- cwd = relpath(cwd)
- else:
- cwd = relpath(cwd) + os.path.sep
- parentdir = relpath(parentdir) + os.path.sep
-
- if os.path.abspath(cwd) == "/":
- parentdir = ""
-
- try:
- folders = os.listdir(cwd)
- except:
- folders = []
-
- files = []
-
- for f in folders:
- try:
- f = f.decode(getfilesystemencoding())
- data = {'name': f, 'fullpath': join(cwd, f)}
- data['sort'] = data['fullpath'].lower()
- data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f))))
- data['ext'] = os.path.splitext(f)[1]
- except:
- continue
-
- if os.path.isdir(join(cwd, f)):
- data['type'] = 'dir'
- else:
- data['type'] = 'file'
-
- if os.path.isfile(join(cwd, f)):
- data['size'] = os.path.getsize(join(cwd, f))
-
- power = 0
- while (data['size'] / 1024) > 0.3:
- power += 1
- data['size'] /= 1024.
- units = ('', 'K', 'M', 'G', 'T')
- data['unit'] = units[power] + 'Byte'
- else:
- data['size'] = ''
-
- files.append(data)
-
- files = sorted(files, key=itemgetter('type', 'sort'))
-
- return render_to_response('pathchooser.html',
- {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile,
- 'absolute': abs}, [])
-
-
-@route("/logs")
-@route("/logs", method="POST")
-@route("/logs/:item")
-@route("/logs/:item", method="POST")
-@login_required('LOGS')
-def logs(item=-1):
- s = request.environ.get('beaker.session')
-
- perpage = s.get('perpage', 34)
- reversed = s.get('reversed', False)
-
- warning = ""
- conf = PYLOAD.getConfigValue("log","file_log")
- if not conf:
- warning = "Warning: File log is disabled, see settings page."
-
- perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all'))
- fro = None
-
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- try:
- fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
- except:
- pass
- try:
- perpage = int(request.forms['perpage'])
- s['perpage'] = perpage
-
- reversed = bool(request.forms.get('reversed', False))
- s['reversed'] = reversed
- except:
- pass
-
- s.save()
-
- try:
- item = int(item)
- except:
- pass
-
- log = PYLOAD.getLog()
- if not perpage:
- item = 0
-
- if item < 1 or type(item) is not int:
- item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1
-
- if type(fro) is datetime: # we will search for datetime
- item = -1
-
- data = []
- counter = 0
- perpagecheck = 0
- for l in log:
- counter += 1
-
- if counter >= item:
- try:
- date, time, level, message = l.decode("utf8", "ignore").split(" ", 3)
- dtime = datetime.strptime(date + ' ' + time, '%d.%m.%Y %H:%M:%S')
- except:
- dtime = None
- date = '?'
- time = ' '
- level = '?'
- message = l
- if item == -1 and dtime is not None and fro <= dtime:
- item = counter #found our datetime
- if item >= 0:
- data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message})
- perpagecheck += 1
- if fro is None and dtime is not None: #if fro not set set it to first showed line
- fro = dtime
- if perpagecheck >= perpage > 0:
- break
-
- if fro is None: #still not set, empty log?
- fro = datetime.now()
- if reversed:
- data.reverse()
- return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'),
- 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p),
- 'iprev': 1 if item - perpage < 1 else item - perpage,
- 'inext': (item + perpage) if item + perpage < len(log) else item},
- [pre_processor])
-
-
-@route("/admin")
-@route("/admin", method="POST")
-@login_required("ADMIN")
-def admin():
- # convert to dict
- user = dict([(name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()])
- perms = permlist()
-
- for data in user.itervalues():
- data["perms"] = {}
- get_permission(data["perms"], data["permission"])
- data["perms"]["admin"] = True if data["role"] is 0 else False
-
-
- s = request.environ.get('beaker.session')
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- for name in user:
- if request.POST.get("%s|admin" % name, False):
- user[name]["role"] = 0
- user[name]["perms"]["admin"] = True
- elif name != s["name"]:
- user[name]["role"] = 1
- user[name]["perms"]["admin"] = False
-
- # set all perms to false
- for perm in perms:
- user[name]["perms"][perm] = False
-
-
- for perm in request.POST.getall("%s|perms" % name):
- user[name]["perms"][perm] = True
-
- user[name]["permission"] = set_permission(user[name]["perms"])
-
- PYLOAD.setUserPermission(name, user[name]["permission"], user[name]["role"])
-
- return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor])
-
-
-@route("/setup")
-def setup():
- if PYLOAD or not SETUP:
- return base([_("Run pyLoadCore.py -s to access the setup.")])
-
- return render_to_response('setup.html', {"user": False, "perms": False})
-
-
-@route("/info")
-def info():
- conf = PYLOAD.getConfigDict()
-
- if hasattr(os, "uname"):
- extra = os.uname()
- else:
- extra = tuple()
-
- data = {"python": sys.version,
- "os": " ".join((os.name, sys.platform) + extra),
- "version": PYLOAD.getServerVersion(),
- "folder": abspath(PYLOAD_DIR), "config": abspath(""),
- "download": abspath(conf["general"]["download_folder"]["value"]),
- "freespace": formatSize(PYLOAD.freeSpace()),
- "remote": conf["remote"]["port"]["value"],
- "webif": conf["webinterface"]["port"]["value"],
- "language": conf["general"]["language"]["value"]}
-
- return render_to_response("info.html", data, [pre_processor])
diff --git a/module/web/servers/lighttpd_default.conf b/module/web/servers/lighttpd_default.conf
deleted file mode 100644
index e56dda35f..000000000
--- a/module/web/servers/lighttpd_default.conf
+++ /dev/null
@@ -1,153 +0,0 @@
-# lighttpd configuration file
-#
-# use it as a base for lighttpd 1.0.0 and above
-#
-# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
-
-############ Options you really have to take care of ####################
-
-## modules to load
-# at least mod_access and mod_accesslog should be loaded
-# all other module should only be loaded if really neccesary
-# - saves some time
-# - saves memory
-server.modules = (
- "mod_rewrite",
- "mod_redirect",
- "mod_alias",
- "mod_access",
-# "mod_trigger_b4_dl",
-# "mod_auth",
-# "mod_status",
-# "mod_setenv",
- "mod_fastcgi",
-# "mod_proxy",
-# "mod_simple_vhost",
-# "mod_evhost",
-# "mod_userdir",
-# "mod_cgi",
-# "mod_compress",
-# "mod_ssi",
-# "mod_usertrack",
-# "mod_expire",
-# "mod_secdownload",
-# "mod_rrdtool",
-# "mod_accesslog"
- )
-
-## A static document-root. For virtual hosting take a look at the
-## mod_simple_vhost module.
-server.document-root = "%(path)"
-
-## where to send error-messages to
-server.errorlog = "%(path)/error.log"
-
-# files to check for if .../ is requested
-index-file.names = ( "index.php", "index.html",
- "index.htm", "default.htm" )
-
-## set the event-handler (read the performance section in the manual)
-# server.event-handler = "freebsd-kqueue" # needed on OS X
-
-# mimetype mapping
-mimetype.assign = (
- ".pdf" => "application/pdf",
- ".sig" => "application/pgp-signature",
- ".spl" => "application/futuresplash",
- ".class" => "application/octet-stream",
- ".ps" => "application/postscript",
- ".torrent" => "application/x-bittorrent",
- ".dvi" => "application/x-dvi",
- ".gz" => "application/x-gzip",
- ".pac" => "application/x-ns-proxy-autoconfig",
- ".swf" => "application/x-shockwave-flash",
- ".tar.gz" => "application/x-tgz",
- ".tgz" => "application/x-tgz",
- ".tar" => "application/x-tar",
- ".zip" => "application/zip",
- ".mp3" => "audio/mpeg",
- ".m3u" => "audio/x-mpegurl",
- ".wma" => "audio/x-ms-wma",
- ".wax" => "audio/x-ms-wax",
- ".ogg" => "application/ogg",
- ".wav" => "audio/x-wav",
- ".gif" => "image/gif",
- ".jar" => "application/x-java-archive",
- ".jpg" => "image/jpeg",
- ".jpeg" => "image/jpeg",
- ".png" => "image/png",
- ".xbm" => "image/x-xbitmap",
- ".xpm" => "image/x-xpixmap",
- ".xwd" => "image/x-xwindowdump",
- ".css" => "text/css",
- ".html" => "text/html",
- ".htm" => "text/html",
- ".js" => "text/javascript",
- ".asc" => "text/plain",
- ".c" => "text/plain",
- ".cpp" => "text/plain",
- ".log" => "text/plain",
- ".conf" => "text/plain",
- ".text" => "text/plain",
- ".txt" => "text/plain",
- ".dtd" => "text/xml",
- ".xml" => "text/xml",
- ".mpeg" => "video/mpeg",
- ".mpg" => "video/mpeg",
- ".mov" => "video/quicktime",
- ".qt" => "video/quicktime",
- ".avi" => "video/x-msvideo",
- ".asf" => "video/x-ms-asf",
- ".asx" => "video/x-ms-asf",
- ".wmv" => "video/x-ms-wmv",
- ".bz2" => "application/x-bzip",
- ".tbz" => "application/x-bzip-compressed-tar",
- ".tar.bz2" => "application/x-bzip-compressed-tar",
- # default mime type
- "" => "application/octet-stream",
- )
-
-# Use the "Content-Type" extended attribute to obtain mime type if possible
-#mimetype.use-xattr = "enable"
-
-#### accesslog module
-accesslog.filename = "%(path)/access.log"
-
-url.access-deny = ( "~", ".inc" )
-
-$HTTP["url"] =~ "\.pdf$" {
- server.range-requests = "disable"
-}
-static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
-
-server.pid-file = "%(path)/lighttpd.pid"
-
-server.bind = "%(host)"
-server.port = %(port)
-
-#server.document-root = "/home/user/public_html"
-fastcgi.server = (
- "/pyload.fcgi" => (
- "main" => (
- "host" => "127.0.0.1",
- "port" => 9295,
- "check-local" => "disable",
- "docroot" => "/",
- )
- ),
-)
-
-alias.url = (
- "/media/" => "%(media)/",
- "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/",
-)
-
-url.rewrite-once = (
- "^(/media.*)$" => "$1",
- "^(/admin/media.*)$" => "$1",
- "^/favicon\.ico$" => "/media/img/favicon.ico",
- "^(/pyload.fcgi.*)$" => "$1",
- "^(/.*)$" => "/pyload.fcgi$1",
-)
-
-%(ssl) \ No newline at end of file
diff --git a/module/web/servers/nginx_default.conf b/module/web/servers/nginx_default.conf
deleted file mode 100644
index b4ebd1e02..000000000
--- a/module/web/servers/nginx_default.conf
+++ /dev/null
@@ -1,87 +0,0 @@
-daemon off;
-pid %(path)/nginx.pid;
-worker_processes 2;
-
-error_log %(path)/error.log info;
-
-events {
- worker_connections 1024;
- use epoll;
-}
-
-http {
- include /etc/nginx/conf/mime.types;
- default_type application/octet-stream;
-
- %(ssl)
-
- log_format main
- '$remote_addr - $remote_user [$time_local] '
- '"$request" $status $bytes_sent '
- '"$http_referer" "$http_user_agent" '
- '"$gzip_ratio"';
-
- error_log %(path)/error.log info;
-
- client_header_timeout 10m;
- client_body_timeout 10m;
- send_timeout 10m;
-
- client_body_temp_path %(path)/client_body_temp;
- proxy_temp_path %(path)/proxy_temp;
- fastcgi_temp_path %(path)/fastcgi_temp;
-
-
- connection_pool_size 256;
- client_header_buffer_size 1k;
- large_client_header_buffers 4 2k;
- request_pool_size 4k;
-
- gzip on;
- gzip_min_length 1100;
- gzip_buffers 4 8k;
- gzip_types text/plain;
-
- output_buffers 1 32k;
- postpone_output 1460;
-
- sendfile on;
- tcp_nopush on;
- tcp_nodelay on;
-
- keepalive_timeout 75 20;
-
- ignore_invalid_headers on;
-
- server {
- listen %(port);
- server_name %(host);
- # site_media - folder in uri for static files
- location ^~ /media {
- root %(media)/..;
- }
- location ^~ /admin/media {
- root /usr/lib/python%(version)/site-packages/django/contrib;
- }
-location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
- access_log off;
- expires 30d;
-}
- location / {
- # host and port to fastcgi server
- fastcgi_pass 127.0.0.1:9295;
- fastcgi_param PATH_INFO $fastcgi_script_name;
- fastcgi_param REQUEST_METHOD $request_method;
- fastcgi_param QUERY_STRING $query_string;
- fastcgi_param CONTENT_TYPE $content_type;
- fastcgi_param CONTENT_LENGTH $content_length;
- fastcgi_param SERVER_NAME $server_name;
- fastcgi_param SERVER_PORT $server_port;
- fastcgi_param SERVER_PROTOCOL $server_protocol;
- fastcgi_pass_header Authorization;
- fastcgi_intercept_errors off;
- }
- access_log %(path)/access.log main;
- error_log %(path)/error.log;
- }
- }
diff --git a/module/web/templates/500.html b/module/web/templates/500.html
deleted file mode 100644
index e15090b66..000000000
--- a/module/web/templates/500.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
- <title>Server Error</title>
-</head>
-<body>
-<h1>Server Error occured. Please enable debug mode to get a more detailed report.</h1>
-</body>
-</html>
diff --git a/module/web/templates/default/admin.html b/module/web/templates/default/admin.html
deleted file mode 100644
index b049411fd..000000000
--- a/module/web/templates/default/admin.html
+++ /dev/null
@@ -1,98 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
- <script type="text/javascript" src="media/js/admin.js"></script>
-{% endblock %}
-
-
-{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Administrate") }}{% endblock %}
-
-{% block content %}
-
- <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
- <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
- <br>
- <br>
-
- {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br>
- {{ _("Important: Admin user have always all permissions!") }}
-
- <form action="" method="POST">
- <table class="settable wide">
- <thead style="font-size: 11px">
- <th>
- {{ _("Name") }}
- </th>
- <th>
- {{ _("Change Password") }}
- </th>
- <th>
- {{ _("Admin") }}
- </th>
- <th>
- {{ _("Permissions") }}
- </th>
- </thead>
-
- {% for name, data in users.iteritems() %}
- <tr>
- <td>{{ name }}</td>
- <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
- <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
- checked="True" {% endif %}"></td>
- <td>
- <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
- {% for perm in permlist %}
- {% if data.perms|getitem(perm) %}
- <option selected="selected">{{ perm }}</option>
- {% else %}
- <option>{{ perm }}</option>
- {% endif %}
- {% endfor %}
- </select>
- </td>
- </tr>
- {% endfor %}
-
-
- </table>
-
- <button class="styled_button" type="submit">{{ _("Submit") }}</button>
- </form>
-{% endblock %}
-{% block hidden %}
- <div id="password_box" class="window_box" style="z-index: 2">
- <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
- <h1>{{ _("Change Password") }}</h1>
-
- <p>{{ _("Enter your current and desired Password.") }}</p>
- <label for="user_login">{{ _("User") }}
- <span class="small">{{ _("Your username.") }}</span>
- </label>
- <input id="user_login" name="user_login" type="text" size="20"/>
-
- <label for="login_current_password">{{ _("Current password") }}
- <span class="small">{{ _("The password for this account.") }}</span>
- </label>
- <input id="login_current_password" name="login_current_password" type="password" size="20"/>
-
- <label for="login_new_password">{{ _("New password") }}
- <span class="small">{{ _("The new password.") }}</span>
- </label>
- <input id="login_new_password" name="login_new_password" type="password" size="20"/>
-
- <label for="login_new_password2">{{ _("New password (repeat)") }}
- <span class="small">{{ _("Please repeat the new password.") }}</span>
- </label>
- <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
-
-
- <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
- <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
- <div class="spacer"></div>
-
- </form>
-
- </div>
-{% endblock %}
diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html
deleted file mode 100644
index 147c08a37..000000000
--- a/module/web/templates/default/base.html
+++ /dev/null
@@ -1,180 +0,0 @@
-<?xml version="1.0" ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/default.css"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/window.css"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/MooDialog.css"/>
-
-<script type="text/javascript" src="/media/js/mootools-core-1.4.1.js"></script>
-<script type="text/javascript" src="/media/js/mootools-more-1.4.0.1.js"></script>
-<script type="text/javascript" src="/media/js/MooDialog_static.js"></script>
-<script type="text/javascript" src="/media/js/purr_static.js"></script>
-
-
-<script type="text/javascript" src="/media/js/base.js"></script>
-
-<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
-
-{% block head %}
-{% endblock %}
-</head>
-<body>
-<a class="anchor" name="top" id="top"></a>
-
-<div id="head-panel">
-
-
- <div id="head-search-and-login">
- {% block headpanel %}
-
- {% if user.is_authenticated %}
-
-
-{% if update %}
-<span>
-<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
-</span>
-{% endif %}
-
-
-{% if plugins %}
-<span>
-<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
-</span>
-{% endif %}
-
-<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
-<img src="/media/default/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
-<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
-</span>
-
- <img src="/media/default/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
- <ul id="user-actions">
- <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
- {% if user.is_admin %}
- <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
- {% endif %}
- <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
-
- </ul>
-{% else %}
- <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
-{% endif %}
-
- {% endblock %}
- </div>
-
- <a href="/"><img id="head-logo" src="/media/default/img/pyload-logo-edited3.5-new-font-small.png" alt="pyLoad" /></a>
-
- <div id="head-menu">
- <ul>
-
- {% macro selected(name, right=False) -%}
- {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
- {% if not name in url and right -%}class="right"{%- endif %}
- {%- endmacro %}
-
-
- {% block menu %}
- <li>
- <a href="/" title=""><img src="/media/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
- </li>
- <li {{ selected('queue') }}>
- <a href="/queue/" title=""><img src="/media/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
- </li>
- <li {{ selected('collector') }}>
- <a href="/collector/" title=""><img src="/media/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
- </li>
- <li {{ selected('downloads') }}>
- <a href="/downloads/" title=""><img src="/media/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
- </li>
-{# <li {{ selected('filemanager') }}>#}
-{# <a href="/filemanager/" title=""><img src="/media/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
-{# </li>#}
- <li {{ selected('logs', True) }}>
- <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
- </li>
- <li {{ selected('settings', True) }}>
- <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
- </li>
- {% endblock %}
-
- </ul>
- </div>
-
- <div style="clear:both;"></div>
-</div>
-
-{% if perms.STATUS %}
-<ul id="page-actions2">
- <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
- <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
- <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
- <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
-</ul>
-{% endif %}
-
-{% if perms.LIST %}
-<ul id="page-actions">
- <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
- <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
- <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
- <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
- <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
-</ul>
-{% endif %}
-
-{% block pageactions %}
-{% endblock %}
-<br/>
-
-<div id="body-wrapper" class="dokuwiki">
-
-<div id="content" lang="en" dir="ltr">
-
-<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
-
-{% block statusbar %}
-{% endblock %}
-
-
-<br/>
-
-<div class="level1" style="clear:both">
-</div>
-<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
-
-{% for message in messages %}
- <b><p>{{message}}</p></b>
-{% endfor %}
-
-<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
- <img src="/media/default/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
- {{_("loading")}}
-</div>
-
-{% block content %}
-{% endblock content %}
-
- <hr style="clear: both;" />
-
-<div id="foot">&copy; 2008-2011 pyLoad Team
-<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
-<!--<div class="breadcrumbs"></div>-->
-
-</div>
-</div>
-</div>
-
-<div style="display: none;">
- {% include "default/window.html" %}
- {% include "default/captcha.html" %}
- {% block hidden %}
- {% endblock %}
-</div>
-</body>
-</html>
diff --git a/module/web/templates/default/captcha.html b/module/web/templates/default/captcha.html
deleted file mode 100644
index 288375b76..000000000
--- a/module/web/templates/default/captcha.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!-- Captcha box -->
-<div id="cap_box" class="window_box">
-
- <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
-
- <h1>{{_("Captcha reading")}}</h1>
- <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
-
- <div id="cap_textual">
-
- <input id="cap_id" name="cap_id" type="hidden" value="" />
-
- <label>{{_("Captcha")}}
- <span class="small">{{_("The captcha.")}}</span>
- </label>
- <span class="cont">
- <img id="cap_textual_img" src="">
- </span>
-
- <label>{{_("Text")}}
- <span class="small">{{_("Input the text on the captcha.")}}</span>
- </label>
- <input id="cap_result" name="cap_result" type="text" size="20" />
-
- </div>
-
- <div id="cap_positional" style="text-align: center">
- <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
- </div>
-
- <div id="button_bar" style="text-align: center">
- <span>
- <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
- <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
- </span>
- </div>
-
- <div class="spacer"></div>
-
- </form>
-
-</div> \ No newline at end of file
diff --git a/module/web/templates/default/downloads.html b/module/web/templates/default/downloads.html
deleted file mode 100644
index 450b8a102..000000000
--- a/module/web/templates/default/downloads.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}Downloads - {{super()}} {% endblock %}
-
-{% block subtitle %}
-{{_("Downloads")}}
-{% endblock %}
-
-{% block content %}
-
-<ul>
- {% for folder in files.folder %}
- <li>
- {{ folder.name }}
- <ul>
- {% for file in folder.files %}
- <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
- {% endfor %}
- </ul>
- </li>
- {% endfor %}
-
- {% for file in files.files %}
- <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
- {% endfor %}
-
-</ul>
-
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/filemanager.html b/module/web/templates/default/filemanager.html
deleted file mode 100644
index 97095c13e..000000000
--- a/module/web/templates/default/filemanager.html
+++ /dev/null
@@ -1,80 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
-
-<script type="text/javascript" src="/filemanager_ui.js"></script>
-
-<script type="text/javascript">
-
-document.addEvent("domready", function(){
- var fmUI = new FilemanagerUI("url",1);
-});
-</script>
-{% endblock %}
-
-{% block title %}Downloads - {{super()}} {% endblock %}
-
-
-{% block subtitle %}
-{{_("FileManager")}}
-{% endblock %}
-
-{% macro display_file(file) %}
- <li class="file">
- <input type="hidden" name="path" class="path" value="{{ file.path }}" />
- <input type="hidden" name="name" class="name" value="{{ file.name }}" />
- <span>
- <b>{{ file.name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- </span>
- </span>
- </li>
-{%- endmacro %}
-
-{% macro display_folder(fld, open = false) -%}
- <li class="folder">
- <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
- <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
- <span>
- <b>{{ fld.name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/add_folder.png" />
- </span>
- </span>
- {% if (fld.folders|length + fld.files|length) > 0 %}
- {% if open %}
- <ul>
- {% else %}
- <ul style="display:none">
- {% endif %}
- {% for child in fld.folders %}
- {{ display_folder(child) }}
- {% endfor %}
- {% for child in fld.files %}
- {{ display_file(child) }}
- {% endfor %}
- </ul>
- {% else %}
- <div style="display:none">{{ _("Folder is empty") }}</div>
- {% endif %}
- </li>
-{%- endmacro %}
-
-{% block content %}
-
-<div style="clear:both"><!-- --></div>
-
-<ul id="directories-list">
-{{ display_folder(root, true) }}
-</ul>
-
-{% include "default/rename_directory.html" %}
-
-{% endblock %}
diff --git a/module/web/templates/default/folder.html b/module/web/templates/default/folder.html
deleted file mode 100644
index b385e80cb..000000000
--- a/module/web/templates/default/folder.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<li class="folder">
- <input type="hidden" name="path" class="path" value="{{ path }}" />
- <input type="hidden" name="name" class="name" value="{{ name }}" />
- <span>
- <b>{{ name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/add_folder.png" />
- </span>
- </span>
- <div style="display:none">{{ _("Folder is empty") }}</div>
-</li> \ No newline at end of file
diff --git a/module/web/templates/default/home.html b/module/web/templates/default/home.html
deleted file mode 100644
index 7359e326c..000000000
--- a/module/web/templates/default/home.html
+++ /dev/null
@@ -1,266 +0,0 @@
-{% extends 'default/base.html' %}
-{% block head %}
-
-<script type="text/javascript">
-
-var em;
-var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
-
-document.addEvent("domready", function(){
- em = new EntryManager();
-});
-
-var EntryManager = new Class({
- initialize: function(){
- this.json = new Request.JSON({
- url: "json/links",
- secure: false,
- async: true,
- onSuccess: this.update.bind(this),
- initialDelay: 0,
- delay: 2500,
- limit: 30000
- });
-
- this.ids = [{% for link in content %}
- {% if forloop.last %}
- {{ link.id }}
- {% else %}
- {{ link.id }},
- {% endif %}
- {% endfor %}];
-
- this.entries = [];
- this.container = $('LinksAktiv');
-
- this.parseFromContent();
-
- this.json.startTimer();
- },
- parseFromContent: function(){
- this.ids.each(function(id,index){
- var entry = new LinkEntry(id);
- entry.parse();
- this.entries.push(entry)
- }, this);
- },
- update: function(data){
-
- try{
- this.ids = this.entries.map(function(item){
- return item.fid
- });
-
- this.ids.filter(function(id){
- return !this.ids.contains(id)
- },data).each(function(id){
- var index = this.ids.indexOf(id);
- this.entries[index].remove();
- this.entries = this.entries.filter(function(item){return item.fid != this},id);
- this.ids = this.ids.erase(id)
- }, this);
-
- data.links.each(function(link, i){
- if (this.ids.contains(link.fid)){
-
- var index = this.ids.indexOf(link.fid);
- this.entries[index].update(link)
-
- }else{
- var entry = new LinkEntry(link.fid);
- entry.insert(link);
- this.entries.push(entry);
- this.ids.push(link.fid);
- this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
- entry.fade.start('opacity', 1);
- entry.fadeBar.start('opacity', 1);
-
- }
- }, this)
- }catch(e){
- //alert(e)
- }
- }
-});
-
-
-var LinkEntry = new Class({
- initialize: function(id){
- this.fid = id;
- this.id = id;
- },
- parse: function(){
- this.elements = {
- tr: $("link_{id}".substitute({id: this.id})),
- name: $("link_{id}_name".substitute({id: this.id})),
- status: $("link_{id}_status".substitute({id: this.id})),
- info: $("link_{id}_info".substitute({id: this.id})),
- bleft: $("link_{id}_bleft".substitute({id: this.id})),
- percent: $("link_{id}_percent".substitute({id: this.id})),
- remove: $("link_{id}_remove".substitute({id: this.id})),
- pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
- pgb: $("link_{id}_pgb".substitute({id: this.id}))
- };
- this.initEffects();
- },
- insert: function(item){
- try{
-
- this.elements = {
- tr: new Element('tr', {
- 'html': '',
- 'styles':{
- 'opacity': 0
- }
- }),
- name: new Element('td', {
- 'html': item.name
- }),
- status: new Element('td', {
- 'html': item.statusmsg
- }),
- info: new Element('td', {
- 'html': item.info
- }),
- bleft: new Element('td', {
- 'html': humanFileSize(item.size)
- }),
- percent: new Element('span', {
- 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
- }),
- remove: new Element('img',{
- 'src': 'media/default/img/control_cancel.png',
- 'styles':{
- 'vertical-align': 'middle',
- 'margin-right': '-20px',
- 'margin-left': '5px',
- 'margin-top': '-2px',
- 'cursor': 'pointer'
- }
- }),
- pgbTr: new Element('tr', {
- 'html':''
- }),
- pgb: new Element('div', {
- 'html': '&nbsp;',
- 'styles':{
- 'height': '4px',
- 'width': item.percent+'%',
- 'background-color': '#ddd'
- }
- })
- };
-
- this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
- this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
- this.initEffects();
- }catch(e){
- alert(e)
- }
- },
- initEffects: function(){
- if(!operafix)
- this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
- this.fade = new Fx.Tween(this.elements.tr);
- this.fadeBar = new Fx.Tween(this.elements.pgbTr);
-
- this.elements.remove.addEvent('click', function(){
- new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
- }.bind(this));
-
- },
- update: function(item){
- this.elements.name.set('text', item.name);
- this.elements.status.set('text', item.statusmsg);
- this.elements.info.set('text', item.info);
- this.elements.bleft.set('text', item.format_size);
- this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
- if(!operafix)
- {
- this.bar.start({
- 'width': item.percent,
- 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
- });
- }
- else
- {
- this.elements.pgb.set(
- 'styles', {
- 'height': '4px',
- 'width': item.percent+'%',
- 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
- });
- }
- },
- remove: function(){
- this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
- this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
-
- }
- });
-</script>
-
-{% endblock %}
-
-{% block subtitle %}
-{{_("Active Downloads")}}
-{% endblock %}
-
-{% block menu %}
-<li class="selected">
- <a href="/" title=""><img src="/media/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
-</li>
-<li>
- <a href="/queue/" title=""><img src="/media/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
-</li>
-<li>
- <a href="/collector/" title=""><img src="/media/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
-</li>
-<li>
- <a href="/downloads/" title=""><img src="/media/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
-</li>
-{#<li>#}
-{# <a href="/filemanager/" title=""><img src="/media/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
-{#</li>#}
-<li class="right">
- <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
-</li>
-<li class="right">
- <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
-</li>
-{% endblock %}
-
-{% block content %}
-<table width="100%" class="queue">
- <thead>
- <tr class="header">
- <th>{{_("Name")}}</th>
- <th>{{_("Status")}}</th>
- <th>{{_("Information")}}</th>
- <th>{{_("Size")}}</th>
- <th>{{_("Progress")}}</th>
- </tr>
- </thead>
- <tbody id="LinksAktiv">
-
- {% for link in content %}
- <tr id="link_{{ link.id }}">
- <td id="link_{{ link.id }}_name">{{ link.name }}</td>
- <td id="link_{{ link.id }}_status">{{ link.status }}</td>
- <td id="link_{{ link.id }}_info">{{ link.info }}</td>
- <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
- <td>
- <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
- <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="media/default/img/control_cancel.png"/>
- </td>
- </tr>
- <tr id="link_{{ link.id }}_pgb_tr">
- <td colspan="5">
- <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
- </td>
- </tr>
- {% endfor %}
-
- </tbody>
-</table>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/info.html b/module/web/templates/default/info.html
deleted file mode 100644
index 77ae57376..000000000
--- a/module/web/templates/default/info.html
+++ /dev/null
@@ -1,81 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
- <script type="text/javascript">
- window.addEvent("domready", function() {
- var ul = new Element('ul#twitter_update_list');
- var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.js][type=text/javascript]');
- var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
- $("twitter").adopt(ul, script1, script2);
- });
- </script>
-{% endblock %}
-
-{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Information") }}{% endblock %}
-
-{% block content %}
- <h3>{{ _("News") }}</h3>
- <div id="twitter"></div>
-
- <h3>{{ _("Support") }}</h3>
-
- <ul>
- <li style="font-weight:bold;">
- <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
- &nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="http://forum.pyload.org/" target="_blank">Forum</a>
- &nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="http://pyload.org/irc/" target="_blank">Chat</a>
- </li>
- <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
- <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
- <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
-
- </ul>
-
- <h3>{{ _("System") }}</h3>
- <table class="system">
- <tr>
- <td>{{ _("Python:") }}</td>
- <td>{{ python }}</td>
- </tr>
- <tr>
- <td>{{ _("OS:") }}</td>
- <td>{{ os }}</td>
- </tr>
- <tr>
- <td>{{ _("pyLoad version:") }}</td>
- <td>{{ version }}</td>
- </tr>
- <tr>
- <td>{{ _("Installation Folder:") }}</td>
- <td>{{ folder }}</td>
- </tr>
- <tr>
- <td>{{ _("Config Folder:") }}</td>
- <td>{{ config }}</td>
- </tr>
- <tr>
- <td>{{ _("Download Folder:") }}</td>
- <td>{{ download }}</td>
- </tr>
- <tr>
- <td>{{ _("Free Space:") }}</td>
- <td>{{ freespace }}</td>
- </tr>
- <tr>
- <td>{{ _("Language:") }}</td>
- <td>{{ language }}</td>
- </tr>
- <tr>
- <td>{{ _("Webinterface Port:") }}</td>
- <td>{{ webif }}</td>
- </tr>
- <tr>
- <td>{{ _("Remote Interface Port:") }}</td>
- <td>{{ remote }}</td>
- </tr>
- </table>
-
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/login.html b/module/web/templates/default/login.html
deleted file mode 100644
index 9e91ad309..000000000
--- a/module/web/templates/default/login.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
-
-{% block content %}
-
-<div class="centeralign">
-<form action="" method="post" accept-charset="utf-8" id="login">
- <div class="no">
- <input type="hidden" name="do" value="login" />
- <fieldset>
- <legend>Login</legend>
- <label>
- <span>{{_("Username")}}</span>
- <input type="text" size="20" name="username" />
- </label>
- <br />
- <label>
- <span>{{_("Password")}}</span>
- <input type="password" size="20" name="password" />
- </label>
- <br />
- <input type="submit" value="Login" class="button" />
- </fieldset>
- </div>
-</form>
-
-{% if errors %}
-<p>{{_("Your username and password didn't match. Please try again.")}}</p>
- {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b>
-{% endif %}
-
-</div>
-<br>
-
-{% endblock %}
diff --git a/module/web/templates/default/logout.html b/module/web/templates/default/logout.html
deleted file mode 100644
index d3f07472b..000000000
--- a/module/web/templates/default/logout.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
-<meta http-equiv="refresh" content="3; url=/">
-{% endblock %}
-
-{% block content %}
-<p><b>{{_("You were successfully logged out.")}}</b></p>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/logs.html b/module/web/templates/default/logs.html
deleted file mode 100644
index d6288df0e..000000000
--- a/module/web/templates/default/logs.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
-{% block subtitle %}{{_("Logs")}}{% endblock %}
-{% block head %}
-<link rel="stylesheet" type="text/css" href="/media/default/css/log.css"/>
-{% endblock %}
-
-{% block content %}
-<div style="clear: both;"></div>
-
-<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
-<div class="logperpage">
- <form id="logform1" action="" method="POST">
- <label for="reversed">Reversed:</label>
- <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
- <label for="perpage">Lines per page:</label>
- <select name="perpage" onchange="this.form.submit();">
- {% for value in perpage_p %}
- <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
- {% endfor %}
- </select>
- </form>
-</div>
-<div class="logwarn">{{warning}}</div>
-<div style="clear: both;"></div>
-<div class="logdiv">
- <table class="logtable" cellpadding="0" cellspacing="0">
- {% for line in log %}
- <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
- {% endfor %}
- </table>
-</div>
-<div class="logform">
-<form id="logform2" action="" method="POST">
- <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
- <input type="submit" value="ok" />
-</form>
-</div>
-<div style="clear: both; height: 10px;">&nbsp; </div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/pathchooser.html b/module/web/templates/default/pathchooser.html
deleted file mode 100644
index d00637055..000000000
--- a/module/web/templates/default/pathchooser.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<html>
-<head>
- <script class="javascript">
- function chosen()
- {
- opener.ifield.value = document.forms[0].p.value;
- close();
- }
- function exit()
- {
- close();
- }
- function setInvalid() {
- document.forms[0].send.disabled = 'disabled';
- document.forms[0].p.style.color = '#FF0000';
- }
- function setValid() {
- document.forms[0].send.disabled = '';
- document.forms[0].p.style.color = '#000000';
- }
- function setFile(file)
- {
- document.forms[0].p.value = file;
- setValid();
-
- }
- </script>
- <link rel="stylesheet" type="text/css" href="/media/default/css/pathchooser.css"/>
-</head>
-<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
-<center>
- <div id="paths">
- <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
- <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
- <input type="submit" value="Ok" name="send">
- </form>
-
- {% if type == 'folder' %}
- <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
- {% else %}
- <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
- {% endif %}
- </div>
- <table border="0" cellspacing="0" cellpadding="3">
- <tr>
- <th>{{_("name")}}</th>
- <th>{{_("size")}}</th>
- <th>{{_("type")}}</th>
- <th>{{_("last modified")}}</th>
- </tr>
- {% if parentdir %}
- <tr>
- <td colspan="4">
- <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
- </td>
- </tr>
- {% endif %}
-{% for file in files %}
- <tr>
- {% if type == 'folder' %}
- <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
- {% else %}
- <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
- {% endif %}
- <td class="size">{{ file.size|float|filesizeformat }}</td>
- <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
- <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
- <tr>
-<!-- <tr>
- <td colspan="4">{{_("no content")}}</td>
- </tr> -->
-{% endfor %}
- </table>
- </center>
-</body>
-</html> \ No newline at end of file
diff --git a/module/web/templates/default/queue.html b/module/web/templates/default/queue.html
deleted file mode 100644
index 046abbe49..000000000
--- a/module/web/templates/default/queue.html
+++ /dev/null
@@ -1,104 +0,0 @@
-{% extends 'default/base.html' %}
-{% block head %}
-
-<script type="text/javascript" src="/media/js/package_ui.js"></script>
-
-<script type="text/javascript">
-
-document.addEvent("domready", function(){
- var pUI = new PackageUI("url", {{ target }});
-});
-</script>
-{% endblock %}
-
-{% if target %}
- {% set name = _("Queue") %}
-{% else %}
- {% set name = _("Collector") %}
-{% endif %}
-
-{% block title %}{{name}} - {{super()}} {% endblock %}
-{% block subtitle %}{{name}}{% endblock %}
-
-{% block pageactions %}
-<ul id="page-actions-more">
- <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
- <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
-</ul>
-{% endblock %}
-
-{% block content %}
-{% autoescape true %}
-
-<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
-{% for package in content %}
- <li>
-<div id="package_{{package.pid}}" class="package">
- <div class="order" style="display: none;">{{ package.order }}</div>
-
- <div class="packagename" style="cursor: pointer">
- <img class="package_drag" src="/media/default/img/folder.png" style="cursor: move; margin-bottom: -2px">
- <span class="name">{{package.name}}</span>
- &nbsp;&nbsp;
- <span class="buttons" style="opacity:0">
- <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/arrow_refresh.png" />
- &nbsp;&nbsp;
- <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/package_go.png" />
- </span>
- </div>
- {% set progress = (package.linksdone * 100) / package.linkstotal %}
-
- <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
- <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
- <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
- {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
- <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
- {{ package.linksdone }} / {{ package.linkstotal }}</label>
- </div>
- <div style="clear: both; margin-bottom: -10px"></div>
-
- <div id="children_{{package.pid}}" style="display: none;" class="children">
- <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
- <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
- </ul>
- </div>
-</div>
- </li>
-{% endfor %}
-</ul>
-{% endautoescape %}
-{% endblock %}
-
-{% block hidden %}
-<div id="pack_box" class="window_box" style="z-index: 2">
- <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
- <h1>{{_("Edit Package")}}</h1>
- <p>{{_("Edit the package detais below.")}}</p>
- <input name="pack_id" id="pack_id" type="hidden" value=""/>
- <label for="pack_name">{{_("Name")}}
- <span class="small">{{_("The name of the package.")}}</span>
- </label>
- <input id="pack_name" name="pack_name" type="text" size="20" />
-
- <label for="pack_folder">{{_("Folder")}}
- <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
- </label>
- <input id="pack_folder" name="pack_folder" type="text" size="20" />
-
- <label for="pack_pws">{{_("Password")}}
- <span class="small">{{_("List of passwords used for unrar.")}}</span>
- </label>
- <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
-
- <button type="submit">{{_("Submit")}}</button>
- <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
- <div class="spacer"></div>
-
- </form>
-
-</div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html
deleted file mode 100644
index a4443025a..000000000
--- a/module/web/templates/default/settings.html
+++ /dev/null
@@ -1,204 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Config") }}{% endblock %}
-
-{% block head %}
- <script type="text/javascript" src="/media/js/tinytab_static.js"></script>
- <script type="text/javascript" src="/media/js/MooDropMenu_static.js"></script>
- <script type="text/javascript" src="/media/js/settings.js"></script>
-
-{% endblock %}
-
-{% block content %}
-
- <ul id="toptabs" class="tabs">
- <li><a class="selected" href="#">{{ _("General") }}</a></li>
- <li><a href="#">{{ _("Plugins") }}</a></li>
- <li><a href="#">{{ _("Accounts") }}</a></li>
- </ul>
-
- <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
-
- </div>
-
- <span id="tabs-body">
- <!-- General -->
- <span id="general" class="active tabContent">
- <ul class="nav tabs">
- <li class>
- <a>Menu</a>
- <ul id="general-menu">
- {% for entry,name in conf.general %}
- <nobr>
- <li id="general|{{ entry }}">{{ name }}</li>
- </nobr>
- <br>
- {% endfor %}
- </ul>
- </li>
- </ul>
-
- <form id="general_form" action="" method="POST" autocomplete="off">
- <span id="general_form_content">
- <br>
- <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
- <br>
- </span>
-
- <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
- </form>
- </span>
-
- <!-- Plugins -->
- <span id="plugins" class="tabContent">
- <ul class="nav tabs">
- <li class>
- <a>Menu</a>
- <ul id="plugin-menu">
- {% for entry,name in conf.plugin %}
- <nobr>
- <li id="plugin|{{ entry }}">{{ name }}</li>
- </nobr>
- <br>
- {% endfor %}
- </ul>
- </li>
- </ul>
-
-
- <form id="plugin_form" action="" method="POST" autocomplete="off">
-
- <span id="plugin_form_content">
- <br>
- <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
- <br>
- </span>
- <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
- </form>
-
- </span>
-
- <!-- Accounts -->
- <span id="accounts" class="tabContent">
- <form id="account_form" action="/json/update_accounts" method="POST">
-
- <table class="settable wide">
-
- <thead>
- <tr>
- <th>{{ _("Plugin") }}</th>
- <th>{{ _("Name") }}</th>
- <th>{{ _("Password") }}</th>
- <th>{{ _("Status") }}</th>
- <th>{{ _("Premium") }}</th>
- <th>{{ _("Valid until") }}</th>
- <th>{{ _("Traffic left") }}</th>
- <th>{{ _("Time") }}</th>
- <th>{{ _("Max Parallel") }}</th>
- <th>{{ _("Delete?") }}</th>
- </tr>
- </thead>
-
-
- {% for account in conf.accs %}
- {% set plugin = account.type %}
- <tr>
- <td>
- <span style="padding:5px">{{ plugin }}</span>
- </td>
-
- <td><label for="{{plugin}}|password;{{account.login}}"
- style="color:#424242;">{{ account.login }}</label></td>
- <td>
- <input id="{{plugin}}|password;{{account.login}}"
- name="{{plugin}}|password;{{account.login}}"
- type="password" value="{{account.password}}" size="12"/>
- </td>
- <td>
- {% if account.valid %}
- <span style="font-weight: bold; color: #006400;">
- {{ _("valid") }}
- {% else %}
- <span style="font-weight: bold; color: #8b0000;">
- {{ _("not valid") }}
- {% endif %}
- </span>
- </td>
- <td>
- {% if account.premium %}
- <span style="font-weight: bold; color: #006400;">
- {{ _("yes") }}
- {% else %}
- <span style="font-weight: bold; color: #8b0000;">
- {{ _("no") }}
- {% endif %}
- </span>
- </td>
- <td>
- <span style="font-weight: bold;">
- {{ account.validuntil }}
- </span>
- </td>
- <td>
- <span style="font-weight: bold;">
- {{ account.trafficleft }}
- </span>
- </td>
- <td>
- <input id="{{plugin}}|time;{{account.login}}"
- name="{{plugin}}|time;{{account.login}}" type="text"
- size="7" value="{{account.time}}"/>
- </td>
- <td>
- <input id="{{plugin}}|limitdl;{{account.login}}"
- name="{{plugin}}|limitdl;{{account.login}}" type="text"
- size="2" value="{{account.limitdl}}"/>
- </td>
- <td>
- <input id="{{plugin}}|delete;{{account.login}}"
- name="{{plugin}}|delete;{{account.login}}" type="checkbox"
- value="True"/>
- </td>
- </tr>
- {% endfor %}
- </table>
-
- <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
- <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
- </form>
- </span>
- </span>
-{% endblock %}
-{% block hidden %}
-<div id="account_box" class="window_box" style="z-index: 2">
-<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
-<h1>{{_("Add Account")}}</h1>
-<p>{{_("Enter your account data to use premium features.")}}</p>
-<label for="account_login">{{_("Login")}}
-<span class="small">{{_("Your username.")}}</span>
-</label>
-<input id="account_login" name="account_login" type="text" size="20" />
-
-<label for="account_password">{{_("Password")}}
-<span class="small">{{_("The password for this account.")}}</span>
-</label>
-<input id="account_password" name="account_password" type="password" size="20" />
-
-<label for="account_type">{{_("Type")}}
-<span class="small">{{_("Choose the hoster for your account.")}}</span>
-</label>
- <select name=account_type id="account_type">
- {% for type in types|sort %}
- <option value="{{ type }}">{{ type }}</option>
- {% endfor %}
- </select>
-
-<button id="account_add_button" type="submit">{{_("Add")}}</button>
-<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
-<div class="spacer"></div>
-
-</form>
-
-</div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/settings_item.html b/module/web/templates/default/settings_item.html
deleted file mode 100644
index 813383343..000000000
--- a/module/web/templates/default/settings_item.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<table class="settable">
- {% if section.outline %}
- <tr><th colspan="2">{{ section.outline }}</th></tr>
- {% endif %}
- {% for okey, option in section.iteritems() %}
- {% if okey not in ("desc","outline") %}
- <tr>
- <td><label for="{{skey}}|{{okey}}"
- style="color:#424242;">{{ option.desc }}:</label></td>
- <td>
- {% if option.type == "bool" %}
- <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
- <option {% if option.value %} selected="selected"
- {% endif %}value="True">{{ _("on") }}</option>
- <option {% if not option.value %} selected="selected"
- {% endif %}value="False">{{ _("off") }}</option>
- </select>
- {% elif ";" in option.type %}
- <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
- {% for entry in option.list %}
- <option {% if option.value == entry %}
- selected="selected" {% endif %}>{{ entry }}</option>
- {% endfor %}
- </select>
- {% elif option.type == "folder" %}
- <input name="{{skey}}|{{okey}}" type="text"
- id="{{skey}}|{{okey}}" value="{{option.value}}"/>
- <input name="browsebutton" type="button"
- onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
- value="{{_("Browse")}}"/>
- {% elif option.type == "file" %}
- <input name="{{skey}}|{{okey}}" type="text"
- id="{{skey}}|{{okey}}" value="{{option.value}}"/>
- <input name="browsebutton" type="button"
- onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
- value="{{_("Browse")}}"/>
- {% elif option.type == "password" %}
- <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
- type="password" value="{{option.value}}"/>
- {% else %}
- <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
- type="text" value="{{option.value}}"/>
- {% endif %}
- </td>
- </tr>
- {% endif %}
- {% endfor %}
-</table> \ No newline at end of file
diff --git a/module/web/templates/default/setup.html b/module/web/templates/default/setup.html
deleted file mode 100644
index 39ef6f1e8..000000000
--- a/module/web/templates/default/setup.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{ _("Setup") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Setup") }}{% endblock %}
-{% block headpanel %}Welcome to pyLoad{% endblock %}
-{% block menu %}
- <li style="height: 25px"> <!-- Needed to get enough margin -->
- </li>
-{% endblock %}
-
-{% block content %}
- Comming Soon.
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/window.html b/module/web/templates/default/window.html
deleted file mode 100644
index a11323fe0..000000000
--- a/module/web/templates/default/window.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
-
-<div id="add_box" class="window_box">
-<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
-<h1>{{_("Add Package")}}</h1>
-<p>{{_("Paste your links or upload a container.")}}</p>
-<label for="add_name">{{_("Name")}}
-<span class="small">{{_("The name of the new package.")}}</span>
-</label>
-<input id="add_name" name="add_name" type="text" size="20" />
-
-<label for="add_links">{{_("Links")}}
-<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
-<span class="small"> {{ _("Filter urls") }}
-<img alt="URIParsing" Title="Parse Uri" src="/media/default/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
-</span>
-
-</label>
-<textarea rows="5" name="add_links" id="add_links"></textarea>
-
-<label for="add_password">{{_("Password")}}
- <span class="small">{{_("Password for RAR-Archive")}}</span>
-</label>
-<input id="add_password" name="add_password" type="text" size="20">
-
-<label>{{_("File")}}
-<span class="small">{{_("Upload a container.")}}</span>
-</label>
-<input type="file" name="add_file" id="add_file"/>
-
-<label for="add_dest">{{_("Destination")}}
-</label>
-<span class="cont">
- {{_("Queue")}}
- <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
- {{_("Collector")}}
- <input type="radio" name="add_dest" id="add_dest2" value="0"/>
-</span>
-
-<button type="submit">{{_("Add Package")}}</button>
-<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
-<div class="spacer"></div>
-
-</form>
-
-</div> \ No newline at end of file
diff --git a/module/web/utils.py b/module/web/utils.py
deleted file mode 100644
index a89c87558..000000000
--- a/module/web/utils.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this plrogram; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from bottle import request, HTTPError, redirect, ServerAdapter
-
-from webinterface import env, TEMPLATE
-
-from module.Api import has_permission, PERMS, ROLE
-
-def render_to_response(name, args={}, proc=[]):
- for p in proc:
- args.update(p())
-
- t = env.get_template(TEMPLATE + "/" + name)
- return t.render(**args)
-
-
-def parse_permissions(session):
- perms = dict([(x, False) for x in dir(PERMS) if not x.startswith("_")])
- perms["ADMIN"] = False
- perms["is_admin"] = False
-
- if not session.get("authenticated", False):
- return perms
-
- if session.get("role") == ROLE.ADMIN:
- for k in perms.iterkeys():
- perms[k] = True
-
- elif session.get("perms"):
- p = session.get("perms")
- get_permission(perms, p)
-
- return perms
-
-
-def permlist():
- return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"]
-
-
-def get_permission(perms, p):
- """Returns a dict with permission key
-
- :param perms: dictionary
- :param p: bits
- """
- for name in permlist():
- perms[name] = has_permission(p, getattr(PERMS, name))
-
-
-def set_permission(perms):
- """generates permission bits from dictionary
-
- :param perms: dict
- """
- permission = 0
- for name in dir(PERMS):
- if name.startswith("_"): continue
-
- if name in perms and perms[name]:
- permission |= getattr(PERMS, name)
-
- return permission
-
-
-def set_session(request, info):
- s = request.environ.get('beaker.session')
- s["authenticated"] = True
- s["user_id"] = info["id"]
- s["name"] = info["name"]
- s["role"] = info["role"]
- s["perms"] = info["permission"]
- s["template"] = info["template"]
- s.save()
-
- return s
-
-
-def parse_userdata(session):
- return {"name": session.get("name", "Anonymous"),
- "is_admin": True if session.get("role", 1) == 0 else False,
- "is_authenticated": session.get("authenticated", False)}
-
-
-def login_required(perm=None):
- def _dec(func):
- def _view(*args, **kwargs):
- s = request.environ.get('beaker.session')
- if s.get("name", None) and s.get("authenticated", False):
- if perm:
- perms = parse_permissions(s)
- if perm not in perms or not perms[perm]:
- if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
- return HTTPError(403, "Forbidden")
- else:
- return redirect("/nopermission")
-
- return func(*args, **kwargs)
- else:
- if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
- return HTTPError(403, "Forbidden")
- else:
- return redirect("/login")
-
- return _view
-
- return _dec
-
-
-def toDict(obj):
- ret = {}
- for att in obj.__slots__:
- ret[att] = getattr(obj, att)
- return ret
-
-
-class CherryPyWSGI(ServerAdapter):
- def run(self, handler):
- from wsgiserver import CherryPyWSGIServer
-
- server = CherryPyWSGIServer((self.host, self.port), handler)
- server.start()
diff --git a/module/web/webinterface.py b/module/web/webinterface.py
deleted file mode 100644
index ec8b2e56c..000000000
--- a/module/web/webinterface.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-import sys
-import module.common.pylgettext as gettext
-
-import os
-from os.path import join, abspath, dirname, exists
-from os import makedirs
-
-PROJECT_DIR = abspath(dirname(__file__))
-PYLOAD_DIR = abspath(join(PROJECT_DIR, "..", ".."))
-
-sys.path.append(PYLOAD_DIR)
-
-from module import InitHomeDir
-from module.utils import decode, formatSize
-
-import bottle
-from bottle import run, app
-
-from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache
-from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware
-
-SETUP = None
-PYLOAD = None
-
-from module.web import ServerThread
-
-if not ServerThread.core:
- if ServerThread.setup:
- SETUP = ServerThread.setup
- config = SETUP.config
- else:
- raise Exception("Could not access pyLoad Core")
-else:
- PYLOAD = ServerThread.core.api
- config = ServerThread.core.config
-
-from module.common.JsEngine import JsEngine
-
-JS = JsEngine()
-
-TEMPLATE = config.get('webinterface', 'template')
-DL_ROOT = config.get('general', 'download_folder')
-LOG_ROOT = config.get('log', 'log_folder')
-PREFIX = config.get('webinterface', 'prefix')
-
-if PREFIX:
- PREFIX = PREFIX.rstrip("/")
- if not PREFIX.startswith("/"):
- PREFIX = "/" + PREFIX
-
-DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv
-bottle.debug(DEBUG)
-
-cache = join("tmp", "jinja_cache")
-if not exists(cache):
- makedirs(cache)
-
-bcc = FileSystemBytecodeCache(cache, '%s.cache')
-loader = PrefixLoader({
- "default": FileSystemLoader(join(PROJECT_DIR, "templates", "default")),
- 'js': FileSystemLoader(join(PROJECT_DIR, 'media', 'js'))
-})
-
-env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False,
- bytecode_cache=bcc)
-
-from filters import quotepath, path_make_relative, path_make_absolute, truncate, date
-
-env.filters["quotepath"] = quotepath
-env.filters["truncate"] = truncate
-env.filters["date"] = date
-env.filters["path_make_relative"] = path_make_relative
-env.filters["path_make_absolute"] = path_make_absolute
-env.filters["decode"] = decode
-env.filters["type"] = lambda x: str(type(x))
-env.filters["formatsize"] = formatSize
-env.filters["getitem"] = lambda x, y: x.__getitem__(y)
-if PREFIX:
- env.filters["url"] = lambda x: x
-else:
- env.filters["url"] = lambda x: PREFIX + x if x.startswith("/") else x
-
-gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
-translation = gettext.translation("django", join(PYLOAD_DIR, "locale"),
- languages=[config.get("general", "language"), "en"],fallback=True)
-translation.install(True)
-env.install_gettext_translations(translation)
-
-from beaker.middleware import SessionMiddleware
-
-session_opts = {
- 'session.type': 'file',
- 'session.cookie_expires': False,
- 'session.data_dir': './tmp',
- 'session.auto': False
-}
-
-web = StripPathMiddleware(SessionMiddleware(app(), session_opts))
-web = GZipMiddleWare(web)
-
-if PREFIX:
- web = PrefixMiddleware(web, prefix=PREFIX)
-
-import pyload_app
-import json_app
-import cnl_app
-import api_app
-
-def run_simple(host="0.0.0.0", port="8000"):
- run(app=web, host=host, port=port, quiet=True)
-
-
-def run_lightweight(host="0.0.0.0", port="8000"):
- run(app=web, host=host, port=port, quiet=True, server="bjoern")
-
-
-def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""):
- from wsgiserver import CherryPyWSGIServer
-
- if cert and key:
- CherryPyWSGIServer.ssl_certificate = cert
- CherryPyWSGIServer.ssl_private_key = key
-
- CherryPyWSGIServer.numthreads = theads
-
- from utils import CherryPyWSGI
-
- run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True)
-
-
-def run_fcgi(host="0.0.0.0", port="8000"):
- from bottle import FlupFCGIServer
-
- run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True)
-
-
-if __name__ == "__main__":
- run(app=web, port=8001)
diff --git a/pavement.py b/pavement.py
deleted file mode 100644
index ac9a6fa1a..000000000
--- a/pavement.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-from paver.easy import *
-from paver.setuputils import setup
-from paver.doctools import cog
-
-import sys
-import re
-from urllib import urlretrieve
-from subprocess import call, Popen, PIPE
-from zipfile import ZipFile
-
-PROJECT_DIR = path(__file__).dirname()
-sys.path.append(PROJECT_DIR)
-
-options = environment.options
-path('pyload').mkdir()
-
-extradeps = []
-if sys.version_info <= (2, 5):
- extradeps += 'simplejson'
-
-setup(
- name="pyload",
- version="0.4.9",
- description='Fast, lightweight and full featured download manager.',
- long_description=open(PROJECT_DIR / "README").read(),
- keywords = ('pyload', 'download-manager', 'one-click-hoster', 'download'),
- url="http://pyload.org",
- download_url='http://pyload.org/download',
- license='GPL v3',
- author="pyLoad Team",
- author_email="support@pyload.org",
- platforms = ('Any',),
- #package_dir={'pyload': 'src'},
- packages=['pyload'],
- #package_data=find_package_data(),
- #data_files=[],
- include_package_data=True,
- exclude_package_data={'pyload': ['docs*', 'scripts*', 'tests*']}, #exluced from build but not from sdist
- # 'bottle >= 0.10.0' not in list, because its small and contain little modifications
- install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker', 'BeautifulSoup>=3.2, <3.3'] + extradeps,
- extras_require={
- 'SSL': ["pyOpenSSL"],
- 'DLC': ['pycrypto'],
- 'lightweight webserver': ['bjoern'],
- 'RSS plugins': ['feedparser'],
- },
- #setup_requires=["setuptools_hg"],
- entry_points={
- 'console_scripts': [
- 'pyLoadCore = pyLoadCore:main',
- 'pyLoadCli = pyLoadCli:main'
- ]},
- zip_safe=False,
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Topic :: Internet :: WWW/HTTP",
- "Environment :: Console",
- "Environment :: Web Environment",
- "Intended Audience :: End Users/Desktop",
- "License :: OSI Approved :: GNU General Public License (GPL)",
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 2"
- ]
-)
-
-options(
- sphinx=Bunch(
- builddir="_build",
- sourcedir=""
- ),
- get_source=Bunch(
- src="https://bitbucket.org/spoob/pyload/get/tip.zip",
- rev=None,
- clean=False
- ),
- thrift=Bunch(
- path="../thrift/trunk/compiler/cpp/thrift",
- gen=""
- ),
- virtualenv=Bunch(
- dir="env",
- python="python2",
- virtual="virtualenv2",
- ),
- cog=Bunch(
- pattern="*.py",
- )
-)
-
-# xgettext args
-xargs = ["--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyLoad",
- "--package-version=%s" % options.version, "--msgid-bugs-address='bugs@pyload.org'"]
-
-@task
-@needs('cog')
-def html():
- """Build html documentation"""
- module = path("docs") / "module"
- module.rmtree()
- call_task('paver.doctools.html')
-
-
-@task
-@cmdopts([
- ('src=', 's', 'Url to source'),
- ('rev=', 'r', "HG revision"),
- ("clean", 'c', 'Delete old source folder')
-])
-def get_source(options):
- """ Downloads pyload source from bitbucket tip or given rev"""
- if options.rev: options.url = "https://bitbucket.org/spoob/pyload/get/%s.zip" % options.rev
-
- pyload = path("pyload")
-
- if len(pyload.listdir()) and not options.clean:
- return
- elif pyload.exists():
- pyload.rmtree()
-
- urlretrieve(options.src, "pyload_src.zip")
- zip = ZipFile("pyload_src.zip")
- zip.extractall()
- path("pyload_src.zip").remove()
-
- folder = [x for x in path(".").dirs() if x.name.startswith("spoob-pyload-")][0]
- folder.move(pyload)
-
- change_mode(pyload, 0644)
- change_mode(pyload, 0755, folder=True)
-
- for file in pyload.files():
- if file.name.endswith(".py"):
- file.chmod(0755)
-
- (pyload / ".hgtags").remove()
- (pyload / ".hgignore").remove()
- #(pyload / "docs").rmtree()
-
- f = open(pyload / "__init__.py", "wb")
- f.close()
-
- #options.setup.packages = find_packages()
- #options.setup.package_data = find_package_data()
-
-
-@task
-@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist')
-def sdist():
- """ Build source code package with distutils """
-
-
-@task
-@cmdopts([
- ('path=', 'p', 'Thrift path'),
- ('gen=', 'g', "Extra --gen option")
-])
-def thrift(options):
- """ Generate Thrift stubs """
-
- print "add import for TApplicationException manually as long it is not fixed"
-
- outdir = path("module") / "remote" / "thriftbackend"
- (outdir / "gen-py").rmtree()
-
- cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots,dynamic", outdir / "pyload.thrift"]
-
- if options.gen:
- cmd.insert(len(cmd) - 1, "--gen")
- cmd.insert(len(cmd) - 1, options.gen)
-
- print "running", cmd
-
- p = Popen(cmd)
- p.communicate()
-
- (outdir / "thriftgen").rmtree()
- (outdir / "gen-py").move(outdir / "thriftgen")
-
- #create light ttypes
- from module.remote.socketbackend.create_ttypes import main
- main()
-
-@task
-def compile_js():
- """ Compile .coffee files to javascript"""
-
- root = path("module") / "web" / "media" / "js"
- for f in root.glob("*.coffee"):
- print "generate", f
- coffee = Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE)
- yui = Popen(["yuicompressor", "--type", "js"], stdin=coffee.stdout, stdout=PIPE)
- coffee.stdout.close()
- content = yui.communicate()[0]
- with open(root / f.name.replace(".coffee", ".js"), "wb") as js:
- js.write("{% autoescape true %}\n")
- js.write(content)
- js.write("\n{% endautoescape %}")
-
-
-@task
-def generate_locale():
- """ Generates localisation files """
-
- EXCLUDE = ["BeautifulSoup.py", "module/gui", "module/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload",
- "setup.py"]
- makepot("core", path("module"), EXCLUDE, "./pyLoadCore.py\n")
-
- makepot("gui", path("module") / "gui", [], includes="./pyLoadGui.py\n")
- makepot("cli", path("module") / "cli", [], includes="./pyLoadCli.py\n")
- makepot("setup", "", [], includes="./module/setup.py\n")
-
- EXCLUDE = ["ServerThread.py", "web/media/default"]
-
- # strings from js files
- strings = set()
-
- for fi in path("module/web").walkfiles():
- if not fi.name.endswith(".js") and not fi.endswith(".coffee"): continue
- with open(fi, "rb") as c:
- content = c.read()
-
- strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content))
- strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content))
-
- trans = path("module") / "web" / "translations.js"
-
- with open(trans, "wb") as js:
- for s in strings:
- js.write('_("%s")\n' % s)
-
- makepot("django", path("module/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"])
-
- trans.remove()
-
- path("includes.txt").remove()
-
- print "Locale generated"
-
-
-@task
-def tests():
- call(["nosetests2"])
-
-@task
-def virtualenv(options):
- """Setup virtual environment"""
- if path(options.dir).exists():
- return
-
- call([options.virtual, "--no-site-packages", "--python", options.python, options.dir])
- print "$ source %s/bin/activate" % options.dir
-
-
-@task
-def clean_env():
- """Deletes the virtual environment"""
- env = path(options.virtualenv.dir)
- if env.exists():
- env.rmtree()
-
-
-@task
-@needs('generate_setup', 'minilib', 'get_source', 'virtualenv')
-def env_install():
- """Install pyLoad into the virtualenv"""
- venv = options.virtualenv
- call([path(venv.dir) / "bin" / "easy_install", "."])
-
-
-@task
-def clean():
- """Cleans build directories"""
- path("build").rmtree()
- path("dist").rmtree()
-
-
-#helper functions
-
-def walk_trans(path, EXCLUDE, endings=[".py"]):
- result = ""
-
- for f in path.walkfiles():
- if [True for x in EXCLUDE if x in f.dirname().relpath()]: continue
- if f.name in EXCLUDE: continue
-
- for e in endings:
- if f.name.endswith(e):
- result += "./%s\n" % f.relpath()
- break
-
- return result
-
-
-def makepot(domain, p, excludes=[], includes="", endings=[".py"], xxargs=[]):
- print "Generate %s.pot" % domain
-
- f = open("includes.txt", "wb")
- if includes:
- f.write(includes)
-
- if p:
- f.write(walk_trans(path(p), excludes, endings))
-
- f.close()
-
- call(["xgettext", "--files-from=includes.txt", "--default-domain=%s" % domain] + xargs + xxargs)
-
- # replace charset und move file
- with open("%s.po" % domain, "rb") as f:
- content = f.read()
-
- path("%s.po" % domain).remove()
- content = content.replace("charset=CHARSET", "charset=UTF-8")
-
- with open("locale/%s.pot" % domain, "wb") as f:
- f.write(content)
-
-
-def change_owner(dir, uid, gid):
- for p in dir.walk():
- p.chown(uid, gid)
-
-
-def change_mode(dir, mode, folder=False):
- for p in dir.walk():
- if folder and p.isdir():
- p.chmod(mode)
- elif p.isfile() and not folder:
- p.chmod(mode)
diff --git a/pyLoadCli.py b/pyLoadCli.py
deleted file mode 100755
index 079cee19c..000000000
--- a/pyLoadCli.py
+++ /dev/null
@@ -1,590 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-from __future__ import with_statement
-from getopt import GetoptError, getopt
-
-import module.common.pylgettext as gettext
-import os
-from os import _exit
-from os.path import join, exists, abspath, basename
-import sys
-from sys import exit
-from threading import Thread, Lock
-from time import sleep
-from traceback import print_exc
-
-import ConfigParser
-
-from codecs import getwriter
-
-if os.name == "nt":
- enc = "cp850"
-else:
- enc = "utf8"
-
-sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
-
-from module import InitHomeDir
-from module.cli.printer import *
-from module.cli import AddPackage, ManageFiles
-
-from module.Api import Destination
-from module.utils import formatSize, decode
-from module.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed
-from module.lib.Getch import Getch
-from module.lib.rename_process import renameProcess
-
-class Cli:
- def __init__(self, client, command):
- self.client = client
- self.command = command
-
- if not self.command:
- renameProcess('pyLoadCli')
- self.getch = Getch()
- self.input = ""
- self.inputline = 0
- self.lastLowestLine = 0
- self.menuline = 0
-
- self.lock = Lock()
-
- #processor funcions, these will be changed dynamically depending on control flow
- self.headerHandler = self #the download status
- self.bodyHandler = self #the menu section
- self.inputHandler = self
-
- os.system("clear")
- println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- println(2, "")
-
- self.thread = RefreshThread(self)
- self.thread.start()
-
- self.start()
- else:
- self.processCommand()
-
- def reset(self):
- """ reset to initial main menu """
- self.input = ""
- self.headerHandler = self.bodyHandler = self.inputHandler = self
-
- def start(self):
- """ main loop. handle input """
- while True:
- #inp = raw_input()
- inp = self.getch.impl()
- if ord(inp) == 3:
- os.system("clear")
- sys.exit() # ctrl + c
- elif ord(inp) == 13: #enter
- try:
- self.lock.acquire()
- self.inputHandler.onEnter(self.input)
-
- except Exception, e:
- println(2, red(e))
- finally:
- self.lock.release()
-
- elif ord(inp) == 127:
- self.input = self.input[:-1] #backspace
- try:
- self.lock.acquire()
- self.inputHandler.onBackSpace()
- finally:
- self.lock.release()
-
- elif ord(inp) == 27: #ugly symbol
- pass
- else:
- self.input += inp
- try:
- self.lock.acquire()
- self.inputHandler.onChar(inp)
- finally:
- self.lock.release()
-
- self.inputline = self.bodyHandler.renderBody(self.menuline)
- self.renderFooter(self.inputline)
-
-
- def refresh(self):
- """refresh screen"""
-
- println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- println(2, "")
-
- self.lock.acquire()
-
- self.menuline = self.headerHandler.renderHeader(3) + 1
- println(self.menuline - 1, "")
- self.inputline = self.bodyHandler.renderBody(self.menuline)
- self.renderFooter(self.inputline)
-
- self.lock.release()
-
-
- def setInput(self, string=""):
- self.input = string
-
- def setHandler(self, klass):
- #create new handler with reference to cli
- self.bodyHandler = self.inputHandler = klass(self)
- self.input = ""
-
- def renderHeader(self, line):
- """ prints download status """
- #print updated information
- # print "\033[J" #clear screen
- # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- # self.println(2, "")
- # self.println(3, white(_("%s Downloads:") % (len(data))))
-
- data = self.client.statusDownloads()
- speed = 0
-
- println(line, white(_("%s Downloads:") % (len(data))))
- line += 1
-
- for download in data:
- if download.status == 12: # downloading
- percent = download.percent
- z = percent / 4
- speed += download.speed
- println(line, cyan(download.name))
- line += 1
- println(line,
- blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _(
- " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green(
- download.format_size) + _(" Finished in: ") + green(download.format_eta) + _(
- " ID: ") + green(download.fid))
- line += 1
- if download.status == 5:
- println(line, cyan(download.name))
- line += 1
- println(line, _("waiting: ") + green(download.format_wait))
- line += 1
-
- println(line, "")
- line += 1
- status = self.client.statusServer()
- if status.pause:
- paused = _("Status:") + " " + red(_("paused"))
- else:
- paused = _("Status:") + " " + red(_("running"))
-
- println(line,"%s %s: %s %s: %s %s: %s" % (
- paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red(
- status.queue), _("Total"), red(status.total)))
-
- return line + 1
-
- def renderBody(self, line):
- """ prints initial menu """
- println(line, white(_("Menu:")))
- println(line + 1, "")
- println(line + 2, mag("1.") + _(" Add Links"))
- println(line + 3, mag("2.") + _(" Manage Queue"))
- println(line + 4, mag("3.") + _(" Manage Collector"))
- println(line + 5, mag("4.") + _(" (Un)Pause Server"))
- println(line + 6, mag("5.") + _(" Kill Server"))
- println(line + 7, mag("6.") + _(" Quit"))
-
- return line + 8
-
- def renderFooter(self, line):
- """ prints out the input line with input """
- println(line, "")
- line += 1
-
- println(line, white(" Input: ") + decode(self.input))
-
- #clear old output
- if line < self.lastLowestLine:
- for i in range(line + 1, self.lastLowestLine + 1):
- println(i, "")
-
- self.lastLowestLine = line
-
- #set cursor to position
- print "\033[" + str(self.inputline) + ";0H"
-
- def onChar(self, char):
- """ default no special handling for single chars """
- if char == "1":
- self.setHandler(AddPackage)
- elif char == "2":
- self.setHandler(ManageFiles)
- elif char == "3":
- self.setHandler(ManageFiles)
- self.bodyHandler.target = Destination.Collector
- elif char == "4":
- self.client.togglePause()
- self.setInput()
- elif char == "5":
- self.client.kill()
- self.client.close()
- sys.exit()
- elif char == "6":
- os.system('clear')
- sys.exit()
-
- def onEnter(self, inp):
- pass
-
- def onBackSpace(self):
- pass
-
- def processCommand(self):
- command = self.command[0]
- args = []
- if len(self.command) > 1:
- args = self.command[1:]
-
- if command == "status":
- files = self.client.statusDownloads()
-
- if not files:
- print "No downloads running."
-
- for download in files:
- if download.status == 12: # downloading
- print print_status(download)
- print "\tDownloading: %s @ %s/s\t %s (%s%%)" % (
- download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft),
- download.percent)
- elif download.status == 5:
- print print_status(download)
- print "\tWaiting: %s" % download.format_wait
- else:
- print print_status(download)
-
- elif command == "queue":
- print_packages(self.client.getQueueData())
-
- elif command == "collector":
- print_packages(self.client.getCollectorData())
-
- elif command == "add":
- if len(args) < 2:
- print _("Please use this syntax: add <Package name> <link> <link2> ...")
- return
-
- self.client.addPackage(args[0], args[1:], Destination.Queue)
-
- elif command == "add_coll":
- if len(args) < 2:
- print _("Please use this syntax: add <Package name> <link> <link2> ...")
- return
-
- self.client.addPackage(args[0], args[1:], Destination.Collector)
-
- elif command == "del_file":
- self.client.deleteFiles([int(x) for x in args])
- print "Files deleted."
-
- elif command == "del_package":
- self.client.deletePackages([int(x) for x in args])
- print "Packages deleted."
-
- elif command == "move":
- for pid in args:
- pack = self.client.getPackageInfo(int(pid))
- self.client.movePackage((pack.dest + 1) % 2, pack.pid)
-
- elif command == "check":
- print _("Checking %d links:") % len(args)
- print
- rid = self.client.checkOnlineStatus(args).rid
- self.printOnlineCheck(self.client, rid)
-
-
- elif command == "check_container":
- path = args[0]
- if not exists(join(owd, path)):
- print _("File does not exists.")
- return
-
- f = open(join(owd, path), "rb")
- content = f.read()
- f.close()
-
- rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid
- self.printOnlineCheck(self.client, rid)
-
-
- elif command == "pause":
- self.client.pause()
-
- elif command == "unpause":
- self.client.unpause()
-
- elif command == "toggle":
- self.client.togglePause()
-
- elif command == "kill":
- self.client.kill()
- elif command == "restart_file":
- for x in args:
- self.client.restartFile(int(x))
- print "Files restarted."
- elif command == "restart_package":
- for pid in args:
- self.client.restartPackage(int(pid))
- print "Packages restarted."
-
- else:
- print_commands()
-
- def printOnlineCheck(self, client, rid):
- while True:
- sleep(1)
- result = client.pollResults(rid)
- for url, status in result.data.iteritems():
- if status.status == 2: check = "Online"
- elif status.status == 1: check = "Offline"
- else: check = "Unknown"
-
- print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check)
-
- if result.rid == -1: break
-
-
-class RefreshThread(Thread):
- def __init__(self, cli):
- Thread.__init__(self)
- self.setDaemon(True)
- self.cli = cli
-
- def run(self):
- while True:
- sleep(1)
- try:
- self.cli.refresh()
- except ConnectionClosed:
- os.system("clear")
- print _("pyLoad was terminated")
- _exit(0)
- except Exception, e:
- println(2, red(str(e)))
- self.cli.reset()
- print_exc()
-
-
-def print_help(config):
- print
- print "pyLoadCli Copyright (c) 2008-2011 the pyLoad Team"
- print
- print "Usage: [python] pyLoadCli.py [options] [command]"
- print
- print "<Commands>"
- print "See pyLoadCli.py -c for a complete listing."
- print
- print "<Options>"
- print " -i, --interactive", " Start in interactive mode"
- print
- print " -u, --username=", " " * 2, "Specify Username"
- print " --pw=<password>", " " * 2, "Password"
- print " -a, --address=", " " * 3, "Specify address (current=%s)" % config["addr"]
- print " -p, --port", " " * 7, "Specify port (current=%s)" % config["port"]
- print
- print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"]
- print " -h, --help", " " * 7, "Display this help screen"
- print " -c, --commands", " " * 3, "List all available commands"
- print
-
-
-def print_packages(data):
- for pack in data:
- print "Package %s (#%s):" % (pack.name, pack.pid)
- for download in pack.links:
- print "\t" + print_file(download)
- print
-
-
-def print_file(download):
- return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % {
- "id": download.fid,
- "name": download.name,
- "statusmsg": download.statusmsg,
- "plugin": download.plugin
- }
-
-
-def print_status(download):
- return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % {
- "id": download.fid,
- "name": download.name,
- "statusmsg": download.statusmsg,
- "size": download.format_size
- }
-
-
-def print_commands():
- commands = [("status", _("Prints server status")),
- ("queue", _("Prints downloads in queue")),
- ("collector", _("Prints downloads in collector")),
- ("add <name> <link1> <link2>...", _("Adds package to queue")),
- ("add_coll <name> <link1> <link2>...", _("Adds package to collector")),
- ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")),
- ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")),
- ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")),
- ("restart_file <fid> <fid2>...", _("Restart files")),
- ("restart_package <pid> <pid2>...", _("Restart packages")),
- ("check <container|url> ...", _("Check online status, works with local container")),
- ("check_container path", _("Checks online status of a container file")),
- ("pause", _("Pause the server")),
- ("unpause", _("continue downloads")),
- ("toggle", _("Toggle pause/unpause")),
- ("kill", _("kill server")), ]
-
- print _("List of commands:")
- print
- for c in commands:
- print "%-35s %s" % c
-
-
-def writeConfig(opts):
- try:
- with open(join(homedir, ".pyloadcli"), "w") as cfgfile:
- cfgfile.write("[cli]")
- for opt in opts:
- cfgfile.write("%s=%s\n" % (opt, opts[opt]))
- except:
- print _("Couldn't write user config file")
-
-
-def main():
- config = {"addr": "127.0.0.1", "port": "7227", "language": "en"}
- try:
- config["language"] = os.environ["LANG"][0:2]
- except:
- pass
-
- if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "":
- config["language"] = "en"
-
- configFile = ConfigParser.ConfigParser()
- configFile.read(join(homedir, ".pyloadcli"))
-
- if configFile.has_section("cli"):
- for opt in configFile.items("cli"):
- config[opt[0]] = opt[1]
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadCli", join(pypath, "locale"),
- languages=[config["language"],"en"],fallback=True)
- translation.install(unicode=True)
-
- interactive = False
- command = None
- username = ""
- password = ""
-
- shortOptions = 'iu:p:a:hcl:'
- longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="]
-
- try:
- opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions)
- for option, params in opts:
- if option in ("-i", "--interactive"):
- interactive = True
- elif option in ("-u", "--username"):
- username = params
- elif option in ("-a", "--address"):
- config["addr"] = params
- elif option in ("-p", "--port"):
- config["port"] = params
- elif option in ("-l", "--language"):
- config["language"] = params
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadCli", join(pypath, "locale"),
- languages=[config["language"],"en"],fallback=True)
- translation.install(unicode=True)
- elif option in ("-h", "--help"):
- print_help(config)
- exit()
- elif option in ("--pw"):
- password = params
- elif option in ("-c", "--comands"):
- print_commands()
- exit()
-
- except GetoptError:
- print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:])
- print_help(config)
- exit()
-
- if len(extraparams) >= 1:
- command = extraparams
-
- client = False
-
- if interactive:
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- pass
- except NoSSL:
- print _("You need py-openssl to connect to this pyLoad Core.")
- exit()
- except NoConnection:
- config["addr"] = False
- config["port"] = False
-
- if not client:
- if not config["addr"]: config["addr"] = raw_input(_("Address: "))
- if not config["port"]: config["port"] = raw_input(_("Port: "))
- if not username: username = raw_input(_("Username: "))
- if not password:
- from getpass import getpass
-
- password = getpass(_("Password: "))
-
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- print _("Login data is wrong.")
- except NoConnection:
- print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
- "port": config["port"]})
-
- else:
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- print _("Login data is wrong.")
- except NoConnection:
- print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
- "port": config["port"]})
- except NoSSL:
- print _("You need py-openssl to connect to this pyLoad core.")
-
- if interactive and command: print _("Interactive mode ignored since you passed some commands.")
-
- if client:
- writeConfig(config)
- cli = Cli(client, command)
-
-
-if __name__ == "__main__":
- main()
diff --git a/pyLoadCore.py b/pyLoadCore.py
deleted file mode 100755
index 35cac4682..000000000
--- a/pyLoadCore.py
+++ /dev/null
@@ -1,668 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: spoob
- @author: sebnapi
- @author: RaNaN
- @author: mkaay
- @version: v0.4.9
-"""
-CURRENT_VERSION = '0.4.9'
-
-import __builtin__
-
-from getopt import getopt, GetoptError
-import module.common.pylgettext as gettext
-from imp import find_module
-import logging
-import logging.handlers
-import os
-from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close
-from os.path import exists, join
-import signal
-import subprocess
-import sys
-from sys import argv, executable, exit
-from time import time, sleep
-from traceback import print_exc
-
-from module import InitHomeDir
-from module.plugins.AccountManager import AccountManager
-from module.CaptchaManager import CaptchaManager
-from module.ConfigParser import ConfigParser
-from module.plugins.PluginManager import PluginManager
-from module.PullEvents import PullManager
-from module.network.RequestFactory import RequestFactory
-from module.web.ServerThread import WebServer
-from module.Scheduler import Scheduler
-from module.common.JsEngine import JsEngine
-from module import remote
-from module.remote.RemoteManager import RemoteManager
-from module.database import DatabaseBackend, FileHandler
-
-from module.utils import freeSpace, formatSize, get_console_encoding
-
-from codecs import getwriter
-
-enc = get_console_encoding(sys.stdout.encoding)
-sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
-
-# TODO List
-# - configurable auth system ldap/mysql
-# - cron job like sheduler
-
-class Core(object):
- """pyLoad Core, one tool to rule them all... (the filehosters) :D"""
-
- def __init__(self):
- self.doDebug = False
- self.startedInGui = False
- self.running = False
- self.daemon = False
- self.remote = True
- self.arg_links = []
- self.pidfile = "pyload.pid"
- self.deleteLinks = False # will delete links on startup
-
- if len(argv) > 1:
- try:
- options, args = getopt(argv[1:], 'vchdusqp:',
- ["version", "clear", "clean", "help", "debug", "user",
- "setup", "configdir=", "changedir", "daemon",
- "quit", "status", "no-remote","pidfile="])
-
- for option, argument in options:
- if option in ("-v", "--version"):
- print "pyLoad", CURRENT_VERSION
- exit()
- elif option in ("-p", "--pidfile"):
- self.pidfile = argument
- elif option == "--daemon":
- self.daemon = True
- elif option in ("-c", "--clear"):
- self.deleteLinks = True
- elif option in ("-h", "--help"):
- self.print_help()
- exit()
- elif option in ("-d", "--debug"):
- self.doDebug = True
- elif option in ("-u", "--user"):
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.set_user()
- exit()
- elif option in ("-s", "--setup"):
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.start()
- exit()
- elif option == "--changedir":
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.conf_path(True)
- exit()
- elif option in ("-q", "--quit"):
- self.quitInstance()
- exit()
- elif option == "--status":
- pid = self.isAlreadyRunning()
- if self.isAlreadyRunning():
- print pid
- exit(0)
- else:
- print "false"
- exit(1)
- elif option == "--clean":
- self.cleanTree()
- exit()
- elif option == "--no-remote":
- self.remote = False
-
- except GetoptError:
- print 'Unknown Argument(s) "%s"' % " ".join(argv[1:])
- self.print_help()
- exit()
-
- def print_help(self):
- print ""
- print "pyLoad v%s 2008-2011 the pyLoad Team" % CURRENT_VERSION
- print ""
- if sys.argv[0].endswith(".py"):
- print "Usage: python pyLoadCore.py [options]"
- else:
- print "Usage: pyLoadCore [options]"
- print ""
- print "<Options>"
- print " -v, --version", " " * 10, "Print version to terminal"
- print " -c, --clear", " " * 12, "Delete all saved packages/links"
- #print " -a, --add=<link/list>", " " * 2, "Add the specified links"
- print " -u, --user", " " * 13, "Manages users"
- print " -d, --debug", " " * 12, "Enable debug mode"
- print " -s, --setup", " " * 12, "Run Setup Assistent"
- print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory"
- print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
- print " --changedir", " " * 12, "Change config dir permanently"
- print " --daemon", " " * 15, "Daemonmize after start"
- print " --no-remote", " " * 12, "Disable remote access (saves RAM)"
- print " --status", " " * 15, "Display pid if running or False"
- print " --clean", " " * 16, "Remove .pyc/.pyo files"
- print " -q, --quit", " " * 13, "Quit running pyLoad instance"
- print " -h, --help", " " * 13, "Display this help screen"
- print ""
-
- def toggle_pause(self):
- if self.threadManager.pause:
- self.threadManager.pause = False
- return False
- elif not self.threadManager.pause:
- self.threadManager.pause = True
- return True
-
- def quit(self, a, b):
- self.shutdown()
- self.log.info(_("Received Quit signal"))
- _exit(1)
-
- def writePidFile(self):
- self.deletePidFile()
- pid = os.getpid()
- f = open(self.pidfile, "wb")
- f.write(str(pid))
- f.close()
-
- def deletePidFile(self):
- if self.checkPidFile():
- self.log.debug("Deleting old pidfile %s" % self.pidfile)
- os.remove(self.pidfile)
-
- def checkPidFile(self):
- """ return pid as int or 0"""
- if os.path.isfile(self.pidfile):
- f = open(self.pidfile, "rb")
- pid = f.read().strip()
- f.close()
- if pid:
- pid = int(pid)
- return pid
-
- return 0
-
- def isAlreadyRunning(self):
- pid = self.checkPidFile()
- if not pid or os.name == "nt": return False
- try:
- os.kill(pid, 0) # 0 - default signal (does nothing)
- except:
- return 0
-
- return pid
-
- def quitInstance(self):
- if os.name == "nt":
- print "Not supported on windows."
- return
-
- pid = self.isAlreadyRunning()
- if not pid:
- print "No pyLoad running."
- return
-
- try:
- os.kill(pid, 3) #SIGUIT
-
- t = time()
- print "waiting for pyLoad to quit"
-
- while exists(self.pidfile) and t + 10 > time():
- sleep(0.25)
-
- if not exists(self.pidfile):
- print "pyLoad successfully stopped"
- else:
- os.kill(pid, 9) #SIGKILL
- print "pyLoad did not respond"
- print "Kill signal was send to process with id %s" % pid
-
- except:
- print "Error quitting pyLoad"
-
-
- def cleanTree(self):
- for path, dirs, files in walk(self.path("")):
- for f in files:
- if not f.endswith(".pyo") and not f.endswith(".pyc"):
- continue
-
- if "_25" in f or "_26" in f or "_27" in f:
- continue
-
- print join(path, f)
- remove(join(path, f))
-
- def start(self, rpc=True, web=True):
- """ starts the fun :D """
-
- self.version = CURRENT_VERSION
-
- if not exists("pyload.conf"):
- from module.setup import Setup
-
- print "This is your first start, running configuration assistent now."
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- res = False
- try:
- res = s.start()
- except SystemExit:
- pass
- except KeyboardInterrupt:
- print "\nSetup interrupted"
- except:
- res = False
- print_exc()
- print "Setup failed"
- if not res:
- remove("pyload.conf")
-
- exit()
-
- try: signal.signal(signal.SIGQUIT, self.quit)
- except: pass
-
- self.config = ConfigParser()
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoad", self.path("locale"),
- languages=[self.config['general']['language'],"en"],fallback=True)
- translation.install(True)
-
- self.debug = self.doDebug or self.config['general']['debug_mode']
- self.remote &= self.config['remote']['activated']
-
- pid = self.isAlreadyRunning()
- if pid:
- print _("pyLoad already running with pid %s") % pid
- exit()
-
- if os.name != "nt" and self.config["general"]["renice"]:
- os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid()))
-
- if self.config["permission"]["change_group"]:
- if os.name != "nt":
- try:
- from grp import getgrnam
-
- group = getgrnam(self.config["permission"]["group"])
- os.setgid(group[2])
- except Exception, e:
- print _("Failed changing group: %s") % e
-
- if self.config["permission"]["change_user"]:
- if os.name != "nt":
- try:
- from pwd import getpwnam
-
- user = getpwnam(self.config["permission"]["user"])
- os.setuid(user[2])
- except Exception, e:
- print _("Failed changing user: %s") % e
-
- self.check_file(self.config['log']['log_folder'], _("folder for logs"), True)
-
- if self.debug:
- self.init_logger(logging.DEBUG) # logging level
- else:
- self.init_logger(logging.INFO) # logging level
-
- self.do_kill = False
- self.do_restart = False
- self.shuttedDown = False
-
- self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION)
- self.log.info(_("Using home directory: %s") % getcwd())
-
- self.writePidFile()
-
- #@TODO refractor
-
- remote.activated = self.remote
- self.log.debug("Remote activated: %s" % self.remote)
-
- self.check_install("Crypto", _("pycrypto to decode container files"))
- #img = self.check_install("Image", _("Python Image Libary (PIL) for captcha reading"))
- #self.check_install("pycurl", _("pycurl to download any files"), True, True)
- self.check_file("tmp", _("folder for temporary files"), True)
- #tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True
-
- self.captcha = True # checks seems to fail, althoug tesseract is available
-
- self.check_file(self.config['general']['download_folder'], _("folder for downloads"), True)
-
- if self.config['ssl']['activated']:
- self.check_install("OpenSSL", _("OpenSSL for secure connection"))
-
- self.setupDB()
- if self.config.oldRemoteData:
- self.log.info(_("Moving old user config to DB"))
- self.db.addUser(self.config.oldRemoteData["username"], self.config.oldRemoteData["password"])
-
- self.log.info(_("Please check your logindata with ./pyLoadCore.py -u"))
-
- if self.deleteLinks:
- self.log.info(_("All links removed"))
- self.db.purgeLinks()
-
- self.requestFactory = RequestFactory(self)
- __builtin__.pyreq = self.requestFactory
-
- self.lastClientConnected = 0
-
- # later imported because they would trigger api import, and remote value not set correctly
- from module import Api
- from module.HookManager import HookManager
- from module.ThreadManager import ThreadManager
-
- if Api.activated != self.remote:
- self.log.warning("Import error: API remote status not correct.")
-
- self.api = Api.Api(self)
-
- self.scheduler = Scheduler(self)
-
- #hell yeah, so many important managers :D
- self.pluginManager = PluginManager(self)
- self.pullManager = PullManager(self)
- self.accountManager = AccountManager(self)
- self.threadManager = ThreadManager(self)
- self.captchaManager = CaptchaManager(self)
- self.hookManager = HookManager(self)
- self.remoteManager = RemoteManager(self)
-
- self.js = JsEngine()
-
- self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
-
- if rpc:
- self.remoteManager.startBackends()
-
- if web:
- self.init_webserver()
-
- spaceLeft = freeSpace(self.config["general"]["download_folder"])
-
- self.log.info(_("Free space: %s") % formatSize(spaceLeft))
-
- self.config.save() #save so config files gets filled
-
- link_file = join(pypath, "links.txt")
-
- if exists(link_file):
- f = open(link_file, "rb")
- if f.read().strip():
- self.api.addPackage("links.txt", [link_file], 1)
- f.close()
-
- link_file = "links.txt"
- if exists(link_file):
- f = open(link_file, "rb")
- if f.read().strip():
- self.api.addPackage("links.txt", [link_file], 1)
- f.close()
-
- #self.scheduler.addJob(0, self.accountManager.getAccountInfos)
- self.log.info(_("Activating Accounts..."))
- self.accountManager.getAccountInfos()
-
- self.threadManager.pause = False
- self.running = True
-
- self.log.info(_("Activating Plugins..."))
- self.hookManager.coreReady()
-
- self.log.info(_("pyLoad is up and running"))
-
- #test api
-# from module.common.APIExerciser import startApiExerciser
-# startApiExerciser(self, 3)
-
- #some memory stats
-# from guppy import hpy
-# hp=hpy()
-# import objgraph
-# objgraph.show_most_common_types(limit=20)
-# import memdebug
-# memdebug.start(8002)
-
- locals().clear()
-
- while True:
- sleep(2)
- if self.do_restart:
- self.log.info(_("restarting pyLoad"))
- self.restart()
- if self.do_kill:
- self.shutdown()
- self.log.info(_("pyLoad quits"))
- self.removeLogger()
- _exit(0) #@TODO thrift blocks shutdown
-
- self.threadManager.work()
- self.scheduler.work()
-
- def setupDB(self):
- self.db = DatabaseBackend(self) # the backend
- self.db.setup()
-
- self.files = FileHandler(self)
- self.db.manager = self.files #ugly?
-
- def init_webserver(self):
- if self.config['webinterface']['activated']:
- self.webserver = WebServer(self)
- self.webserver.start()
-
- def init_logger(self, level):
- console = logging.StreamHandler(sys.stdout)
- frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S")
- console.setFormatter(frm)
- self.log = logging.getLogger("log") # settable in config
-
- if self.config['log']['file_log']:
- if self.config['log']['log_rotate']:
- file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'),
- maxBytes=self.config['log']['log_size'] * 1024,
- backupCount=int(self.config['log']['log_count']),
- encoding="utf8")
- else:
- file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8")
-
- file_handler.setFormatter(frm)
- self.log.addHandler(file_handler)
-
- self.log.addHandler(console) #if console logging
- self.log.setLevel(level)
-
- def removeLogger(self):
- for h in list(self.log.handlers):
- self.log.removeHandler(h)
- h.close()
-
- def check_install(self, check_name, legend, python=True, essential=False):
- """check wether needed tools are installed"""
- try:
- if python:
- find_module(check_name)
- else:
- pipe = subprocess.PIPE
- subprocess.Popen(check_name, stdout=pipe, stderr=pipe)
-
- return True
- except:
- if essential:
- self.log.info(_("Install %s") % legend)
- exit()
-
- return False
-
- def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False):
- """check wether needed files exists"""
- tmp_names = []
- if not type(check_names) == list:
- tmp_names.append(check_names)
- else:
- tmp_names.extend(check_names)
- file_created = True
- file_exists = True
- for tmp_name in tmp_names:
- if not exists(tmp_name):
- file_exists = False
- if empty:
- try:
- if folder:
- tmp_name = tmp_name.replace("/", sep)
- makedirs(tmp_name)
- else:
- open(tmp_name, "w")
- except:
- file_created = False
- else:
- file_created = False
-
- if not file_exists and not quiet:
- if file_created:
- #self.log.info( _("%s created") % description )
- pass
- else:
- if not empty:
- self.log.warning(
- _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name})
- else:
- print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}
- if essential:
- exit()
-
- def isClientConnected(self):
- return (self.lastClientConnected + 30) > time()
-
- def restart(self):
- self.shutdown()
- chdir(owd)
- # close some open fds
- for i in range(3,50):
- try:
- close(i)
- except :
- pass
-
- execl(executable, executable, *sys.argv)
- _exit(0)
-
- def shutdown(self):
- self.log.info(_("shutting down..."))
- try:
- if self.config['webinterface']['activated'] and hasattr(self, "webserver"):
- self.webserver.quit()
-
- for thread in self.threadManager.threads:
- thread.put("quit")
- pyfiles = self.files.cache.values()
-
- for pyfile in pyfiles:
- pyfile.abortDownload()
-
- self.hookManager.coreExiting()
-
- except:
- if self.debug:
- print_exc()
- self.log.info(_("error while shutting down"))
-
- finally:
- self.files.syncSave()
- self.shuttedDown = True
-
- self.deletePidFile()
-
-
- def path(self, *args):
- return join(pypath, *args)
-
-
-def deamon():
- try:
- pid = os.fork()
- if pid > 0:
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
- sys.exit(1)
-
- # decouple from parent environment
- os.setsid()
- os.umask(0)
-
- # do second fork
- try:
- pid = os.fork()
- if pid > 0:
- # exit from second parent, print eventual PID before
- print "Daemon PID %d" % pid
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
- sys.exit(1)
-
- # Iterate through and close some file descriptors.
- for fd in range(0, 3):
- try:
- os.close(fd)
- except OSError: # ERROR, fd wasn't open to begin with (ignored)
- pass
-
- os.open(os.devnull, os.O_RDWR) # standard input (0)
- os.dup2(0, 1) # standard output (1)
- os.dup2(0, 2)
-
- pyload_core = Core()
- pyload_core.start()
-
-
-def main():
- #change name to 'pyLoadCore'
- #from module.lib.rename_process import renameProcess
- #renameProcess('pyLoadCore')
- if "--daemon" in sys.argv:
- deamon()
- else:
- pyload_core = Core()
- try:
- pyload_core.start()
- except KeyboardInterrupt:
- pyload_core.shutdown()
- pyload_core.log.info(_("killed pyLoad from Terminal"))
- pyload_core.removeLogger()
- _exit(1)
-
-# And so it begins...
-if __name__ == "__main__":
- main()
-
diff --git a/pyLoadGui.py b/pyLoadGui.py
deleted file mode 100755
index 5f620e52a..000000000
--- a/pyLoadGui.py
+++ /dev/null
@@ -1,765 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-import sys
-
-from uuid import uuid4 as uuid # should be above PyQt imports
-from time import sleep, time
-
-from base64 import b64decode
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-import re
-import module.common.pylgettext as gettext
-import os
-from os.path import abspath
-from os.path import join
-from os.path import basename
-from os.path import commonprefix
-
-from module import InitHomeDir
-from module.gui.ConnectionManager import *
-from module.gui.Connector import Connector
-from module.gui.MainWindow import *
-from module.gui.Queue import *
-from module.gui.Overview import *
-from module.gui.Collector import *
-from module.gui.XMLParser import *
-from module.gui.CoreConfigParser import ConfigParser
-
-from module.lib.rename_process import renameProcess
-from module.utils import formatSize, formatSpeed
-
-try:
- import pynotify
-except ImportError:
- print "pynotify not installed, falling back to qt tray notification"
-
-class main(QObject):
- def __init__(self):
- """
- main setup
- """
- QObject.__init__(self)
- self.app = QApplication(sys.argv)
- self.path = pypath
- self.homedir = abspath("")
-
- self.configdir = ""
-
- self.init(True)
-
- def init(self, first=False):
- """
- set main things up
- """
- self.parser = XMLParser(join(self.configdir, "gui.xml"), join(self.path, "module", "config", "gui_default.xml"))
- lang = self.parser.xml.elementsByTagName("language").item(0).toElement().text()
- if not lang:
- parser = XMLParser(join(self.path, "module", "config", "gui_default.xml"))
- lang = parser.xml.elementsByTagName("language").item(0).toElement().text()
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadGui", join(pypath, "locale"), languages=[str(lang), "en"], fallback=True)
- try:
- translation.install(unicode=(True if sys.stdout.encoding.lower().startswith("utf") else False))
- except:
- translation.install(unicode=False)
-
-
- self.connector = Connector()
- self.mainWindow = MainWindow(self.connector)
- self.connWindow = ConnectionManager()
- self.mainloop = self.Loop(self)
- self.connectSignals()
-
- self.checkClipboard = False
- default = self.refreshConnections()
- self.connData = None
- self.captchaProcessing = False
- self.serverStatus = {"freespace":0}
-
- self.core = None # pyLoadCore if started
- self.connectionLost = False
-
- if True: # when used if first, minimizing not working correctly..
- self.tray = TrayIcon()
- self.tray.show()
- self.notification = Notification(self.tray)
- self.connect(self, SIGNAL("showMessage"), self.notification.showMessage)
- self.connect(self.tray.exitAction, SIGNAL("triggered()"), self.slotQuit)
- self.connect(self.tray.showAction, SIGNAL("toggled(bool)"), self.mainWindow.setVisible)
- self.connect(self.mainWindow, SIGNAL("hidden"), self.tray.mainWindowHidden)
-
- if not first:
- self.connWindow.show()
- else:
- self.connWindow.edit.setData(default)
- data = self.connWindow.edit.getData()
- self.slotConnect(data)
-
- def startMain(self):
- """
- start all refresh threads and show main window
- """
- if not self.connector.connectProxy():
- self.init()
- return
- self.connect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost)
- self.restoreMainWindow()
- self.mainWindow.show()
- self.initQueue()
- self.initPackageCollector()
- self.mainloop.start()
- self.clipboard = self.app.clipboard()
- self.connect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange)
- self.mainWindow.actions["clipboard"].setChecked(self.checkClipboard)
-
- self.mainWindow.tabs["settings"]["w"].setConnector(self.connector)
- self.mainWindow.tabs["settings"]["w"].loadConfig()
- self.tray.showAction.setDisabled(False)
-
- def stopMain(self):
- """
- stop all refresh threads and hide main window
- """
- self.tray.showAction.setDisabled(True)
- self.disconnect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange)
- self.disconnect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost)
- self.mainloop.stop()
- self.mainWindow.saveWindow()
- self.mainWindow.hide()
- self.queue.stop()
-
- def connectSignals(self):
- """
- signal and slot stuff, yay!
- """
- self.connect(self.connector, SIGNAL("errorBox"), self.slotErrorBox)
- self.connect(self.connWindow, SIGNAL("saveConnection"), self.slotSaveConnection)
- self.connect(self.connWindow, SIGNAL("removeConnection"), self.slotRemoveConnection)
- self.connect(self.connWindow, SIGNAL("connect"), self.slotConnect)
- self.connect(self.mainWindow, SIGNAL("connector"), self.slotShowConnector)
- self.connect(self.mainWindow, SIGNAL("addPackage"), self.slotAddPackage)
- self.connect(self.mainWindow, SIGNAL("setDownloadStatus"), self.slotSetDownloadStatus)
- self.connect(self.mainWindow, SIGNAL("saveMainWindow"), self.slotSaveMainWindow)
- self.connect(self.mainWindow, SIGNAL("pushPackageToQueue"), self.slotPushPackageToQueue)
- self.connect(self.mainWindow, SIGNAL("restartDownload"), self.slotRestartDownload)
- self.connect(self.mainWindow, SIGNAL("removeDownload"), self.slotRemoveDownload)
- self.connect(self.mainWindow, SIGNAL("abortDownload"), self.slotAbortDownload)
- self.connect(self.mainWindow, SIGNAL("addContainer"), self.slotAddContainer)
- self.connect(self.mainWindow, SIGNAL("stopAllDownloads"), self.slotStopAllDownloads)
- self.connect(self.mainWindow, SIGNAL("setClipboardStatus"), self.slotSetClipboardStatus)
- self.connect(self.mainWindow, SIGNAL("changePackageName"), self.slotChangePackageName)
- self.connect(self.mainWindow, SIGNAL("pullOutPackage"), self.slotPullOutPackage)
- self.connect(self.mainWindow, SIGNAL("refreshStatus"), self.slotRefreshStatus)
- self.connect(self.mainWindow, SIGNAL("reloadAccounts"), self.slotReloadAccounts)
- self.connect(self.mainWindow, SIGNAL("Quit"), self.slotQuit)
-
- self.connect(self.mainWindow.mactions["exit"], SIGNAL("triggered()"), self.slotQuit)
- self.connect(self.mainWindow.captchaDock, SIGNAL("done"), self.slotCaptchaDone)
-
- def slotShowConnector(self):
- """
- emitted from main window (menu)
- hide the main window and show connection manager
- (to switch to other core)
- """
- self.quitInternal()
- self.stopMain()
- self.init()
-
- #def quit(self): #not used anymore?
- # """
- # quit gui
- # """
- # self.app.quit()
-
- def loop(self):
- """
- start application loop
- """
- sys.exit(self.app.exec_())
-
- def slotErrorBox(self, msg):
- """
- display a nice error box
- """
- msgb = QMessageBox(QMessageBox.Warning, "Error", msg)
- msgb.exec_()
-
- def initPackageCollector(self):
- """
- init the package collector view
- * columns
- * selection
- * refresh thread
- * drag'n'drop
- """
- view = self.mainWindow.tabs["collector"]["package_view"]
- view.setSelectionBehavior(QAbstractItemView.SelectRows)
- view.setSelectionMode(QAbstractItemView.ExtendedSelection)
- def dropEvent(klass, event):
- event.setDropAction(Qt.CopyAction)
- event.accept()
- view = event.source()
- if view == klass:
- items = view.selectedItems()
- for item in items:
- if not hasattr(item.parent(), "getPackData"):
- continue
- target = view.itemAt(event.pos())
- if not hasattr(target, "getPackData"):
- target = target.parent()
- klass.emit(SIGNAL("droppedToPack"), target.getPackData()["id"], item.getFileData()["id"])
- event.ignore()
- return
- items = view.selectedItems()
- for item in items:
- row = view.indexOfTopLevelItem(item)
- view.takeTopLevelItem(row)
- def dragEvent(klass, event):
- #view = event.source()
- #dragOkay = False
- #items = view.selectedItems()
- #for item in items:
- # if hasattr(item, "_data"):
- # if item._data["id"] == "fixed" or item.parent()._data["id"] == "fixed":
- # dragOkay = True
- # else:
- # dragOkay = True
- #if dragOkay:
- event.accept()
- #else:
- # event.ignore()
- view.dropEvent = dropEvent
- view.dragEnterEvent = dragEvent
- view.setDragEnabled(True)
- view.setDragDropMode(QAbstractItemView.DragDrop)
- view.setDropIndicatorShown(True)
- view.setDragDropOverwriteMode(True)
- view.connect(view, SIGNAL("droppedToPack"), self.slotAddFileToPackage)
- #self.packageCollector = PackageCollector(view, self.connector)
- self.packageCollector = view.model()
-
- def initQueue(self):
- """
- init the queue view
- * columns
- * progressbar
- """
- view = self.mainWindow.tabs["queue"]["view"]
- view.setSelectionBehavior(QAbstractItemView.SelectRows)
- view.setSelectionMode(QAbstractItemView.ExtendedSelection)
- self.queue = view.model()
- self.connect(self.queue, SIGNAL("updateCount"), self.slotUpdateCount)
- overview = self.mainWindow.tabs["overview"]["view"].model()
- overview.queue = self.queue
- self.connect(self.queue, SIGNAL("updateCount"), overview.queueChanged)
- self.queue.start()
-
- def slotUpdateCount(self, pc, fc):
- self.mainWindow.packageCount.setText("%i" % pc)
- self.mainWindow.fileCount.setText("%i" % fc)
-
- def refreshServerStatus(self):
- """
- refresh server status and overall speed in the status bar
- """
- s = self.connector.statusServer()
- if s.pause:
- self.mainWindow.status.setText(_("paused"))
- else:
- self.mainWindow.status.setText(_("running"))
- self.mainWindow.speed.setText(formatSpeed(s.speed))
- self.mainWindow.space.setText(formatSize(self.serverStatus["freespace"]))
- self.mainWindow.actions["toggle_status"].setChecked(not s.pause)
-
- def refreshLog(self):
- """
- update log window
- """
- offset = self.mainWindow.tabs["log"]["text"].logOffset
- lines = self.connector.getLog(offset)
- if not lines:
- return
- self.mainWindow.tabs["log"]["text"].logOffset += len(lines)
- for line in lines:
- self.mainWindow.tabs["log"]["text"].emit(SIGNAL("append(QString)"), line.strip("\n"))
- cursor = self.mainWindow.tabs["log"]["text"].textCursor()
- cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
- self.mainWindow.tabs["log"]["text"].setTextCursor(cursor)
-
- def getConnections(self):
- """
- parse all connections in the config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- ret = []
- for conn in connections:
- data = {}
- data["type"] = conn.attribute("type", "remote")
- data["default"] = conn.attribute("default", "False")
- data["id"] = conn.attribute("id", uuid().hex)
- if data["default"] == "True":
- data["default"] = True
- else:
- data["default"] = False
- subs = self.parser.parseNode(conn, "dict")
- if not subs.has_key("name"):
- data["name"] = _("Unnamed")
- else:
- data["name"] = subs["name"].text()
- if data["type"] == "remote":
- if not subs.has_key("server"):
- continue
- else:
- data["host"] = subs["server"].text()
- data["user"] = subs["server"].attribute("user", "admin")
- data["port"] = int(subs["server"].attribute("port", "7227"))
- data["password"] = subs["server"].attribute("password", "")
- ret.append(data)
- return ret
-
- def slotSaveConnection(self, data):
- """
- save connection to config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- connNode = self.parser.xml.createElement("connection")
- connNode.setAttribute("default", str(data["default"]))
- connNode.setAttribute("type", data["type"])
- connNode.setAttribute("id", data["id"])
- nameNode = self.parser.xml.createElement("name")
- nameText = self.parser.xml.createTextNode(data["name"])
- nameNode.appendChild(nameText)
- connNode.appendChild(nameNode)
- if data["type"] == "remote":
- serverNode = self.parser.xml.createElement("server")
- serverNode.setAttribute("user", data["user"])
- serverNode.setAttribute("port", data["port"])
- serverNode.setAttribute("password", data["password"])
- hostText = self.parser.xml.createTextNode(data["host"])
- serverNode.appendChild(hostText)
- connNode.appendChild(serverNode)
- found = False
- for c in connections:
- cid = c.attribute("id", "None")
- if str(cid) == str(data["id"]):
- found = c
- break
- if found:
- connectionsNode.replaceChild(connNode, found)
- else:
- connectionsNode.appendChild(connNode)
- self.parser.saveData()
- self.refreshConnections()
-
- def slotRemoveConnection(self, data):
- """
- remove connection from config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- found = False
- for c in connections:
- cid = c.attribute("id", "None")
- if str(cid) == str(data["id"]):
- found = c
- break
- if found:
- connectionsNode.removeChild(found)
- self.parser.saveData()
- self.refreshConnections()
-
- def slotConnect(self, data):
- """
- connect to a core
- if connection is local, parse the core config file for data
- if internal, start pyLoadCore
- set up connector, show main window
- """
- self.connWindow.hide()
- if data["type"] not in ("remote","internal"):
-
- coreparser = ConfigParser(self.configdir)
- if not coreparser.config:
- self.connector.setConnectionData("127.0.0.1", 7227, "anonymous", "anonymous", False)
- else:
- self.connector.setConnectionData("127.0.0.1", coreparser.get("remote","port"), "anonymous", "anonymous")
-
- elif data["type"] == "remote":
- self.connector.setConnectionData(data["host"], data["port"], data["user"], data["password"])
-
- elif data["type"] == "internal":
- from pyLoadCore import Core
- from module.ConfigParser import ConfigParser as CoreConfig
- import thread
-
- if not self.core:
-
- config = CoreConfig() #create so at least default config exists
- self.core = Core()
- self.core.startedInGui = True
- thread.start_new_thread(self.core.start, (False, False))
- while not self.core.running:
- sleep(0.5)
-
- self.connector.proxy = self.core.api
- self.connector.internal = True
-
- #self.connector.setConnectionData("127.0.0.1", config.get("remote","port"), "anonymous", "anonymous")
-
- self.startMain()
-# try:
-# host = data["host"]
-# except:
-# host = "127.0.0.1"
-
- def refreshConnections(self):
- """
- reload connetions and display them
- """
- self.parser.loadData()
- conns = self.getConnections()
- self.connWindow.emit(SIGNAL("setConnections"), conns)
- for conn in conns:
- if conn["default"]:
- return conn
- return None
-
- def slotSetDownloadStatus(self, status):
- """
- toolbar start/pause slot
- """
- if status:
- self.connector.unpauseServer()
- else:
- self.connector.pauseServer()
-
- def slotAddPackage(self, name, links, password=None):
- """
- emitted from main window
- add package to the collector
- """
- pack = self.connector.addPackage(name, links, Destination.Collector)
- if password:
- data = {"password": password}
- self.connector.setPackageData(pack, data)
-
- def slotAddFileToPackage(self, pid, fid): #TODO deprecated? gets called
- """
- emitted from collector view after a drop action
- """
- #self.connector.addFileToPackage(fid, pid)
- pass
-
- def slotAddContainer(self, path):
- """
- emitted from main window
- add container
- """
- filename = basename(path)
- #type = "".join(filename.split(".")[-1])
- fh = open(path, "r")
- content = fh.read()
- fh.close()
- self.connector.uploadContainer(filename, content)
-
- def slotSaveMainWindow(self, state, geo):
- """
- save the window geometry and toolbar/dock position to config file
- """
- mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0)
- if mainWindowNode.isNull():
- mainWindowNode = self.parser.xml.createElement("mainWindow")
- self.parser.root.appendChild(mainWindowNode)
- stateNode = mainWindowNode.toElement().elementsByTagName("state").item(0)
- geoNode = mainWindowNode.toElement().elementsByTagName("geometry").item(0)
- newStateNode = self.parser.xml.createTextNode(state)
- newGeoNode = self.parser.xml.createTextNode(geo)
-
- stateNode.removeChild(stateNode.firstChild())
- geoNode.removeChild(geoNode.firstChild())
- stateNode.appendChild(newStateNode)
- geoNode.appendChild(newGeoNode)
-
- self.parser.saveData()
-
- def restoreMainWindow(self):
- """
- load and restore main window geometry and toolbar/dock position from config
- """
- mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0)
- if mainWindowNode.isNull():
- return
- nodes = self.parser.parseNode(mainWindowNode, "dict")
-
- state = str(nodes["state"].text())
- geo = str(nodes["geometry"].text())
-
- self.mainWindow.restoreWindow(state, geo)
- self.mainWindow.captchaDock.hide()
-
- def slotPushPackageToQueue(self, id):
- """
- emitted from main window
- push the collector package to queue
- """
- self.connector.pushToQueue(id)
-
- def slotRestartDownload(self, id, isPack):
- """
- emitted from main window
- restart download
- """
- if isPack:
- self.connector.restartPackage(id)
- else:
- self.connector.restartFile(id)
-
- def slotRefreshStatus(self, id):
- """
- emitted from main window
- refresh download status
- """
- self.connector.recheckPackage(id)
-
- def slotRemoveDownload(self, id, isPack):
- """
- emitted from main window
- remove download
- """
- if isPack:
- self.connector.deletePackages([id])
- else:
- self.connector.deleteFiles([id])
-
- def slotAbortDownload(self, id, isPack):
- """
- emitted from main window
- remove download
- """
- if isPack:
- data = self.connector.getFileOrder(id) #less data to transmit
- self.connector.stopDownloads(data.values())
- else:
- self.connector.stopDownloads([id])
-
- def slotStopAllDownloads(self):
- """
- emitted from main window
- stop all running downloads
- """
- self.connector.stopAllDownloads()
-
- def slotClipboardChange(self):
- """
- called if clipboard changes
- """
- if self.checkClipboard:
- text = self.clipboard.text()
- pattern = re.compile(r"(http|https|ftp)://[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?/.*)?")
- matches = pattern.finditer(text)
-
- # thanks to: jmansour //#139
- links = [str(match.group(0)) for match in matches]
- if len(links) == 0:
- return
-
- filenames = [link.rpartition("/")[2] for link in links]
-
- packagename = commonprefix(filenames)
- if len(packagename) == 0:
- packagename = filenames[0]
-
- self.slotAddPackage(packagename, links)
-
- def slotSetClipboardStatus(self, status):
- """
- set clipboard checking
- """
- self.checkClipboard = status
-
- def slotChangePackageName(self, pid, name):
- """
- package name edit finished
- """
- self.connector.setPackageName(pid, str(name))
-
- def slotPullOutPackage(self, pid):
- """
- pull package out of the queue
- """
- self.connector.pullFromQueue(pid)
-
- def checkCaptcha(self):
- if self.connector.isCaptchaWaiting() and self.mainWindow.captchaDock.isFree():
- t = self.connector.getCaptchaTask(False)
- self.mainWindow.show()
- self.mainWindow.raise_()
- self.mainWindow.activateWindow()
- self.mainWindow.captchaDock.emit(SIGNAL("setTask"), t.tid, b64decode(t.data), t.type)
- elif not self.mainWindow.captchaDock.isFree():
- status = self.connector.getCaptchaTaskStatus(self.mainWindow.captchaDock.currentID)
- if not (status == "user" or status == "shared-user"):
- self.mainWindow.captchaDock.hide()
- self.mainWindow.captchaDock.processing = False
- self.mainWindow.captchaDock.currentID = None
-
- def slotCaptchaDone(self, cid, result):
- self.connector.setCaptchaResult(cid, str(result))
-
- def pullEvents(self):
- events = self.connector.getEvents(self.connector.connectionID)
- if not events:
- return
- for event in events:
- if event.eventname == "account":
- self.mainWindow.emit(SIGNAL("reloadAccounts"), False)
- elif event.eventname == "config":
- pass
- elif event.destination == Destination.Queue:
- self.queue.addEvent(event)
- try:
- if event.eventname == "update" and event.type == ElementType.File:
- info = self.connector.getFileData(event.id)
- if info.statusmsg == "finished":
- self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info.name)
- elif info.statusmsg == "failed":
- self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info.name)
- if event.event == "insert" and event.type == ElementType.File:
- info = self.connector.getLinkInfo(event[3])
- self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info.name)
- except:
- print "can't send notification"
- elif event.destination == Destination.Collector:
- self.packageCollector.addEvent(event)
-
- def slotReloadAccounts(self, force=False):
- self.mainWindow.tabs["accounts"]["view"].model().reloadData(force)
-
- def slotQuit(self):
- self.tray.hide()
- self.quitInternal()
- self.app.quit()
-
- def quitInternal(self):
- if self.core:
- self.core.api.kill()
- for i in range(10):
- if self.core.shuttedDown:
- break
- sleep(0.5)
-
- def slotConnectionLost(self):
- if not self.connectionLost:
- self.connectionLost = True
- m = QMessageBox(QMessageBox.Critical, _("Connection lost"), _("Lost connection to the core!"), QMessageBox.Ok)
- m.exec_()
- self.slotQuit()
-
- class Loop():
- def __init__(self, parent):
- self.parent = parent
- self.timer = QTimer()
- self.timer.connect(self.timer, SIGNAL("timeout()"), self.update)
- self.lastSpaceCheck = 0
-
- def start(self):
- self.update()
- self.timer.start(1000)
-
- def update(self):
- """
- methods to call
- """
- self.parent.refreshServerStatus()
- if self.lastSpaceCheck + 5 < time():
- self.lastSpaceCheck = time()
- self.parent.serverStatus["freespace"] = self.parent.connector.freeSpace()
- self.parent.refreshLog()
- self.parent.checkCaptcha()
- self.parent.pullEvents()
-
- def stop(self):
- self.timer.stop()
-
-
-class TrayIcon(QSystemTrayIcon):
- def __init__(self):
- QSystemTrayIcon.__init__(self, QIcon(join(pypath, "icons", "logo-gui.png")))
- self.contextMenu = QMenu()
- self.showAction = QAction(_("Show"), self.contextMenu)
- self.showAction.setCheckable(True)
- self.showAction.setChecked(True)
- self.showAction.setDisabled(True)
- self.contextMenu.addAction(self.showAction)
- self.exitAction = QAction(QIcon(join(pypath, "icons", "close.png")), _("Exit"), self.contextMenu)
- self.contextMenu.addAction(self.exitAction)
- self.setContextMenu(self.contextMenu)
-
- self.connect(self, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.clicked)
-
- def mainWindowHidden(self):
- self.showAction.setChecked(False)
-
- def clicked(self, reason):
- if self.showAction.isEnabled():
- if reason == QSystemTrayIcon.Trigger:
- self.showAction.toggle()
-
-class Notification(QObject):
- def __init__(self, tray):
- QObject.__init__(self)
- self.tray = tray
- self.usePynotify = False
-
- try:
- self.usePynotify = pynotify.init("icon-summary-body")
- except:
- print "init error"
-
- def showMessage(self, body):
- if self.usePynotify:
- n = pynotify.Notification("pyload", body, join(pypath, "icons", "logo.png"))
- try:
- n.set_hint_string("x-canonical-append", "")
- except:
- pass
- n.show()
- else:
- self.tray.showMessage("pyload", body)
-
-if __name__ == "__main__":
- renameProcess('pyLoadGui')
- app = main()
- app.loop()
-
diff --git a/pyload-cli.py b/pyload-cli.py
new file mode 100644
index 000000000..0ef70368b
--- /dev/null
+++ b/pyload-cli.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from pyload.cli.Cli import main
+
+if __name__ == "__main__":
+ main()
diff --git a/pyload.py b/pyload.py
new file mode 100755
index 000000000..dc1957928
--- /dev/null
+++ b/pyload.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from pyload.Core import main
+
+if __name__ == "__main__":
+ main()
diff --git a/pyload/Core.py b/pyload/Core.py
new file mode 100755
index 000000000..ad4412a87
--- /dev/null
+++ b/pyload/Core.py
@@ -0,0 +1,686 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay, sebnapi, spoob, vuolter
+# @version: v0.4.10
+
+from __future__ import with_statement
+
+import pyload
+import __builtin__
+
+from getopt import getopt, GetoptError
+import pyload.utils.pylgettext as gettext
+from imp import find_module
+import logging
+import logging.handlers
+import os
+from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close
+from os.path import exists, join
+import signal
+import subprocess
+import sys
+from sys import argv, executable, exit
+from time import time, sleep
+from traceback import print_exc
+
+from pyload.manager.Account import AccountManager
+from pyload.manager.Captcha import CaptchaManager
+from pyload.config.Parser import ConfigParser
+from pyload.manager.Plugin import PluginManager
+from pyload.manager.Event import PullManager
+from pyload.network.RequestFactory import RequestFactory
+from pyload.manager.thread.Server import WebServer
+from pyload.manager.event.Scheduler import Scheduler
+from pyload.network.JsEngine import JsEngine
+from pyload import remote
+from pyload.manager.Remote import RemoteManager
+from pyload.database import DatabaseBackend, FileHandler
+
+from pyload.utils import freeSpace, formatSize, get_console_encoding
+
+from codecs import getwriter
+
+enc = get_console_encoding(sys.stdout.encoding)
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+
+# TODO List
+# - configurable auth system ldap/mysql
+# - cron job like sheduler
+class Core(object):
+ """pyLoad Core, one tool to rule them all... (the filehosters) :D"""
+
+
+ def __init__(self):
+ self.doDebug = False
+ self.running = False
+ self.daemon = False
+ self.remote = True
+ self.arg_links = []
+ self.pidfile = "pyload.pid"
+ self.deleteLinks = False #: will delete links on startup
+
+ if len(argv) > 1:
+ try:
+ options, args = getopt(argv[1:], 'vchdusqp:',
+ ["version", "clear", "clean", "help", "debug", "user",
+ "setup", "configdir=", "changedir", "daemon",
+ "quit", "status", "no-remote","pidfile="])
+
+ for option, argument in options:
+ if option in ("-v", "--version"):
+ print "pyLoad", pyload.__version__
+ exit()
+ elif option in ("-p", "--pidfile"):
+ self.pidfile = argument
+ elif option == "--daemon":
+ self.daemon = True
+ elif option in ("-c", "--clear"):
+ self.deleteLinks = True
+ elif option in ("-h", "--help"):
+ self.print_help()
+ exit()
+ elif option in ("-d", "--debug"):
+ self.doDebug = True
+ elif option in ("-u", "--user"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.set_user()
+ exit()
+ elif option in ("-s", "--setup"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.start()
+ exit()
+ elif option == "--changedir":
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.conf_path(True)
+ exit()
+ elif option in ("-q", "--quit"):
+ self.quitInstance()
+ exit()
+ elif option == "--status":
+ pid = self.isAlreadyRunning()
+ if self.isAlreadyRunning():
+ print pid
+ exit(0)
+ else:
+ print "false"
+ exit(1)
+ elif option == "--clean":
+ self.cleanTree()
+ exit()
+ elif option == "--no-remote":
+ self.remote = False
+
+ except GetoptError:
+ print 'Unknown Argument(s) "%s"' % " ".join(argv[1:])
+ self.print_help()
+ exit()
+
+
+ def print_help(self):
+ print
+ print "pyLoad v%s 2008-2015 the pyLoad Team" % pyload.__version__
+ print
+ if sys.argv[0].endswith(".py"):
+ print "Usage: python pyload.py [options]"
+ else:
+ print "Usage: pyload [options]"
+ print
+ print "<Options>"
+ print " -v, --version", " " * 10, "Print version to terminal"
+ print " -c, --clear", " " * 12, "Delete all saved packages/links"
+ # print " -a, --add=<link/list>", " " * 2, "Add the specified links"
+ print " -u, --user", " " * 13, "Manages users"
+ print " -d, --debug", " " * 12, "Enable debug mode"
+ print " -s, --setup", " " * 12, "Run Setup Assistant"
+ print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory"
+ print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
+ print " --changedir", " " * 12, "Change config dir permanently"
+ print " --daemon", " " * 15, "Daemonmize after start"
+ print " --no-remote", " " * 12, "Disable remote access (saves RAM)"
+ print " --status", " " * 15, "Display pid if running or False"
+ print " --clean", " " * 16, "Remove .pyc/.pyo files"
+ print " -q, --quit", " " * 13, "Quit running pyLoad instance"
+ print " -h, --help", " " * 13, "Display this help screen"
+ print
+
+
+ def toggle_pause(self):
+ if self.threadManager.pause:
+ self.threadManager.pause = False
+ return False
+ elif not self.threadManager.pause:
+ self.threadManager.pause = True
+ return True
+
+
+ def quit(self, a, b):
+ self.shutdown()
+ self.log.info(_("Received Quit signal"))
+ _exit(1)
+
+
+ def writePidFile(self):
+ self.deletePidFile()
+ pid = os.getpid()
+ with open(self.pidfile, "wb") as f:
+ f.write(str(pid))
+
+
+ def deletePidFile(self):
+ if self.checkPidFile():
+ self.log.debug("Deleting old pidfile %s" % self.pidfile)
+ os.remove(self.pidfile)
+
+
+ def checkPidFile(self):
+ """ return pid as int or 0"""
+ if os.path.isfile(self.pidfile):
+ with open(self.pidfile, "rb") as f:
+ pid = f.read().strip()
+ if pid:
+ pid = int(pid)
+ return pid
+
+ return 0
+
+
+ def isAlreadyRunning(self):
+ pid = self.checkPidFile()
+ if not pid or os.name == "nt":
+ return False
+ try:
+ os.kill(pid, 0) #: 0 - default signal (does nothing)
+ except Exception:
+ return 0
+
+ return pid
+
+
+ def quitInstance(self):
+ if os.name == "nt":
+ print "Not supported on windows."
+ return
+
+ pid = self.isAlreadyRunning()
+ if not pid:
+ print "No pyLoad running."
+ return
+
+ try:
+ os.kill(pid, 3) #: SIGUIT
+
+ t = time()
+ print "waiting for pyLoad to quit"
+
+ while exists(self.pidfile) and t + 10 > time():
+ sleep(0.25)
+
+ if not exists(self.pidfile):
+ print "pyLoad successfully stopped"
+ else:
+ os.kill(pid, 9) #: SIGKILL
+ print "pyLoad did not respond"
+ print "Kill signal was send to process with id %s" % pid
+
+ except Exception:
+ print "Error quitting pyLoad"
+
+
+ def cleanTree(self):
+ for path, dirs, files in walk(self.path("")):
+ for f in files:
+ if not f.endswith(".pyo") and not f.endswith(".pyc"):
+ continue
+
+ if "_25" in f or "_26" in f or "_27" in f:
+ continue
+
+ print join(path, f)
+ remove(join(path, f))
+
+
+ def start(self, rpc=True, web=True):
+ """ starts the fun :D """
+
+ self.version = pyload.__version__
+
+ if not exists("pyload.conf"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ print "This is your first start, running configuration assistent now."
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ res = False
+ try:
+ res = s.start()
+ except SystemExit:
+ pass
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ except Exception:
+ res = False
+ print_exc()
+ print "Setup failed"
+ if not res:
+ remove("pyload.conf")
+
+ exit()
+
+ try: signal.signal(signal.SIGQUIT, self.quit)
+ except Exception:
+ pass
+
+ self.config = ConfigParser()
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("pyLoad", self.path("locale"),
+ languages=[self.config.get("general", "language"), "en"], fallback=True)
+ translation.install(True)
+
+ self.debug = self.doDebug or self.config.get("general", "debug_mode")
+ self.remote &= self.config.get("remote", "activated")
+
+ pid = self.isAlreadyRunning()
+ if pid:
+ print _("pyLoad already running with pid %s") % pid
+ exit()
+
+ if os.name != "nt" and self.config.get("general", "renice"):
+ os.system("renice %d %d" % (self.config.get("general", "renice"), os.getpid()))
+
+ if self.config.get("permission", "change_group"):
+ if os.name != "nt":
+ try:
+ from grp import getgrnam
+
+ group = getgrnam(self.config.get("permission", "group"))
+ os.setgid(group[2])
+ except Exception, e:
+ print _("Failed changing group: %s") % e
+
+ if self.config.get("permission", "change_user"):
+ if os.name != "nt":
+ try:
+ from pwd import getpwnam
+
+ user = getpwnam(self.config.get("permission", "user"))
+ os.setuid(user[2])
+ except Exception, e:
+ print _("Failed changing user: %s") % e
+
+ self.check_file(self.config.get("log", "log_folder"), _("folder for logs"), True)
+
+ if self.debug:
+ self.init_logger(logging.DEBUG) #: logging level
+ else:
+ self.init_logger(logging.INFO) #: logging level
+
+ self.do_kill = False
+ self.do_restart = False
+ self.shuttedDown = False
+
+ self.log.info(_("Starting") + " pyLoad %s" % pyload.__version__)
+ self.log.info(_("Using home directory: %s") % getcwd())
+
+ self.writePidFile()
+
+ #@TODO refractor
+
+ remote.activated = self.remote
+ self.log.debug("Remote activated: %s" % self.remote)
+
+ self.check_install("Crypto", _("pycrypto to decode container files"))
+ # img = self.check_install("Image", _("Python Image Library (PIL) for captcha reading"))
+ # self.check_install("pycurl", _("pycurl to download any files"), True, True)
+ self.check_file("tmp", _("folder for temporary files"), True)
+ # tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True
+
+ self.captcha = True #: checks seems to fail, although tesseract is available
+
+ self.check_file(self.config.get("general", "download_folder"), _("folder for downloads"), True)
+
+ if self.config.get("ssl", "activated"):
+ self.check_install("OpenSSL", _("OpenSSL for secure connection"))
+
+ self.setupDB()
+ if self.config.oldRemoteData:
+ self.log.info(_("Moving old user config to DB"))
+ self.db.addUser(self.config.oldRemoteData['username'], self.config.oldRemoteData['password'])
+
+ self.log.info(_("Please check your logindata with ./pyload.py -u"))
+
+ if self.deleteLinks:
+ self.log.info(_("All links removed"))
+ self.db.purgeLinks()
+
+ self.requestFactory = RequestFactory(self)
+ __builtin__.pyreq = self.requestFactory
+
+ self.lastClientConnected = 0
+
+ # later imported because they would trigger api import, and remote value not set correctly
+ from pyload import api
+ from pyload.manager.Addon import AddonManager
+ from pyload.manager.Thread import ThreadManager
+
+ if api.activated != self.remote:
+ self.log.warning("Import error: API remote status not correct.")
+
+ self.api = api.Api(self)
+
+ self.scheduler = Scheduler(self)
+
+ # hell yeah, so many important managers :D
+ self.pluginManager = PluginManager(self)
+ self.pullManager = PullManager(self)
+ self.accountManager = AccountManager(self)
+ self.threadManager = ThreadManager(self)
+ self.captchaManager = CaptchaManager(self)
+ self.addonManager = AddonManager(self)
+ self.remoteManager = RemoteManager(self)
+
+ self.js = JsEngine(self)
+
+ self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
+
+ if rpc:
+ self.remoteManager.startBackends()
+
+ if web:
+ self.init_webserver()
+
+ spaceLeft = freeSpace(self.config.get("general", "download_folder"))
+
+ self.log.info(_("Free space: %s") % formatSize(spaceLeft))
+
+ self.config.save() #: save so config files gets filled
+
+ link_file = join(pypath, "links.txt")
+
+ if exists(link_file):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ link_file = "links.txt"
+ if exists(link_file):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ # self.scheduler.addJob(0, self.accountManager.getAccountInfos)
+ self.log.info(_("Activating Accounts..."))
+ self.accountManager.getAccountInfos()
+
+ self.threadManager.pause = False
+ self.running = True
+
+ self.log.info(_("Activating Plugins..."))
+ self.addonManager.coreReady()
+
+ self.log.info(_("pyLoad is up and running"))
+
+ locals().clear()
+
+ while True:
+ sleep(2)
+ if self.do_restart:
+ self.log.info(_("restarting pyLoad"))
+ self.restart()
+ if self.do_kill:
+ self.shutdown()
+ self.log.info(_("pyLoad quits"))
+ self.removeLogger()
+ _exit(0) #@TODO thrift blocks shutdown
+
+ self.threadManager.work()
+ self.scheduler.work()
+
+
+ def setupDB(self):
+ self.db = DatabaseBackend(self) #: the backend
+ self.db.setup()
+
+ self.files = FileHandler(self)
+ self.db.manager = self.files #: ugly?
+
+
+ def init_webserver(self):
+ if self.config.get("webui", "activated"):
+ self.webserver = WebServer(self)
+ self.webserver.start()
+
+
+ def init_logger(self, level):
+ self.log = logging.getLogger("log")
+ self.log.setLevel(level)
+
+ date_fmt = "%Y-%m-%d %H:%M:%S"
+ fh_fmt = "%(asctime)s %(levelname)-8s %(message)s"
+
+ fh_frm = logging.Formatter(fh_fmt, date_fmt) #: file handler formatter
+ console_frm = fh_frm #: console formatter did not use colors as default
+
+ # Console formatter with colors
+ if self.config.get("log", "color_console"):
+ import colorlog
+
+ if self.config.get("log", "console_mode") == "label":
+ c_fmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)-8s%(reset)s %(message)s"
+ clr = {
+ 'DEBUG' : "bg_cyan" ,
+ 'INFO' : "bg_green" ,
+ 'WARNING' : "bg_yellow",
+ 'ERROR' : "bg_red" ,
+ 'CRITICAL': "bg_purple",
+ }
+
+ else:
+ c_fmt = "%(log_color)s%(asctime)s %(levelname)-8s %(message)s"
+ clr = {
+ 'DEBUG' : "cyan" ,
+ 'WARNING' : "yellow",
+ 'ERROR' : "red" ,
+ 'CRITICAL': "purple",
+ }
+
+ console_frm = colorlog.ColoredFormatter(c_fmt, date_fmt, clr)
+
+ # Set console formatter
+ console = logging.StreamHandler(sys.stdout)
+ console.setFormatter(console_frm)
+ self.log.addHandler(console)
+
+ log_folder = self.config.get("log", "log_folder")
+ if not exists(log_folder):
+ makedirs(log_folder, 0700)
+
+ # Set file handler formatter
+ if self.config.get("log", "file_log"):
+ if self.config.get("log", "log_rotate"):
+ file_handler = logging.handlers.RotatingFileHandler(join(log_folder, 'log.txt'),
+ maxBytes=self.config.get("log", "log_size") * 1024,
+ backupCount=int(self.config.get("log", "log_count")),
+ encoding="utf8")
+ else:
+ file_handler = logging.FileHandler(join(log_folder, 'log.txt'), encoding="utf8")
+
+ file_handler.setFormatter(fh_frm)
+ self.log.addHandler(file_handler)
+
+
+ def removeLogger(self):
+ for h in list(self.log.handlers):
+ self.log.removeHandler(h)
+ h.close()
+
+
+ def check_install(self, check_name, legend, python=True, essential=False):
+ """check wether needed tools are installed"""
+ try:
+ if python:
+ find_module(check_name)
+ else:
+ pipe = subprocess.PIPE
+ subprocess.Popen(check_name, stdout=pipe, stderr=pipe)
+
+ return True
+ except Exception:
+ if essential:
+ self.log.info(_("Install %s") % legend)
+ exit()
+
+ return False
+
+
+ def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False):
+ """check wether needed files exists"""
+ tmp_names = []
+ if not type(check_names) == list:
+ tmp_names.append(check_names)
+ else:
+ tmp_names.extend(check_names)
+ file_created = True
+ file_exists = True
+ for tmp_name in tmp_names:
+ if not exists(tmp_name):
+ file_exists = False
+ if empty:
+ try:
+ if folder:
+ tmp_name = tmp_name.replace("/", sep)
+ makedirs(tmp_name)
+ else:
+ open(tmp_name, "w")
+ except Exception:
+ file_created = False
+ else:
+ file_created = False
+
+ if not file_exists and not quiet:
+ if file_created:
+ # self.log.info( _("%s created") % description )
+ pass
+ else:
+ if not empty:
+ self.log.warning(
+ _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name})
+ else:
+ print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}
+ if essential:
+ exit()
+
+
+ def isClientConnected(self):
+ return (self.lastClientConnected + 30) > time()
+
+
+ def restart(self):
+ self.shutdown()
+ chdir(owd)
+ # close some open fds
+ for i in xrange(3, 50):
+ try:
+ close(i)
+ except Exception:
+ pass
+
+ execl(executable, executable, *sys.argv)
+ _exit(0)
+
+
+ def shutdown(self):
+ self.log.info(_("shutting down..."))
+ try:
+ if self.config.get("webui", "activated") and hasattr(self, "webserver"):
+ self.webserver.quit()
+
+ for thread in list(self.threadManager.threads):
+ thread.put("quit")
+ pyfiles = self.files.cache.values()
+
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ self.addonManager.coreExiting()
+
+ except Exception:
+ if self.debug:
+ print_exc()
+ self.log.info(_("error while shutting down"))
+
+ finally:
+ self.files.syncSave()
+ self.shuttedDown = True
+
+ self.deletePidFile()
+
+
+ def path(self, *args):
+ return join(pypath, *args)
+
+
+def deamon():
+ try:
+ pid = os.fork()
+ if pid > 0:
+ sys.exit(0)
+ except OSError, e:
+ print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+ # decouple from parent environment
+ os.setsid()
+ os.umask(0)
+
+ # do second fork
+ try:
+ pid = os.fork()
+ if pid > 0:
+ # exit from second parent, print eventual PID before
+ print "Daemon PID %d" % pid
+ sys.exit(0)
+ except OSError, e:
+ print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+ # Iterate through and close some file descriptors.
+ for fd in xrange(0, 3):
+ try:
+ os.close(fd)
+ except OSError: # ERROR, fd wasn't open to begin with (ignored)
+ pass
+
+ os.open(os.devnull, os.O_RDWR) # standard input (0)
+ os.dup2(0, 1) # standard output (1)
+ os.dup2(0, 2)
+
+ pyload_core = Core()
+ pyload_core.start()
+
+
+def main():
+ if "--daemon" in sys.argv:
+ deamon()
+ else:
+ pyload_core = Core()
+ try:
+ pyload_core.start()
+ except KeyboardInterrupt:
+ pyload_core.shutdown()
+ pyload_core.log.info(_("killed pyLoad from Terminal"))
+ pyload_core.removeLogger()
+ _exit(1)
+
+# And so it begins...
+if __name__ == "__main__":
+ main()
diff --git a/pyload/__init__.py b/pyload/__init__.py
new file mode 100644
index 000000000..7f5ac0667
--- /dev/null
+++ b/pyload/__init__.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import __builtin__
+
+import os
+import platform
+import sys
+
+from codecs import getwriter
+
+from pyload.utils import get_console_encoding
+
+
+__all__ = ["__status_code__", "__status__", "__version_info__", "__version__", "__author_name__", "__author_mail__", "__license__"]
+
+__version_info__ = (0, 4, 10)
+__version__ = '.'.join(map(str, __version_info__))
+
+__status_code__ = 4
+__status__ = {1: "Planning",
+ 2: "Pre-Alpha",
+ 3: "Alpha",
+ 4: "Beta",
+ 5: "Production/Stable",
+ 6: "Mature",
+ 7: "Inactive"}[__status_code__] #: PyPI Development Status Classifiers
+
+__description__ = "Fast, lightweight and full featured download manager"
+
+__license__ = "GNU General Public License v3"
+
+__website__ = "http://pyload.org"
+
+__authors__ = [("Marius" , "mkaay@mkaay.de" ),
+ ("RaNaN" , "Mast3rRaNaN@hotmail.de"),
+ ("Stefano" , "l.stickell@yahoo.it" ),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("himbrr" , "himbrr@himbrr.ws" ),
+ ("sebnapi" , "" ),
+ ("spoob" , "spoob@gmx.de" ),
+ ("zoidberg10" , "zoidberg@mujmail.cz" )]
+
+
+################################# InitHomeDir #################################
+
+__builtin__.owd = os.path.abspath("") #: original working directory
+__builtin__.homedir = os.path.expanduser("~")
+__builtin__.rootdir = os.path.abspath(os.path.join(__file__, ".."))
+__builtin__.configdir = ""
+__builtin__.pypath = os.path.abspath(os.path.join(rootdir, ".."))
+
+
+if "64" in platform.machine():
+ sys.path.append(os.path.join(pypath, "lib64"))
+sys.path.append(os.path.join(pypath, "lib", "Python", "Lib"))
+sys.path.append(os.path.join(pypath, "lib"))
+
+
+sys.stdout = getwriter(get_console_encoding(sys.stdout.encoding))(sys.stdout, errors="replace")
+
+if homedir == "~" and os.name == "nt":
+ import ctypes
+
+ CSIDL_APPDATA = 26
+
+ _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW
+ _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND,
+ ctypes.c_int,
+ ctypes.wintypes.HANDLE,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.LPCWSTR]
+
+ path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
+
+ _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf)
+
+ __builtin__.homedir = path_buf.value
+
+try:
+ p = os.path.join(rootdir, "config", "configdir")
+
+ with open(p, "rb") as f:
+ configdir = f.read().strip()
+
+except IOError:
+ if os.name == "posix":
+ configdir = os.path.join(homedir, ".pyload")
+ else:
+ configdir = os.path.join(homedir, "pyload")
+
+try:
+ if not os.path.exists(configdir):
+ os.makedirs(configdir, 0700)
+
+ os.chdir(configdir)
+
+except IOError, e:
+ print >> sys.stderr, "configdir init failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+else:
+ __builtin__.configdir = configdir
diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py
new file mode 100644
index 000000000..442e9ef95
--- /dev/null
+++ b/pyload/api/__init__.py
@@ -0,0 +1,1052 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+from base64 import standard_b64encode
+from os.path import join
+from time import time
+import re
+
+from urlparse import urlparse
+
+from pyload.datatype.File import PyFile
+from pyload.utils.packagetools import parseNames
+from pyload.network.RequestFactory import getURL
+from pyload.remote import activated
+from pyload.utils import compare_time, freeSpace, safe_filename
+
+if activated:
+ try:
+ from thrift.protocol import TBase
+ from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+ from pyload.remote.thriftbackend.thriftgen.pyload.Pyload import Iface
+
+ BaseObject = TBase
+
+ except ImportError:
+ from pyload.api.types import *
+
+ print "Thrift not imported"
+
+else:
+ from pyload.api.types import *
+
+# contains function names mapped to their permissions
+# unlisted functions are for admins only
+permMap = {}
+
+
+# decorator only called on init, never initialized, so has no effect on runtime
+def permission(bits):
+ class _Dec(object):
+
+ def __new__(cls, func, *args, **kwargs):
+ permMap[func.__name__] = bits
+ return func
+ return _Dec
+
+
+urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&\[\]\|]*)", re.IGNORECASE)
+
+
+class PERMS(object):
+ ALL = 0 #: requires no permission, but login
+ ADD = 1 #: can add packages
+ DELETE = 2 #: can delete packages
+ STATUS = 4 #: see and change server status
+ LIST = 16 #: see queue and collector
+ MODIFY = 32 #: moddify some attribute of downloads
+ DOWNLOAD = 64 #: can download from webinterface
+ SETTINGS = 128 #: can access settings
+ ACCOUNTS = 256 #: can access accounts
+ LOGS = 512 #: can see server logs
+
+
+class ROLE(object):
+ ADMIN = 0 #: admin has all permissions implicit
+ USER = 1
+
+
+def has_permission(userperms, perms):
+ # bytewise or perms before if needed
+ return perms == (userperms & perms)
+
+
+class Api(Iface):
+ """
+ **pyLoads API**
+
+ This is accessible either internal via core.api or via thrift backend.
+
+ see Thrift specification file remote/thriftbackend/pyload.thrift\
+ for information about data structures and what methods are usuable with rpc.
+
+ Most methods requires specific permissions, please look at the source code if you need to know.\
+ These can be configured via webinterface.
+ Admin user have all permissions, and are the only ones who can access the methods with no specific permission.
+ """
+ EXTERNAL = Iface #: let the json api know which methods are external
+
+
+ def __init__(self, core):
+ self.core = core
+
+
+ def _convertPyFile(self, p):
+ fdata = FileData(p['id'], p['url'], p['name'], p['plugin'], p['size'],
+ p['format_size'], p['status'], p['statusmsg'],
+ p['package'], p['error'], p['order'])
+ return fdata
+
+
+ def _convertConfigFormat(self, c):
+ sections = {}
+ for sectionName, sub in c.iteritems():
+ section = ConfigSection(sectionName, sub['desc'])
+ items = []
+ for key, data in sub.iteritems():
+ if key in ("desc", "outline"):
+ continue
+ item = ConfigItem()
+ item.name = key
+ item.description = data['desc']
+ item.value = str(data['value']) if not isinstance(data['value'], basestring) else data['value']
+ item.type = data['type']
+ items.append(item)
+ section.items = items
+ sections[sectionName] = section
+ if "outline" in sub:
+ section.outline = sub['outline']
+ return sections
+
+
+ @permission(PERMS.SETTINGS)
+ def getConfigValue(self, category, option, section="core"):
+ """Retrieve config value.
+
+ :param category: name of category, or plugin
+ :param option: config option
+ :param section: 'plugin' or 'core'
+ :return: config value as string
+ """
+ if section == "core":
+ value = self.core.config[category][option]
+ else:
+ value = self.core.config.getPlugin(category, option)
+ return str(value)
+
+
+ @permission(PERMS.SETTINGS)
+ def setConfigValue(self, category, option, value, section="core"):
+ """Set new config value.
+
+ :param section:
+ :param option:
+ :param value: new config value
+ :param section: 'plugin' or 'core
+ """
+ self.core.addonManager.dispatchEvent("config-changed", category, option, value, section)
+ if section == "core":
+ self.core.config[category][option] = value
+ if option in ("limit_speed", "max_speed"): #: not so nice to update the limit
+ self.core.requestFactory.updateBucket()
+ elif section == "plugin":
+ self.core.config.setPlugin(category, option, value)
+
+
+ @permission(PERMS.SETTINGS)
+ def getConfig(self):
+ """Retrieves complete config of core.
+
+ :return: list of `ConfigSection`
+ """
+ return self._convertConfigFormat(self.core.config.config)
+
+
+ def getConfigDict(self):
+ """Retrieves complete config in dict format, not for RPC.
+
+ :return: dict
+ """
+ return self.core.config.config
+
+
+ @permission(PERMS.SETTINGS)
+ def getPluginConfig(self):
+ """Retrieves complete config for all plugins.
+
+ :return: list of `ConfigSection`
+ """
+ return self._convertConfigFormat(self.core.config.plugin)
+
+
+ def getPluginConfigDict(self):
+ """Plugin config as dict, not for RPC.
+
+ :return: dict
+ """
+ return self.core.config.plugin
+
+
+ @permission(PERMS.STATUS)
+ def pauseServer(self):
+ """Pause server: Tt wont start any new downloads, but nothing gets aborted."""
+ self.core.threadManager.pause = True
+
+
+ @permission(PERMS.STATUS)
+ def unpauseServer(self):
+ """Unpause server: New Downloads will be started."""
+ self.core.threadManager.pause = False
+
+
+ @permission(PERMS.STATUS)
+ def togglePause(self):
+ """Toggle pause state.
+
+ :return: new pause state
+ """
+ self.core.threadManager.pause ^= True
+ return self.core.threadManager.pause
+
+
+ @permission(PERMS.STATUS)
+ def toggleReconnect(self):
+ """Toggle reconnect activation.
+
+ :return: new reconnect state
+ """
+ self.core.config['reconnect']['activated'] ^= True
+ return self.core.config.get("reconnect", "activated")
+
+
+ @permission(PERMS.LIST)
+ def statusServer(self):
+ """Some general information about the current status of pyLoad.
+
+ :return: `ServerStatus`
+ """
+ serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()),
+ self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
+ not self.core.threadManager.pause and self.isTimeDownload(),
+ self.core.config.get("reconnect", "activated") and self.isTimeReconnect())
+ for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]:
+ serverStatus.speed += pyfile.getSpeed() #: bytes/s
+ return serverStatus
+
+
+ @permission(PERMS.STATUS)
+ def freeSpace(self):
+ """Available free space at download directory in bytes"""
+ return freeSpace(self.core.config.get("general", "download_folder"))
+
+
+ @permission(PERMS.ALL)
+ def getServerVersion(self):
+ """pyLoad Core version """
+ return self.core.version
+
+
+ def kill(self):
+ """Clean way to quit pyLoad"""
+ self.core.do_kill = True
+
+
+ def restart(self):
+ """Restart pyload core"""
+ self.core.do_restart = True
+
+
+ @permission(PERMS.LOGS)
+ def getLog(self, offset=0):
+ """Returns most recent log entries.
+
+ :param offset: line offset
+ :return: List of log entries
+ """
+ filename = join(self.core.config.get("log", "log_folder"), 'log.txt')
+ try:
+ with open(filename, "r") as fh:
+ lines = fh.readlines()
+ if offset >= len(lines):
+ return []
+ return lines[offset:]
+ except Exception:
+ return ['No log available']
+
+
+ @permission(PERMS.STATUS)
+ def isTimeDownload(self):
+ """Checks if pyload will start new downloads according to time in config.
+
+ :return: bool
+ """
+ start = self.core.config.get("downloadTime", "start").split(":")
+ end = self.core.config.get("downloadTime", "end").split(":")
+ return compare_time(start, end)
+
+
+ @permission(PERMS.STATUS)
+ def isTimeReconnect(self):
+ """Checks if pyload will try to make a reconnect
+
+ :return: bool
+ """
+ start = self.core.config.get("reconnect", "startTime").split(":")
+ end = self.core.config.get("reconnect", "endTime").split(":")
+ return compare_time(start, end) and self.core.config.get("reconnect", "activated")
+
+
+ @permission(PERMS.LIST)
+ def statusDownloads(self):
+ """ Status off all currently running downloads.
+
+ :return: list of `DownloadStatus`
+ """
+ data = []
+ for pyfile in self.core.threadManager.getActiveFiles():
+ if not isinstance(pyfile, PyFile):
+ continue
+ data.append(DownloadInfo(
+ pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(),
+ pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(),
+ pyfile.status, pyfile.getStatusName(), pyfile.formatWait(),
+ pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname))
+ return data
+
+
+ @permission(PERMS.ADD)
+ def addPackage(self, name, links, dest=Destination.Queue):
+ """Adds a package, with links to desired destination.
+
+ :param name: name of the new package
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: package id of the new package
+ """
+ if self.core.config.get("general", "folder_per_package"):
+ folder = urlparse(name).path.split("/")[-1]
+ else:
+ folder = ""
+
+ folder = safe_filename(folder)
+
+ pid = self.core.files.addPackage(name, folder, dest)
+
+ self.core.files.addLinks(links, pid)
+
+ self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)})
+
+ self.core.files.save()
+
+ return pid
+
+
+ @permission(PERMS.ADD)
+ def parseURLs(self, html=None, url=None):
+ """Parses html content or any arbitaty text for links and returns result of `checkURLs`
+
+ :param html: html source
+ :return:
+ """
+ urls = []
+ if html:
+ urls += [x[0] for x in urlmatcher.findall(html)]
+ if url:
+ page = getURL(url)
+ urls += [x[0] for x in urlmatcher.findall(page)]
+ # remove duplicates
+ return self.checkURLs(set(urls))
+
+
+ @permission(PERMS.ADD)
+ def checkURLs(self, urls):
+ """ Gets urls and returns pluginname mapped to list of matches urls.
+
+ :param urls:
+ :return: {plugin: urls}
+ """
+ data = self.core.pluginManager.parseUrls(urls)
+ plugins = {}
+
+ for url, plugintype, pluginname in data:
+ try:
+ plugins[plugintype][pluginname].append(url)
+ except Exception:
+ plugins[plugintype][pluginname] = [url]
+
+ return plugins
+
+
+ @permission(PERMS.ADD)
+ def checkOnlineStatus(self, urls):
+ """ initiates online status check
+
+ :param urls:
+ :return: initial set of data as `OnlineCheck` instance containing the result id
+ """
+ data = self.core.pluginManager.parseUrls(urls)
+
+ rid = self.core.threadManager.createResultThread(data, False)
+
+ tmp = [(url, (url, OnlineStatus(url, (plugintype, pluginname), "unknown", 3, 0))) for url, plugintype, pluginname in data]
+ data = parseNames(tmp)
+ result = {}
+ for k, v in data.iteritems():
+ for url, status in v:
+ status.packagename = k
+ result[url] = status
+
+ return OnlineCheck(rid, result)
+
+
+ @permission(PERMS.ADD)
+ def checkOnlineStatusContainer(self, urls, container, data):
+ """ checks online status of urls and a submited container file
+
+ :param urls: list of urls
+ :param container: container file name
+ :param data: file content
+ :return: online check
+ """
+ with open(join(self.core.config.get("general", "download_folder"), "tmp_" + container), "wb") as th:
+ th.write(str(data))
+ return self.checkOnlineStatus(urls + [th.name])
+
+
+ @permission(PERMS.ADD)
+ def pollResults(self, rid):
+ """ Polls the result available for ResultID
+
+ :param rid: `ResultID`
+ :return: `OnlineCheck`, if rid is -1 then no more data available
+ """
+ result = self.core.threadManager.getInfoResult(rid)
+ if "ALL_INFO_FETCHED" in result:
+ del result['ALL_INFO_FETCHED']
+ return OnlineCheck(-1, result)
+ else:
+ return OnlineCheck(rid, result)
+
+
+ @permission(PERMS.ADD)
+ def generatePackages(self, links):
+ """ Parses links, generates packages names from urls
+
+ :param links: list of urls
+ :return: package names mapped to urls
+ """
+ return parseNames((x, x) for x in links)
+
+
+ @permission(PERMS.ADD)
+ def generateAndAddPackages(self, links, dest=Destination.Queue):
+ """Generates and add packages
+
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: list of package ids
+ """
+ return [self.addPackage(name, urls, dest) for name, urls
+ in self.generatePackages(links).iteritems()]
+
+
+ @permission(PERMS.ADD)
+ def checkAndAddPackages(self, links, dest=Destination.Queue):
+ """Checks online status, retrieves names, and will add packages.\
+ Because of this packages are not added immediatly, only for internal use.
+
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: None
+ """
+ data = self.core.pluginManager.parseUrls(links)
+ self.core.threadManager.createResultThread(data, True)
+
+
+ @permission(PERMS.LIST)
+ def getPackageData(self, pid):
+ """Returns complete information about package, and included files.
+
+ :param pid: package id
+ :return: `PackageData` with .links attribute
+ """
+ data = self.core.files.getPackageData(int(pid))
+ if not data:
+ raise PackageDoesNotExists(pid)
+ return PackageData(data['id'], data['name'], data['folder'], data['site'], data['password'],
+ data['queue'], data['order'],
+ links=[self._convertPyFile(x) for x in data['links'].itervalues()])
+
+
+ @permission(PERMS.LIST)
+ def getPackageInfo(self, pid):
+ """Returns information about package, without detailed information about containing files
+
+ :param pid: package id
+ :return: `PackageData` with .fid attribute
+ """
+ data = self.core.files.getPackageData(int(pid))
+
+ if not data:
+ raise PackageDoesNotExists(pid)
+ return PackageData(data['id'], data['name'], data['folder'], data['site'], data['password'],
+ data['queue'], data['order'],
+ fids=[int(x) for x in data['links']])
+
+
+ @permission(PERMS.LIST)
+ def getFileData(self, fid):
+ """Get complete information about a specific file.
+
+ :param fid: file id
+ :return: `FileData`
+ """
+ info = self.core.files.getFileData(int(fid))
+ if not info:
+ raise FileDoesNotExists(fid)
+ return self._convertPyFile(info.values()[0])
+
+
+ @permission(PERMS.DELETE)
+ def deleteFiles(self, fids):
+ """Deletes several file entries from pyload.
+
+ :param fids: list of file ids
+ """
+ for fid in fids:
+ self.core.files.deleteLink(int(fid))
+ self.core.files.save()
+
+
+ @permission(PERMS.DELETE)
+ def deletePackages(self, pids):
+ """Deletes packages and containing links.
+
+ :param pids: list of package ids
+ """
+ for pid in pids:
+ self.core.files.deletePackage(int(pid))
+ self.core.files.save()
+
+
+ @permission(PERMS.LIST)
+ def getQueue(self):
+ """Returns info about queue and packages, **not** about files, see `getQueueData` \
+ or `getPackageData` instead.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'],
+ pack['password'], pack['queue'], pack['order'],
+ pack['linksdone'], pack['sizedone'], pack['sizetotal'],
+ pack['linkstotal'])
+ for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
+
+
+ @permission(PERMS.LIST)
+ def getQueueData(self):
+ """Return complete data about everything in queue, this is very expensive use it sparely.\
+ See `getQueue` for alternative.
+
+ :return: list of `PackageData`
+ """
+ return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'],
+ pack['password'], pack['queue'], pack['order'],
+ pack['linksdone'], pack['sizedone'], pack['sizetotal'],
+ links=[self._convertPyFile(x) for x in pack['links'].itervalues()])
+ for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
+
+
+ @permission(PERMS.LIST)
+ def getCollector(self):
+ """same as `getQueue` for collector.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'],
+ pack['password'], pack['queue'], pack['order'],
+ pack['linksdone'], pack['sizedone'], pack['sizetotal'],
+ pack['linkstotal'])
+ for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
+
+
+ @permission(PERMS.LIST)
+ def getCollectorData(self):
+ """same as `getQueueData` for collector.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'],
+ pack['password'], pack['queue'], pack['order'],
+ pack['linksdone'], pack['sizedone'], pack['sizetotal'],
+ links=[self._convertPyFile(x) for x in pack['links'].itervalues()])
+ for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
+
+
+ @permission(PERMS.ADD)
+ def addFiles(self, pid, links):
+ """Adds files to specific package.
+
+ :param pid: package id
+ :param links: list of urls
+ """
+ self.core.files.addLinks(links, int(pid))
+ self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
+ self.core.files.save()
+
+
+ @permission(PERMS.MODIFY)
+ def pushToQueue(self, pid):
+ """Moves package from Collector to Queue.
+
+ :param pid: package id
+ """
+ self.core.files.setPackageLocation(pid, Destination.Queue)
+
+
+ @permission(PERMS.MODIFY)
+ def pullFromQueue(self, pid):
+ """Moves package from Queue to Collector.
+
+ :param pid: package id
+ """
+ self.core.files.setPackageLocation(pid, Destination.Collector)
+
+
+ @permission(PERMS.MODIFY)
+ def restartPackage(self, pid):
+ """Restarts a package, resets every containing files.
+
+ :param pid: package id
+ """
+ self.core.files.restartPackage(int(pid))
+
+
+ @permission(PERMS.MODIFY)
+ def restartFile(self, fid):
+ """Resets file status, so it will be downloaded again.
+
+ :param fid: file id
+ """
+ self.core.files.restartFile(int(fid))
+
+
+ @permission(PERMS.MODIFY)
+ def recheckPackage(self, pid):
+ """Proofes online status of all files in a package, also a default action when package is added.
+
+ :param pid:
+ :return:
+ """
+ self.core.files.reCheckPackage(int(pid))
+
+
+ @permission(PERMS.MODIFY)
+ def stopAllDownloads(self):
+ """Aborts all running downloads."""
+
+ pyfiles = self.core.files.cache.values()
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+
+ @permission(PERMS.MODIFY)
+ def stopDownloads(self, fids):
+ """Aborts specific downloads.
+
+ :param fids: list of file ids
+ :return:
+ """
+ pyfiles = self.core.files.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.id in fids:
+ pyfile.abortDownload()
+
+
+ @permission(PERMS.MODIFY)
+ def setPackageName(self, pid, name):
+ """Renames a package.
+
+ :param pid: package id
+ :param name: new package name
+ """
+ pack = self.core.files.getPackage(pid)
+ pack.name = name
+ pack.sync()
+
+
+ @permission(PERMS.MODIFY)
+ def movePackage(self, destination, pid):
+ """Set a new package location.
+
+ :param destination: `Destination`
+ :param pid: package id
+ """
+ if destination in (0, 1):
+ self.core.files.setPackageLocation(pid, destination)
+
+
+ @permission(PERMS.MODIFY)
+ def moveFiles(self, fids, pid):
+ """Move multiple files to another package
+
+ :param fids: list of file ids
+ :param pid: destination package
+ :return:
+ """
+ # TODO: implement
+ pass
+
+
+ @permission(PERMS.ADD)
+ def uploadContainer(self, filename, data):
+ """Uploads and adds a container file to pyLoad.
+
+ :param filename: filename, extension is important so it can correctly decrypted
+ :param data: file content
+ """
+ with open(join(self.core.config.get("general", "download_folder"), "tmp_" + filename), "wb") as th:
+ th.write(str(data))
+ self.addPackage(th.name, [th.name], Destination.Queue)
+
+
+ @permission(PERMS.MODIFY)
+ def orderPackage(self, pid, position):
+ """Gives a package a new position.
+
+ :param pid: package id
+ :param position:
+ """
+ self.core.files.reorderPackage(pid, position)
+
+
+ @permission(PERMS.MODIFY)
+ def orderFile(self, fid, position):
+ """Gives a new position to a file within its package.
+
+ :param fid: file id
+ :param position:
+ """
+ self.core.files.reorderFile(fid, position)
+
+
+ @permission(PERMS.MODIFY)
+ def setPackageData(self, pid, data):
+ """Allows to modify several package attributes.
+
+ :param pid: package id
+ :param data: dict that maps attribute to desired value
+ """
+ package = self.core.files.getPackage(pid)
+ if not package:
+ raise PackageDoesNotExists(pid)
+ for key, value in data.iteritems():
+ if key == "id":
+ continue
+ setattr(package, key, value)
+ package.sync()
+ self.core.files.save()
+
+
+ @permission(PERMS.DELETE)
+ def deleteFinished(self):
+ """Deletes all finished files and completly finished packages.
+
+ :return: list of deleted package ids
+ """
+ return self.core.files.deleteFinishedLinks()
+
+
+ @permission(PERMS.MODIFY)
+ def restartFailed(self):
+ """Restarts all failed failes."""
+ self.core.files.restartFailed()
+
+
+ @permission(PERMS.LIST)
+ def getPackageOrder(self, destination):
+ """Returns information about package order.
+
+ :param destination: `Destination`
+ :return: dict mapping order to package id
+ """
+ packs = self.core.files.getInfoData(destination)
+ order = {}
+ for pid in packs:
+ pack = self.core.files.getPackageData(int(pid))
+ while pack['order'] in order.keys(): #: just in case
+ pack['order'] += 1
+ order[pack['order']] = pack['id']
+ return order
+
+
+ @permission(PERMS.LIST)
+ def getFileOrder(self, pid):
+ """Information about file order within package.
+
+ :param pid:
+ :return: dict mapping order to file id
+ """
+ rawdata = self.core.files.getPackageData(int(pid))
+ order = {}
+ for id, pyfile in rawdata['links'].iteritems():
+ while pyfile['order'] in order.keys(): #: just in case
+ pyfile['order'] += 1
+ order[pyfile['order']] = pyfile['id']
+ return order
+
+
+ @permission(PERMS.STATUS)
+ def isCaptchaWaiting(self):
+ """Indicates wether a captcha task is available
+
+ :return: bool
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTask()
+ return not task is None
+
+
+ @permission(PERMS.STATUS)
+ def getCaptchaTask(self, exclusive=False):
+ """Returns a captcha task
+
+ :param exclusive: unused
+ :return: `CaptchaTask`
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTask()
+ if task:
+ task.setWatingForUser(exclusive=exclusive)
+ data, type, result = task.getCaptcha()
+ ctask = CaptchaTask(int(task.id), standard_b64encode(data), type, result)
+ return ctask
+ return CaptchaTask(-1)
+
+
+ @permission(PERMS.STATUS)
+ def getCaptchaTaskStatus(self, tid):
+ """Get information about captcha task
+
+ :param tid: task id
+ :return: string
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTaskByID(tid)
+ return task.getStatus() if task else ""
+
+
+ @permission(PERMS.STATUS)
+ def setCaptchaResult(self, tid, result):
+ """Set result for a captcha task
+
+ :param tid: task id
+ :param result: captcha result
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTaskByID(tid)
+ if task:
+ task.setResult(result)
+ self.core.captchaManager.removeTask(task)
+
+
+ @permission(PERMS.STATUS)
+ def getEvents(self, uuid):
+ """Lists occured events, may be affected to changes in future.
+
+ :param uuid:
+ :return: list of `Events`
+ """
+ events = self.core.pullManager.getEvents(uuid)
+ new_events = []
+
+
+ def convDest(d):
+ return Destination.Queue if d == "queue" else Destination.Collector
+
+ for e in events:
+ event = EventInfo()
+ event.eventname = e[0]
+ if e[0] in ("update", "remove", "insert"):
+ event.id = e[3]
+ event.type = ElementType.Package if e[2] == "pack" else ElementType.File
+ event.destination = convDest(e[1])
+ elif e[0] == "order":
+ if e[1]:
+ event.id = e[1]
+ event.type = ElementType.Package if e[2] == "pack" else ElementType.File
+ event.destination = convDest(e[3])
+ elif e[0] == "reload":
+ event.destination = convDest(e[1])
+ new_events.append(event)
+ return new_events
+
+
+ @permission(PERMS.ACCOUNTS)
+ def getAccounts(self, refresh):
+ """Get information about all entered accounts.
+
+ :param refresh: reload account info
+ :return: list of `AccountInfo`
+ """
+ accs = self.core.accountManager.getAccountInfos(False, refresh)
+ for group in accs.values():
+ accounts = [AccountInfo(acc['validuntil'], acc['login'], acc['options'], acc['valid'],
+ acc['trafficleft'], acc['maxtraffic'], acc['premium'], acc['type'])
+ for acc in group]
+ return accounts or list()
+
+
+ @permission(PERMS.ALL)
+ def getAccountTypes(self):
+ """All available account types.
+
+ :return: list
+ """
+ return self.core.accountManager.accounts.keys()
+
+
+ @permission(PERMS.ACCOUNTS)
+ def updateAccount(self, plugin, account, password=None, options={}):
+ """Changes pw/options for specific account."""
+ self.core.accountManager.updateAccount(plugin, account, password, options)
+
+
+ @permission(PERMS.ACCOUNTS)
+ def removeAccount(self, plugin, account):
+ """Remove account from pyload.
+
+ :param plugin: pluginname
+ :param account: accountname
+ """
+ self.core.accountManager.removeAccount(plugin, account)
+
+
+ @permission(PERMS.ALL)
+ def login(self, username, password, remoteip=None):
+ """Login into pyLoad, this **must** be called when using rpc before any methods can be used.
+
+ :param username:
+ :param password:
+ :param remoteip: Omit this argument, its only used internal
+ :return: bool indicating login was successful
+ """
+ return bool(self.checkAuth(username, password, remoteip))
+
+
+ def checkAuth(self, username, password, remoteip=None):
+ """Check authentication and returns details
+
+ :param username:
+ :param password:
+ :param remoteip:
+ :return: dict with info, empty when login is incorrect
+ """
+ if self.core.config.get("remote", "nolocalauth") and remoteip == "127.0.0.1":
+ return "local"
+ else:
+ return self.core.db.checkAuth(username, password)
+
+
+ def isAuthorized(self, func, userdata):
+ """checks if the user is authorized for specific method
+
+ :param func: function name
+ :param userdata: dictionary of user data
+ :return: boolean
+ """
+ if userdata == "local" or userdata['role'] == ROLE.ADMIN:
+ return True
+ elif func in permMap and has_permission(userdata['permission'], permMap[func]):
+ return True
+ else:
+ return False
+
+
+ @permission(PERMS.ALL)
+ def getUserData(self, username, password):
+ """similar to `checkAuth` but returns UserData thrift type """
+ user = self.checkAuth(username, password)
+ if user:
+ return UserData(user['name'], user['email'], user['role'], user['permission'], user['template'])
+ else:
+ return UserData()
+
+
+ def getAllUserData(self):
+ """returns all known user and info"""
+ return dict((user, UserData(user, data['email'], data['role'], data['permission'], data['template'])) for user, data
+ in self.core.db.getAllUserData().iteritems())
+
+
+ @permission(PERMS.STATUS)
+ def getServices(self):
+ """ A dict of available services, these can be defined by addon plugins.
+
+ :return: dict with this style: {"plugin": {"method": "description"}}
+ """
+ return dict((plugin, funcs) for plugin, funcs in self.core.addonManager.methods.iteritems())
+
+
+ @permission(PERMS.STATUS)
+ def hasService(self, plugin, func):
+ """Checks wether a service is available.
+
+ :param plugin:
+ :param func:
+ :return: bool
+ """
+ cont = self.core.addonManager.methods
+ return plugin in cont and func in cont[plugin]
+
+
+ @permission(PERMS.STATUS)
+ def call(self, info):
+ """Calls a service (a method in addon plugin).
+
+ :param info: `ServiceCall`
+ :return: result
+ :raises: ServiceDoesNotExists, when its not available
+ :raises: ServiceException, when a exception was raised
+ """
+ plugin = info.plugin
+ func = info.func
+ args = info.arguments
+ parse = info.parseArguments
+ if not self.hasService(plugin, func):
+ raise ServiceDoesNotExists(plugin, func)
+ try:
+ ret = self.core.addonManager.callRPC(plugin, func, args, parse)
+ except Exception, e:
+ raise ServiceException(e.message)
+
+
+ @permission(PERMS.STATUS)
+ def getAllInfo(self):
+ """Returns all information stored by addon plugins. Values are always strings
+
+ :return: {"plugin": {"name": value}}
+ """
+ return self.core.addonManager.getAllInfo()
+
+
+ @permission(PERMS.STATUS)
+ def getInfoByPlugin(self, plugin):
+ """Returns information stored by a specific plugin.
+
+ :param plugin: pluginname
+ :return: dict of attr names mapped to value {"name": value}
+ """
+ return self.core.addonManager.getInfo(plugin)
+
+
+ def changePassword(self, user, oldpw, newpw):
+ """ changes password for specific user """
+ return self.core.db.changePassword(user, oldpw, newpw)
+
+
+ def setUserPermission(self, user, perm, role):
+ self.core.db.setPermission(user, perm)
+ self.core.db.setRole(user, role)
diff --git a/pyload/api/types.py b/pyload/api/types.py
new file mode 100644
index 000000000..9381df3c7
--- /dev/null
+++ b/pyload/api/types.py
@@ -0,0 +1,562 @@
+# -*- coding: utf-8 -*-
+# Autogenerated by pyload
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+
+class BaseObject(object):
+ __slots__ = []
+
+
+class Destination(object):
+ Collector = 0
+ Queue = 1
+
+
+class DownloadStatus(object):
+ Aborted = 9
+ Custom = 11
+ Decrypting = 10
+ Downloading = 12
+ Failed = 8
+ Finished = 0
+ Offline = 1
+ Online = 2
+ Processing = 13
+ Queued = 3
+ Skipped = 4
+ Starting = 7
+ TempOffline = 6
+ Unknown = 14
+ Waiting = 5
+
+
+class ElementType(object):
+ File = 1
+ Package = 0
+
+
+class Input(object):
+ BOOL = 4
+ CHOICE = 6
+ CLICK = 5
+ LIST = 8
+ MULTIPLE = 7
+ NONE = 0
+ PASSWORD = 3
+ TABLE = 9
+ TEXT = 1
+ TEXTBOX = 2
+
+
+class Output(object):
+ CAPTCHA = 1
+ NOTIFICATION = 4
+ QUESTION = 2
+
+
+class AccountInfo(BaseObject):
+ __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type']
+
+
+ def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None):
+ self.validuntil = validuntil
+ self.login = login
+ self.options = options
+ self.valid = valid
+ self.trafficleft = trafficleft
+ self.maxtraffic = maxtraffic
+ self.premium = premium
+ self.type = type
+
+
+class CaptchaTask(BaseObject):
+ __slots__ = ['tid', 'data', 'type', 'resultType']
+
+
+ def __init__(self, tid=None, data=None, type=None, resultType=None):
+ self.tid = tid
+ self.data = data
+ self.type = type
+ self.resultType = resultType
+
+
+class ConfigItem(BaseObject):
+ __slots__ = ['name', 'description', 'value', 'type']
+
+
+ def __init__(self, name=None, description=None, value=None, type=None):
+ self.name = name
+ self.description = description
+ self.value = value
+ self.type = type
+
+
+class ConfigSection(BaseObject):
+ __slots__ = ['name', 'description', 'items', 'outline']
+
+
+ def __init__(self, name=None, description=None, items=None, outline=None):
+ self.name = name
+ self.description = description
+ self.items = items
+ self.outline = outline
+
+
+class DownloadInfo(BaseObject):
+ __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
+
+
+ def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None):
+ self.fid = fid
+ self.name = name
+ self.speed = speed
+ self.eta = eta
+ self.format_eta = format_eta
+ self.bleft = bleft
+ self.size = size
+ self.format_size = format_size
+ self.percent = percent
+ self.status = status
+ self.statusmsg = statusmsg
+ self.format_wait = format_wait
+ self.wait_until = wait_until
+ self.packageID = packageID
+ self.packageName = packageName
+ self.plugin = plugin
+
+
+class EventInfo(BaseObject):
+ __slots__ = ['eventname', 'id', 'type', 'destination']
+
+
+ def __init__(self, eventname=None, id=None, type=None, destination=None):
+ self.eventname = eventname
+ self.id = id
+ self.type = type
+ self.destination = destination
+
+
+class FileData(BaseObject):
+ __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order']
+
+
+ def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None):
+ self.fid = fid
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.format_size = format_size
+ self.status = status
+ self.statusmsg = statusmsg
+ self.packageID = packageID
+ self.error = error
+ self.order = order
+
+
+class FileDoesNotExists(Exception):
+ __slots__ = ['fid']
+
+
+ def __init__(self, fid=None):
+ self.fid = fid
+
+
+class InteractionTask(BaseObject):
+ __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin']
+
+
+ def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None):
+ self.iid = iid
+ self.input = input
+ self.structure = structure
+ self.preset = preset
+ self.output = output
+ self.data = data
+ self.title = title
+ self.description = description
+ self.plugin = plugin
+
+
+class OnlineCheck(BaseObject):
+ __slots__ = ['rid', 'data']
+
+
+ def __init__(self, rid=None, data=None):
+ self.rid = rid
+ self.data = data
+
+
+class OnlineStatus(BaseObject):
+ __slots__ = ['name', 'plugin', 'packagename', 'status', 'size']
+
+
+ def __init__(self, name=None, plugin=(None, None), packagename=None, status=None, size=None):
+ self.name = name
+ self.plugin = plugin
+ self.packagename = packagename
+ self.status = status
+ self.size = size
+
+
+class PackageData(BaseObject):
+ __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids']
+
+
+ def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None):
+ self.pid = pid
+ self.name = name
+ self.folder = folder
+ self.site = site
+ self.password = password
+ self.dest = dest
+ self.order = order
+ self.linksdone = linksdone
+ self.sizedone = sizedone
+ self.sizetotal = sizetotal
+ self.linkstotal = linkstotal
+ self.links = links
+ self.fids = fids
+
+
+class PackageDoesNotExists(Exception):
+ __slots__ = ['pid']
+
+
+ def __init__(self, pid=None):
+ self.pid = pid
+
+
+class ServerStatus(BaseObject):
+ __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect']
+
+
+ def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None):
+ self.pause = pause
+ self.active = active
+ self.queue = queue
+ self.total = total
+ self.speed = speed
+ self.download = download
+ self.reconnect = reconnect
+
+
+class ServiceCall(BaseObject):
+ __slots__ = ['plugin', 'func', 'arguments', 'parseArguments']
+
+
+ def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None):
+ self.plugin = plugin
+ self.func = func
+ self.arguments = arguments
+ self.parseArguments = parseArguments
+
+
+class ServiceDoesNotExists(Exception):
+ __slots__ = ['plugin', 'func']
+
+
+ def __init__(self, plugin=None, func=None):
+ self.plugin = plugin
+ self.func = func
+
+
+class ServiceException(Exception):
+ __slots__ = ['msg']
+
+
+ def __init__(self, msg=None):
+ self.msg = msg
+
+
+class UserData(BaseObject):
+ __slots__ = ['name', 'email', 'role', 'permission', 'templateName']
+
+
+ def __init__(self, name=None, email=None, role=None, permission=None, templateName=None):
+ self.name = name
+ self.email = email
+ self.role = role
+ self.permission = permission
+ self.templateName = templateName
+
+
+class Iface(object):
+
+ def addFiles(self, pid, links):
+ pass
+
+
+ def addPackage(self, name, links, dest):
+ pass
+
+
+ def call(self, info):
+ pass
+
+
+ def checkOnlineStatus(self, urls):
+ pass
+
+
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ pass
+
+
+ def checkURLs(self, urls):
+ pass
+
+
+ def deleteFiles(self, fids):
+ pass
+
+
+ def deleteFinished(self):
+ pass
+
+
+ def deletePackages(self, pids):
+ pass
+
+
+ def freeSpace(self):
+ pass
+
+
+ def generateAndAddPackages(self, links, dest):
+ pass
+
+
+ def generatePackages(self, links):
+ pass
+
+
+ def getAccountTypes(self):
+ pass
+
+
+ def getAccounts(self, refresh):
+ pass
+
+
+ def getAllInfo(self):
+ pass
+
+
+ def getAllUserData(self):
+ pass
+
+
+ def getCaptchaTask(self, exclusive):
+ pass
+
+
+ def getCaptchaTaskStatus(self, tid):
+ pass
+
+
+ def getCollector(self):
+ pass
+
+
+ def getCollectorData(self):
+ pass
+
+
+ def getConfig(self):
+ pass
+
+
+ def getConfigValue(self, category, option, section):
+ pass
+
+
+ def getEvents(self, uuid):
+ pass
+
+
+ def getFileData(self, fid):
+ pass
+
+
+ def getFileOrder(self, pid):
+ pass
+
+
+ def getInfoByPlugin(self, plugin):
+ pass
+
+
+ def getLog(self, offset):
+ pass
+
+
+ def getPackageData(self, pid):
+ pass
+
+
+ def getPackageInfo(self, pid):
+ pass
+
+
+ def getPackageOrder(self, destination):
+ pass
+
+
+ def getPluginConfig(self):
+ pass
+
+
+ def getQueue(self):
+ pass
+
+
+ def getQueueData(self):
+ pass
+
+
+ def getServerVersion(self):
+ pass
+
+
+ def getServices(self):
+ pass
+
+
+ def getUserData(self, username, password):
+ pass
+
+
+ def hasService(self, plugin, func):
+ pass
+
+
+ def isCaptchaWaiting(self):
+ pass
+
+
+ def isTimeDownload(self):
+ pass
+
+
+ def isTimeReconnect(self):
+ pass
+
+
+ def kill(self):
+ pass
+
+
+ def login(self, username, password):
+ pass
+
+
+ def moveFiles(self, fids, pid):
+ pass
+
+
+ def movePackage(self, destination, pid):
+ pass
+
+
+ def orderFile(self, fid, position):
+ pass
+
+
+ def orderPackage(self, pid, position):
+ pass
+
+
+ def parseURLs(self, html, url):
+ pass
+
+
+ def pauseServer(self):
+ pass
+
+
+ def pollResults(self, rid):
+ pass
+
+
+ def pullFromQueue(self, pid):
+ pass
+
+
+ def pushToQueue(self, pid):
+ pass
+
+
+ def recheckPackage(self, pid):
+ pass
+
+
+ def removeAccount(self, plugin, account):
+ pass
+
+
+ def restart(self):
+ pass
+
+
+ def restartFailed(self):
+ pass
+
+
+ def restartFile(self, fid):
+ pass
+
+
+ def restartPackage(self, pid):
+ pass
+
+
+ def setCaptchaResult(self, tid, result):
+ pass
+
+
+ def setConfigValue(self, category, option, value, section):
+ pass
+
+
+ def setPackageData(self, pid, data):
+ pass
+
+
+ def setPackageName(self, pid, name):
+ pass
+
+
+ def statusDownloads(self):
+ pass
+
+
+ def statusServer(self):
+ pass
+
+
+ def stopAllDownloads(self):
+ pass
+
+
+ def stopDownloads(self, fids):
+ pass
+
+
+ def togglePause(self):
+ pass
+
+
+ def toggleReconnect(self):
+ pass
+
+
+ def unpauseServer(self):
+ pass
+
+
+ def updateAccount(self, plugin, account, password, options):
+ pass
+
+
+ def uploadContainer(self, filename, data):
+ pass
diff --git a/pyload/cli/AddPackage.py b/pyload/cli/AddPackage.py
new file mode 100644
index 000000000..e750274ca
--- /dev/null
+++ b/pyload/cli/AddPackage.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from pyload.cli.Handler import Handler
+from pyload.utils.printer import *
+
+
+class AddPackage(Handler):
+ """ let the user add packages """
+
+ def init(self):
+ self.name = ""
+ self.urls = []
+
+
+ def onEnter(self, inp):
+ if inp == "0":
+ self.cli.reset()
+
+ if not self.name:
+ self.name = inp
+ self.setInput()
+ elif inp == "END":
+ # add package
+ self.client.addPackage(self.name, self.urls, 1)
+ self.cli.reset()
+ else:
+ if inp.strip():
+ self.urls.append(inp)
+ self.setInput()
+
+
+ def renderBody(self, line):
+ println(line, white(_("Add Package:")))
+ println(line + 1, "")
+ line += 2
+
+ if not self.name:
+ println(line, _("Enter a name for the new package"))
+ println(line + 1, "")
+ line += 2
+ else:
+ println(line, _("Package: %s") % self.name)
+ println(line + 1, _("Parse the links you want to add."))
+ println(line + 2, _("Type %s when done.") % mag("END"))
+ println(line + 3, _("Links added: ") + mag(len(self.urls)))
+ line += 4
+
+ println(line, "")
+ println(line + 1, mag("0.") + _(" back to main menu"))
+
+ return line + 2
diff --git a/pyload/cli/Cli.py b/pyload/cli/Cli.py
new file mode 100644
index 000000000..fc5236ff9
--- /dev/null
+++ b/pyload/cli/Cli.py
@@ -0,0 +1,588 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+from getopt import GetoptError, getopt
+
+import pyload.utils.pylgettext as gettext
+import os
+from os import _exit
+from os.path import join, exists, basename
+import sys
+from sys import exit
+from threading import Thread, Lock
+from time import sleep
+from traceback import print_exc
+
+from pyload.config.Parser import ConfigParser
+
+from codecs import getwriter
+
+import module.common.pylgettext as gettext
+
+if os.name == "nt":
+ enc = "cp850"
+else:
+ enc = "utf8"
+
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+from pyload.cli.printer import *
+from pyload.cli import AddPackage, ManageFiles
+
+from pyload.api import Destination
+from pyload.utils import formatSize, decode
+from pyload.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed
+from Getch import Getch
+from rename_process import renameProcess
+
+
+class Cli(object):
+
+ def __init__(self, client, command):
+ self.client = client
+ self.command = command
+
+ if not self.command:
+ renameProcess('pyload-cli')
+ self.getch = Getch()
+ self.input = ""
+ self.inputline = 0
+ self.lastLowestLine = 0
+ self.menuline = 0
+
+ self.lock = Lock()
+
+ # processor funcions, these will be changed dynamically depending on control flow
+ self.headerHandler = self #: the download status
+ self.bodyHandler = self #: the menu section
+ self.inputHandler = self
+
+ os.system("clear")
+ println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ println(2, "")
+
+ self.thread = RefreshThread(self)
+ self.thread.start()
+
+ self.start()
+ else:
+ self.processCommand()
+
+
+ def reset(self):
+ """ reset to initial main menu """
+ self.input = ""
+ self.headerHandler = self.bodyHandler = self.inputHandler = self
+
+
+ def start(self):
+ """ main loop. handle input """
+ while True:
+ # inp = raw_input()
+ inp = self.getch.impl()
+ if ord(inp) == 3:
+ os.system("clear")
+ sys.exit() #: ctrl + c
+ elif ord(inp) == 13: #: enter
+ try:
+ self.lock.acquire()
+ self.inputHandler.onEnter(self.input)
+
+ except Exception, e:
+ println(2, red(e))
+ finally:
+ self.lock.release()
+
+ elif ord(inp) == 127:
+ self.input = self.input[:-1] #: backspace
+ try:
+ self.lock.acquire()
+ self.inputHandler.onBackSpace()
+ finally:
+ self.lock.release()
+
+ elif ord(inp) == 27: #: ugly symbol
+ pass
+ else:
+ self.input += inp
+ try:
+ self.lock.acquire()
+ self.inputHandler.onChar(inp)
+ finally:
+ self.lock.release()
+
+ self.inputline = self.bodyHandler.renderBody(self.menuline)
+ self.renderFooter(self.inputline)
+
+
+ def refresh(self):
+ """refresh screen"""
+
+ println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ println(2, "")
+
+ self.lock.acquire()
+
+ self.menuline = self.headerHandler.renderHeader(3) + 1
+ println(self.menuline - 1, "")
+ self.inputline = self.bodyHandler.renderBody(self.menuline)
+ self.renderFooter(self.inputline)
+
+ self.lock.release()
+
+
+ def setInput(self, string=""):
+ self.input = string
+
+
+ def setHandler(self, klass):
+ # create new handler with reference to cli
+ self.bodyHandler = self.inputHandler = klass(self)
+ self.input = ""
+
+
+ def renderHeader(self, line):
+ """ prints download status """
+ # print updated information
+ # print "\033[J" #: clear screen
+ # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ # self.println(2, "")
+ # self.println(3, white(_("%s Downloads:") % (len(data))))
+
+ data = self.client.statusDownloads()
+ speed = 0
+
+ println(line, white(_("%s Downloads:") % (len(data))))
+ line += 1
+
+ for download in data:
+ if download.status == 12: #: downloading
+ percent = download.percent
+ z = percent / 4
+ speed += download.speed
+ println(line, cyan(download.name))
+ line += 1
+ println(line,
+ blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _(
+ " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green(
+ download.format_size) + _(" Finished in: ") + green(download.format_eta) + _(
+ " ID: ") + green(download.fid))
+ line += 1
+ if download.status == 5:
+ println(line, cyan(download.name))
+ line += 1
+ println(line, _("waiting: ") + green(download.format_wait))
+ line += 1
+
+ println(line, "")
+ line += 1
+ status = self.client.statusServer()
+ if status.pause:
+ paused = _("Status:") + " " + red(_("paused"))
+ else:
+ paused = _("Status:") + " " + red(_("running"))
+
+ println(line, "%s %s: %s %s: %s %s: %s" % (
+ paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red(
+ status.queue), _("Total"), red(status.total)))
+
+ return line + 1
+
+
+ def renderBody(self, line):
+ """ prints initial menu """
+ println(line, white(_("Menu:")))
+ println(line + 1, "")
+ println(line + 2, mag("1.") + _(" Add Links"))
+ println(line + 3, mag("2.") + _(" Manage Queue"))
+ println(line + 4, mag("3.") + _(" Manage Collector"))
+ println(line + 5, mag("4.") + _(" (Un)Pause Server"))
+ println(line + 6, mag("5.") + _(" Kill Server"))
+ println(line + 7, mag("6.") + _(" Quit"))
+
+ return line + 8
+
+
+ def renderFooter(self, line):
+ """ prints out the input line with input """
+ println(line, "")
+ line += 1
+
+ println(line, white(" Input: ") + decode(self.input))
+
+ # clear old output
+ if line < self.lastLowestLine:
+ for i in xrange(line + 1, self.lastLowestLine + 1):
+ println(i, "")
+
+ self.lastLowestLine = line
+
+ # set cursor to position
+ print "\033[" + str(self.inputline) + ";0H"
+
+
+ def onChar(self, char):
+ """ default no special handling for single chars """
+ if char == "1":
+ self.setHandler(AddPackage)
+ elif char == "2":
+ self.setHandler(ManageFiles)
+ elif char == "3":
+ self.setHandler(ManageFiles)
+ self.bodyHandler.target = Destination.Collector
+ elif char == "4":
+ self.client.togglePause()
+ self.setInput()
+ elif char == "5":
+ self.client.kill()
+ self.client.close()
+ sys.exit()
+ elif char == "6":
+ os.system('clear')
+ sys.exit()
+
+
+ def onEnter(self, inp):
+ pass
+
+
+ def onBackSpace(self):
+ pass
+
+
+ def processCommand(self):
+ command = self.command[0]
+ args = []
+ if len(self.command) > 1:
+ args = self.command[1:]
+
+ if command == "status":
+ files = self.client.statusDownloads()
+
+ if not files:
+ print "No downloads running."
+
+ for download in files:
+ if download.status == 12: #: downloading
+ print print_status(download)
+ print "\tDownloading: %s @ %s/s\t %s (%s%%)" % (
+ download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft),
+ download.percent)
+ elif download.status == 5:
+ print print_status(download)
+ print "\tWaiting: %s" % download.format_wait
+ else:
+ print print_status(download)
+
+ elif command == "queue":
+ print_packages(self.client.getQueueData())
+
+ elif command == "collector":
+ print_packages(self.client.getCollectorData())
+
+ elif command == "add":
+ if len(args) < 2:
+ print _("Please use this syntax: add <Package name> <link> <link2> ...")
+ return
+
+ self.client.addPackage(args[0], args[1:], Destination.Queue)
+
+ elif command == "add_coll":
+ if len(args) < 2:
+ print _("Please use this syntax: add <Package name> <link> <link2> ...")
+ return
+
+ self.client.addPackage(args[0], args[1:], Destination.Collector)
+
+ elif command == "del_file":
+ self.client.deleteFiles([int(x) for x in args])
+ print "Files deleted."
+
+ elif command == "del_package":
+ self.client.deletePackages([int(x) for x in args])
+ print "Packages deleted."
+
+ elif command == "move":
+ for pid in args:
+ pack = self.client.getPackageInfo(int(pid))
+ self.client.movePackage((pack.dest + 1) % 2, pack.pid)
+
+ elif command == "check":
+ print _("Checking %d links:") % len(args)
+ print
+ rid = self.client.checkOnlineStatus(args).rid
+ self.printOnlineCheck(self.client, rid)
+
+ elif command == "check_container":
+ path = args[0]
+ if not exists(join(owd, path)):
+ print _("File does not exists.")
+ return
+
+ with open(join(owd, path), "rb") as f:
+ content = f.read()
+
+ rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid
+ self.printOnlineCheck(self.client, rid)
+
+ elif command == "pause":
+ self.client.pause()
+
+ elif command == "unpause":
+ self.client.unpause()
+
+ elif command == "toggle":
+ self.client.togglePause()
+
+ elif command == "kill":
+ self.client.kill()
+ elif command == "restart_file":
+ for x in args:
+ self.client.restartFile(int(x))
+ print "Files restarted."
+ elif command == "restart_package":
+ for pid in args:
+ self.client.restartPackage(int(pid))
+ print "Packages restarted."
+
+ else:
+ print_commands()
+
+
+ def printOnlineCheck(self, client, rid):
+ while True:
+ sleep(1)
+ result = client.pollResults(rid)
+ for url, status in result.data.iteritems():
+ if status.status == 2:
+ check = "Online"
+ elif status.status == 1:
+ check = "Offline"
+ else:
+ check = "Unknown"
+
+ print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check)
+
+ if result.rid == -1:
+ break
+
+
+class RefreshThread(Thread):
+
+ def __init__(self, cli):
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.cli = cli
+
+
+ def run(self):
+ while True:
+ sleep(1)
+ try:
+ self.cli.refresh()
+ except ConnectionClosed:
+ os.system("clear")
+ print _("pyLoad was terminated")
+ _exit(0)
+ except Exception, e:
+ println(2, red(str(e)))
+ self.cli.reset()
+ print_exc()
+
+
+def print_help(config):
+ print
+ print "pyLoad CLI Copyright (c) 2008-2015 the pyLoad Team"
+ print
+ print "Usage: [python] pyload-cli.py [options] [command]"
+ print
+ print "<Commands>"
+ print "See pyload-cli.py -c for a complete listing."
+ print
+ print "<Options>"
+ print " -i, --interactive", " Start in interactive mode"
+ print
+ print " -u, --username=", " " * 2, "Specify Username"
+ print " --pw=<password>", " " * 2, "Password"
+ print " -a, --address=", " " * 3, "Specify address (current=%s)" % config['addr']
+ print " -p, --port", " " * 7, "Specify port (current=%s)" % config['port']
+ print
+ print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config['language']
+ print " -h, --help", " " * 7, "Display this help screen"
+ print " -c, --commands", " " * 3, "List all available commands"
+ print
+
+
+def print_packages(data):
+ for pack in data:
+ print "Package %s (#%s):" % (pack.name, pack.pid)
+ for download in pack.links:
+ print "\t" + print_file(download)
+ print
+
+
+def print_file(download):
+ return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % {
+ "id": download.fid,
+ "name": download.name,
+ "statusmsg": download.statusmsg,
+ "plugin": download.plugin
+ }
+
+
+def print_status(download):
+ return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % {
+ "id": download.fid,
+ "name": download.name,
+ "statusmsg": download.statusmsg,
+ "size": download.format_size
+ }
+
+
+def print_commands():
+ commands = [("status", _("Prints server status")),
+ ("queue", _("Prints downloads in queue")),
+ ("collector", _("Prints downloads in collector")),
+ ("add <name> <link1> <link2>...", _("Adds package to queue")),
+ ("add_coll <name> <link1> <link2>...", _("Adds package to collector")),
+ ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")),
+ ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")),
+ ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")),
+ ("restart_file <fid> <fid2>...", _("Restart files")),
+ ("restart_package <pid> <pid2>...", _("Restart packages")),
+ ("check <container|url> ...", _("Check online status, works with local container")),
+ ("check_container path", _("Checks online status of a container file")),
+ ("pause", _("Pause the server")),
+ ("unpause", _("continue downloads")),
+ ("toggle", _("Toggle pause/unpause")),
+ ("kill", _("kill server")), ]
+
+ print _("List of commands:")
+ print
+ for c in commands:
+ print "%-35s %s" % c
+
+
+def writeConfig(opts):
+ try:
+ with open(join(homedir, ".pyload-cli"), "w") as cfgfile:
+ cfgfile.write("[cli]")
+ for opt in opts:
+ cfgfile.write("%s=%s\n" % (opt, opts[opt]))
+ except Exception:
+ print _("Couldn't write user config file")
+
+
+def main():
+ config = {"addr": "127.0.0.1", "port": "7227", "language": "en"}
+ try:
+ config['language'] = os.environ['LANG'][0:2]
+ except Exception:
+ pass
+
+ if (not exists(join(pypath, "locale", config['language']))) or config['language'] == "":
+ config['language'] = "en"
+
+ configFile = ConfigParser.ConfigParser()
+ configFile.read(join(homedir, ".pyload-cli"))
+
+ if configFile.has_section("cli"):
+ for opt in configFile.items("cli"):
+ config[opt[0]] = opt[1]
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("Cli", join(pypath, "locale"),
+ languages=[config['language'], "en"], fallback=True)
+ translation.install(unicode=True)
+
+ interactive = False
+ command = None
+ username = ""
+ password = ""
+
+ shortOptions = 'iu:p:a:hcl:'
+ longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="]
+
+ try:
+ opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions)
+ for option, params in opts:
+ if option in ("-i", "--interactive"):
+ interactive = True
+ elif option in ("-u", "--username"):
+ username = params
+ elif option in ("-a", "--address"):
+ config['addr'] = params
+ elif option in ("-p", "--port"):
+ config['port'] = params
+ elif option in ("-l", "--language"):
+ config['language'] = params
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("Cli", join(pypath, "locale"),
+ languages=[config['language'], "en"], fallback=True)
+ translation.install(unicode=True)
+ elif option in ("-h", "--help"):
+ print_help(config)
+ exit()
+ elif option in ("--pw"):
+ password = params
+ elif option in ("-c", "--comands"):
+ print_commands()
+ exit()
+
+ except GetoptError:
+ print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:])
+ print_help(config)
+ exit()
+
+ if len(extraparams) >= 1:
+ command = extraparams
+
+ client = False
+
+ if interactive:
+ try:
+ client = ThriftClient(config['addr'], int(config['port']), username, password)
+ except WrongLogin:
+ pass
+ except NoSSL:
+ print _("You need py-openssl to connect to this pyLoad Core.")
+ exit()
+ except NoConnection:
+ config['addr'] = False
+ config['port'] = False
+
+ if not client:
+ if not config['addr']: config['addr'] = raw_input(_("Address: "))
+ if not config['port']: config['port'] = raw_input(_("Port: "))
+ if not username: username = raw_input(_("Username: "))
+ if not password:
+ from getpass import getpass
+
+ password = getpass(_("Password: "))
+
+ try:
+ client = ThriftClient(config['addr'], int(config['port']), username, password)
+ except WrongLogin:
+ print _("Login data is wrong.")
+ except NoConnection:
+ print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config['addr'],
+ "port": config['port']})
+
+ else:
+ try:
+ client = ThriftClient(config['addr'], int(config['port']), username, password)
+ except WrongLogin:
+ print _("Login data is wrong.")
+ except NoConnection:
+ print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config['addr'],
+ "port": config['port']})
+ except NoSSL:
+ print _("You need py-openssl to connect to this pyLoad core.")
+
+ if interactive and command: print _("Interactive mode ignored since you passed some commands.")
+
+ if client:
+ writeConfig(config)
+ cli = Cli(client, command)
diff --git a/pyload/cli/Handler.py b/pyload/cli/Handler.py
new file mode 100644
index 000000000..33e5dd8e6
--- /dev/null
+++ b/pyload/cli/Handler.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+class Handler(object):
+
+ def __init__(self, cli):
+ self.cli = cli
+ self.init()
+
+ client = property(lambda self: self.cli.client)
+ input = property(lambda self: self.cli.input)
+
+
+ def init(self):
+ pass
+
+
+ def onChar(self, char):
+ pass
+
+
+ def onBackSpace(self):
+ pass
+
+
+ def onEnter(self, inp):
+ pass
+
+
+ def setInput(self, inp=""):
+ self.cli.setInput(inp)
+
+
+ def backspace(self):
+ self.cli.setInput(self.input[:-1])
+
+
+ def renderBody(self, line):
+ """ gets the line where to render output and should return the line number below its content """
+ return line + 1
diff --git a/pyload/cli/ManageFiles.py b/pyload/cli/ManageFiles.py
new file mode 100644
index 000000000..3833b2c48
--- /dev/null
+++ b/pyload/cli/ManageFiles.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from itertools import islice
+from time import time
+
+from pyload.cli.Handler import Handler
+from pyload.utils.printer import *
+
+from pyload.api import Destination, PackageData
+
+
+class ManageFiles(Handler):
+ """ possibility to manage queue/collector """
+
+ def init(self):
+ self.target = Destination.Queue
+ self.pos = 0 #: position in queue
+ self.package = -1 #: choosen package
+ self.mode = "" #: move/delete/restart
+
+ self.cache = None
+ self.links = None
+ self.time = 0
+
+
+ def onChar(self, char):
+ if char in ("m", "d", "r"):
+ self.mode = char
+ self.setInput()
+ elif char == "p":
+ self.pos = max(0, self.pos - 5)
+ self.backspace()
+ elif char == "n":
+ self.pos += 5
+ self.backspace()
+
+
+ def onBackSpace(self):
+ if not self.input and self.mode:
+ self.mode = ""
+ if not self.input and self.package > -1:
+ self.package = -1
+
+
+ def onEnter(self, input):
+ if input == "0":
+ self.cli.reset()
+ elif self.package < 0 and self.mode:
+ # mode select
+ packs = self.parseInput(input)
+ if self.mode == "m":
+ [self.client.movePackage((self.target + 1) % 2, x) for x in packs]
+ elif self.mode == "d":
+ self.client.deletePackages(packs)
+ elif self.mode == "r":
+ [self.client.restartPackage(x) for x in packs]
+
+ elif self.mode:
+ # edit links
+ links = self.parseInput(input, False)
+
+ if self.mode == "d":
+ self.client.deleteFiles(links)
+ elif self.mode == "r":
+ map(self.client.restartFile, links)
+
+ else:
+ # look into package
+ try:
+ self.package = int(input)
+ except Exception:
+ pass
+
+ self.cache = None
+ self.links = None
+ self.pos = 0
+ self.mode = ""
+ self.setInput()
+
+
+ def renderBody(self, line):
+ if self.package < 0:
+ println(line, white(_("Manage Packages:")))
+ else:
+ println(line, white((_("Manage Links:"))))
+ line += 1
+
+ if self.mode:
+ if self.mode == "m":
+ println(line, _("What do you want to move?"))
+ elif self.mode == "d":
+ println(line, _("What do you want to delete?"))
+ elif self.mode == "r":
+ println(line, _("What do you want to restart?"))
+
+ println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1, 2, 3 or 1-3.")
+ line += 2
+ else:
+ println(line, _("Choose what yout want to do or enter package number."))
+ println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % (
+ _("delete"), _("move"), _("restart")))
+ line += 2
+
+ if self.package < 0:
+ # print package info
+ pack = self.getPackages()
+ i = 0
+ for value in islice(pack, self.pos, self.pos + 5):
+ try:
+ println(line, mag(str(value.pid)) + ": " + value.name)
+ line += 1
+ i += 1
+ except Exception:
+ pass
+ for _i in xrange(5 - i):
+ println(line, "")
+ line += 1
+ else:
+ # print links info
+ pack = self.getLinks()
+ i = 0
+ for value in islice(pack.links, self.pos, self.pos + 5):
+ try:
+ println(line, mag(value.fid) + ": %s | %s | %s" % (
+ value.name, value.statusmsg, value.plugin))
+ line += 1
+ i += 1
+ except Exception, e:
+ pass
+ for _i in xrange(5 - i):
+ println(line, "")
+ line += 1
+
+ println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next"))
+ println(line + 1, mag("0.") + _(" back to main menu"))
+ return line + 2
+
+
+ def getPackages(self):
+ if self.cache and self.time + 2 < time():
+ return self.cache
+
+ if self.target == Destination.Queue:
+ data = self.client.getQueue()
+ else:
+ data = self.client.getCollector()
+
+ self.cache = data
+ self.time = time()
+
+ return data
+
+
+ def getLinks(self):
+ if self.links and self.time + 1 < time():
+ return self.links
+ try:
+ data = self.client.getPackageData(self.package)
+ except Exception:
+ data = PackageData(links=[])
+ self.links = data
+ self.time = time()
+ return data
+
+
+ def parseInput(self, inp, package=True):
+ inp = inp.strip()
+ if "-" in inp:
+ l, _, h = inp.partition("-")
+ r = xrange(int(l), int(h) + 1)
+
+ if package:
+ return [p.pid for p in self.cache if p.pid in r]
+ return [l.lid for l in self.links.links if l.lid in r]
+
+ else:
+ return [int(x) for x in inp.split(",")]
diff --git a/pyload/cli/__init__.py b/pyload/cli/__init__.py
new file mode 100644
index 000000000..a64fc0c0c
--- /dev/null
+++ b/pyload/cli/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from pyload.cli.AddPackage import AddPackage
+from pyload.cli.ManageFiles import ManageFiles
diff --git a/pyload/config/Parser.py b/pyload/config/Parser.py
new file mode 100644
index 000000000..45fb1c8d0
--- /dev/null
+++ b/pyload/config/Parser.py
@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from time import sleep
+from os.path import exists, join
+from shutil import copy
+
+from traceback import print_exc
+from pyload.utils import chmod, encode, decode
+
+
+CONF_VERSION = 1
+
+
+class ConfigParser(object):
+ """
+ holds and manage the configuration
+
+ current dict layout:
+
+ {
+
+ section: {
+ option: {
+ value:
+ type:
+ desc:
+ }
+ desc:
+
+ }
+ """
+
+ def __init__(self):
+ """Constructor"""
+ self.config = {} #: the config values
+ self.plugin = {} #: the config for plugins
+ self.oldRemoteData = {}
+
+ self.pluginCB = None #: callback when plugin config value is changed
+
+ self.checkVersion()
+
+ self.readConfig()
+
+
+ def checkVersion(self, n=0):
+ """determines if config need to be copied"""
+ try:
+ if not exists("pyload.conf"):
+ copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+
+ if not exists("plugin.conf"):
+ with open("plugin.conf", "wb") as f:
+ f.write("version: " + str(CONF_VERSION))
+
+ with open("pyload.conf", "rb") as f:
+ v = f.readline()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+ print "Old version of config was replaced"
+
+ with open("plugin.conf", "rb") as f:
+ v = f.readline()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ with open("plugin.conf", "wb") as f:
+ f.write("version: " + str(CONF_VERSION))
+ print "Old version of plugin-config replaced"
+
+ except Exception:
+ if n >= 3:
+ raise
+ sleep(0.3)
+ self.checkVersion(n + 1)
+
+
+ def readConfig(self):
+ """reads the config file"""
+ self.config = self.parseConfig(join(pypath, "pyload", "config", "default.conf"))
+ self.plugin = self.parseConfig("plugin.conf")
+
+ try:
+ homeconf = self.parseConfig("pyload.conf")
+ if "username" in homeconf['remote']:
+ if "password" in homeconf['remote']:
+ self.oldRemoteData = {"username": homeconf['remote']['username']['value'],
+ "password": homeconf['remote']['username']['value']}
+ del homeconf['remote']['password']
+ del homeconf['remote']['username']
+ self.updateValues(homeconf, self.config)
+ except Exception:
+ print "Config Warning"
+ print_exc()
+
+
+ def parseConfig(self, config):
+ """parses a given configfile"""
+
+ with open(config) as f:
+ config = f.read()
+
+ config = config.splitlines()[1:]
+
+ conf = {}
+
+ section, option, value, typ, desc = "", "", "", "", ""
+
+ listmode = False
+
+ for line in config:
+ comment = line.rfind("#")
+ if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace():
+ line = line.rpartition("#") #: removes comments
+ if line[1]:
+ line = line[0]
+ else:
+ line = line[2]
+
+ line = line.strip()
+
+ try:
+ if line == "":
+ continue
+ elif line.endswith(":"):
+ section, none, desc = line[:-1].partition('-')
+ section = section.strip()
+ desc = desc.replace('"', "").strip()
+ conf[section] = {"desc": desc}
+ else:
+ if listmode:
+ if line.endswith("]"):
+ listmode = False
+ line = line.replace("]", "")
+
+ value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
+
+ if not listmode:
+ conf[section][option] = {"desc": desc,
+ "type": typ,
+ "value": value,
+ "idx": len(conf[section])}
+
+
+ else:
+ content, none, value = line.partition("=")
+
+ content, none, desc = content.partition(":")
+
+ desc = desc.replace('"', "").strip()
+
+ typ, none, option = content.strip().rpartition(" ")
+
+ value = value.strip()
+ typ = typ.strip()
+
+ if value.startswith("["):
+ if value.endswith("]"):
+ listmode = False
+ value = value[:-1]
+ else:
+ listmode = True
+
+ value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
+ else:
+ value = self.cast(typ, value)
+
+ if not listmode:
+ conf[section][option] = {"desc": desc,
+ "type": typ,
+ "value": value,
+ "idx": len(conf[section])}
+
+ except Exception, e:
+ print "Config Warning"
+ print_exc()
+
+ return conf
+
+
+ def updateValues(self, config, dest):
+ """sets the config values from a parsed config file to values in destination"""
+
+ for section in config.iterkeys():
+ if section in dest:
+ for option in config[section].iterkeys():
+ if option in ("desc", "outline"):
+ continue
+
+ if option in dest[section]:
+ dest[section][option]['value'] = config[section][option]['value']
+
+ # else:
+ # dest[section][option] = config[section][option]
+
+
+ # else:
+ # dest[section] = config[section]
+
+
+ def saveConfig(self, config, filename):
+ """saves config to filename"""
+ with open(filename, "wb") as f:
+ chmod(filename, 0600)
+ f.write("version: %i \n" % CONF_VERSION)
+ for section in config.iterkeys():
+ f.write('\n%s - "%s":\n' % (section, config[section]['desc']))
+
+ for option, data in sorted(config[section].items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0):
+ if option in ("desc", "outline"):
+ continue
+
+ if isinstance(data['value'], list):
+ value = "[ \n"
+ for x in data['value']:
+ value += "\t\t" + str(x) + ",\n"
+ value += "\t\t]\n"
+ else:
+ if isinstance(data['value'], basestring):
+ value = data['value'] + "\n"
+ else:
+ value = str(data['value']) + "\n"
+ try:
+ f.write('\t%s %s : "%s" = %s' % (data['type'], option, data['desc'], value))
+ except UnicodeEncodeError:
+ f.write('\t%s %s : "%s" = %s' % (data['type'], option, data['desc'], encode(value)))
+
+
+ def cast(self, typ, value):
+ """cast value to given format"""
+ if not isinstance(value, basestring):
+ return value
+
+ elif typ == "int":
+ return int(value)
+ elif typ == "bool":
+ return value.lower() in ("1", "true", "on", "an", "yes")
+ elif typ == "time":
+ if not value:
+ value = "0:00"
+ if not ":" in value:
+ value += ":00"
+ return value
+ elif typ in ("str", "file", "folder"):
+ return encode(value)
+ else:
+ return value
+
+
+ def save(self):
+ """saves the configs to disk"""
+ self.saveConfig(self.config, "pyload.conf")
+ self.saveConfig(self.plugin, "plugin.conf")
+
+
+ def __getitem__(self, section):
+ """provides dictonary like access: c['section']['option']"""
+ return Section(self, section)
+
+
+ def get(self, section, option):
+ """get value"""
+ value = self.config[section][option]['value']
+ return decode(value)
+
+
+ def set(self, section, option, value):
+ """set value"""
+ value = self.cast(self.config[section][option]['type'], value)
+ self.config[section][option]['value'] = value
+ self.save()
+
+
+ def getPlugin(self, plugin, option):
+ """gets a value for a plugin"""
+ value = self.plugin[plugin][option]['value']
+ return encode(value)
+
+
+ def setPlugin(self, plugin, option, value):
+ """sets a value for a plugin"""
+
+ value = self.cast(self.plugin[plugin][option]['type'], value)
+
+ if self.pluginCB: self.pluginCB(plugin, option, value)
+
+ self.plugin[plugin][option]['value'] = value
+ self.save()
+
+
+ def removeDeletedPlugins(self, plugins):
+ for name in self.plugin.keys():
+ if not name in plugins:
+ del self.plugin[name]
+
+
+ def getMetaData(self, section, option):
+ """ get all config data for an option """
+ return self.config[section][option]
+
+
+ def addPluginConfig(self, name, config, outline=""):
+ """adds config options with tuples (name, type, desc, default)"""
+ if name not in self.plugin:
+ conf = {"desc": name,
+ "outline": outline}
+ self.plugin[name] = conf
+ else:
+ conf = self.plugin[name]
+ conf['outline'] = outline
+
+ for item in config:
+ if item[0] in conf:
+ conf[item[0]]['type'] = item[1]
+ conf[item[0]]['desc'] = item[2]
+ else:
+ conf[item[0]] = {
+ "desc": item[2],
+ "type": item[1],
+ "value": self.cast(item[1], item[3]),
+ "idx": len(conf)
+ }
+
+ values = [x[0] for x in config] + ["desc", "outline"]
+ # delete old values
+ for item in conf.keys():
+ if item not in values:
+ del conf[item]
+
+
+ def deleteConfig(self, name):
+ """Removes a plugin config"""
+ if name in self.plugin:
+ del self.plugin[name]
+
+
+class Section(object):
+ """provides dictionary like access for configparser"""
+
+ def __init__(self, parser, section):
+ """Constructor"""
+ self.parser = parser
+ self.section = section
+
+
+ def __getitem__(self, item):
+ """getitem"""
+ return self.parser.get(self.section, item)
+
+
+ def __setitem__(self, item, value):
+ """setitem"""
+ self.parser.set(self.section, item, value)
diff --git a/pyload/config/Setup.py b/pyload/config/Setup.py
new file mode 100644
index 000000000..e2f96c802
--- /dev/null
+++ b/pyload/config/Setup.py
@@ -0,0 +1,568 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+from __future__ import with_statement
+
+import __builtin__
+
+import os
+import sys
+
+from getpass import getpass
+from os import chdir, makedirs, path
+from subprocess import PIPE, call
+
+from pyload.network.JsEngine import JsEngine
+from pyload.utils import get_console_encoding, load_translation, fs_join, versiontuple
+
+
+class SetupAssistant(object):
+ """ pyLoads initial setup configuration assistant """
+
+ def __init__(self, config):
+ self.config = config
+ self.lang = "en"
+ self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
+
+ # Input shorthand for yes
+ self.yes = "y"
+ # Input shorthand for no
+ self.no = "n"
+
+
+ def start(self):
+ print
+ langs = sorted(self.config.getMetaData("general", "language")['type'].split(";"))
+ self.lang = self.ask(u"Choose setup language", "en", langs)
+
+ load_translation("setup", self.lang)
+
+ # Input shorthand for yes
+ self.yes = _("y")
+ # Input shorthand for no
+ self.no = _("n")
+
+ # print
+ # print _("Would you like to configure pyLoad via Webinterface?")
+ # print _("You need a Browser and a connection to this PC for it.")
+ # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True)
+ # ...
+
+ print
+ print
+ print _("## Welcome to the pyLoad Configuration Assistant ##")
+ print
+ print _("It will check your system and make a basic setup in order to run pyLoad.")
+ print
+ print _("The value in brackets [] always is the default value,")
+ print _("in case you don't want to change it or you are unsure what to choose, just hit enter.")
+ print _(
+ "Don't forget: You can always rerun this assistant with --setup or -s parameter, when you start pyload.py .")
+ print _("If you have any problems with this assistant hit STRG-C,")
+ print _("to abort and don't let him start with pyload.py automatically anymore.")
+ print
+ print
+ raw_input(_("When you are ready for system check, hit enter."))
+ print
+ print
+
+ basic, ssl, captcha, web, js = self.system_check()
+ print
+ print
+
+ if not basic:
+ print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.")
+ print _("Please correct this and re-run pyLoad.")
+ print
+ print _("Setup will now close.")
+ print
+ print
+ raw_input(_("Press Enter to exit."))
+ return False
+
+ raw_input(_("System check finished, hit enter to see your status report."))
+ print
+ print
+ print _("## Status ##")
+ print
+
+ avail = []
+ if self.check_module("Crypto"):
+ avail.append(_("- container decrypting"))
+ if ssl:
+ avail.append(_("- ssl connection"))
+ if captcha:
+ avail.append(_("- automatic captcha decryption"))
+ if web:
+ avail.append(_("- webinterface"))
+ if js:
+ avail.append(_("- extended Click'N'Load"))
+
+ if avail:
+ print _("AVAILABLE FEATURES:")
+ for feature in avail:
+ print feature
+ print
+
+ if len(avail) < 5:
+ print _("MISSING FEATURES:")
+
+ if not self.check_module("Crypto"):
+ print _("- no py-crypto available")
+ print _("You need this if you want to decrypt container files.")
+ print
+
+ if not ssl:
+ print _("- no SSL available")
+ print _("This is needed if you want to establish a secure connection to core or webinterface.")
+ print _("If you only want to access locally to pyLoad ssl is not usefull.")
+ print
+
+ if not captcha:
+ print _("- no Captcha Recognition available")
+ print _("Only needed for some hosters and as freeuser.")
+ print
+
+ if not js:
+ print _("- no JavaScript engine found")
+ print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino")
+ print
+
+ print
+ print _("You can abort the setup now and fix some dependicies if you want.")
+ else:
+ print _("NO MISSING FEATURES!")
+
+ print
+ print
+ con = self.ask(_("Continue with setup?"), self.yes, bool=True)
+
+ if not con:
+ return False
+
+ print
+ print
+ print _("CURRENT CONFIG PATH: %s") % configdir
+ print
+ print _("NOTE: If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.")
+ confpath = self.ask(_("Do you want to change the config path?"), self.no, bool=True)
+ if confpath:
+ print
+ self.conf_path()
+ print
+
+ print
+ print _("Do you want to configure login data and basic settings?")
+ print _("This is recommend for first run.")
+ con = self.ask(_("Make basic setup?"), self.yes, bool=True)
+
+ if con:
+ print
+ print
+ self.conf_basic()
+
+ if ssl:
+ print
+ print _("Do you want to configure ssl?")
+ ssl = self.ask(_("Configure ssl?"), self.no, bool=True)
+ if ssl:
+ print
+ print
+ self.conf_ssl()
+
+ if web:
+ print
+ print _("Do you want to configure webinterface?")
+ web = self.ask(_("Configure webinterface?"), self.yes, bool=True)
+ if web:
+ print
+ print
+ self.conf_web()
+
+ print
+ print
+ print _("Setup finished successfully!")
+ print
+ print
+ raw_input(_("Hit enter to exit and restart pyLoad."))
+ return True
+
+
+ def system_check(self):
+ """ make a systemcheck and return the results """
+ import platform
+
+ print _("## System Information ##")
+ print
+ print _("Platform: ") + platform.platform(aliased=True)
+ print _("OS: ") + platform.system() or "Unknown"
+ print _("Python: ") + sys.version.replace("\n", "")
+ print
+ print
+
+ print _("## System Check ##")
+ print
+
+ if (2, 5) > sys.version_info > (2, 7):
+ python = False
+ else:
+ python = True
+
+ self.print_dep("python", python, false="NOT OK")
+
+ curl = self.check_module("pycurl")
+ self.print_dep("pycurl", curl)
+
+ sqlite = self.check_module("sqlite3")
+ self.print_dep("sqlite3", sqlite)
+
+ basic = python and curl and sqlite
+
+ print
+
+ crypto = self.check_module("Crypto")
+ self.print_dep("pycrypto", crypto)
+
+ ssl = self.check_module("OpenSSL")
+ self.print_dep("py-OpenSSL", ssl)
+
+ print
+
+ pil = self.check_module("Image")
+ self.print_dep("py-imaging", pil)
+
+ if os.name == "nt":
+ tesser = self.check_prog([path.join(pypath, "tesseract", "tesseract.exe"), "-v"])
+ else:
+ tesser = self.check_prog(["tesseract", "-v"])
+
+ self.print_dep("tesseract", tesser)
+
+ captcha = pil and tesser
+
+ print
+
+ try:
+ import jinja2
+
+ v = jinja2.__version__
+ if v and versiontuple(v) < (2, 5, 0):
+ jinja = False
+ else:
+ jinja = True
+ except Exception:
+ jinja = False
+ jinja_error = "MISSING"
+ else:
+ jinja_error = "NOT OK"
+
+ self.print_dep("jinja2", jinja, false=jinja_error)
+
+ beaker = self.check_module("beaker")
+ self.print_dep("beaker", beaker)
+
+ bjoern = self.check_module("bjoern")
+ self.print_dep("bjoern", bjoern)
+
+ web = sqlite and beaker
+
+ js = bool(JsEngine.find())
+ self.print_dep(_("JS engine"), js)
+
+ if not python:
+ print
+ print
+ if sys.version_info > (2, 7):
+ print _("WARNING: Your python version is too NEW!")
+ print _("Please use Python version 2.6/2.7 .")
+ else:
+ print _("WARNING: Your python version is too OLD!")
+ print _("Please use at least Python version 2.5 .")
+
+ if not jinja and jinja_error == "NOT OK":
+ print
+ print
+ print _("WARNING: Your installed jinja2 version %s is too OLD!") % jinja2.__version__
+ print _("You can safely continue but if the webinterface is not working,")
+ print _("please upgrade or uninstall it, because pyLoad self-includes jinja2 libary.")
+
+ return basic, ssl, captcha, web, js
+
+
+ def conf_basic(self):
+ print _("## Basic Setup ##")
+
+ print
+ print _("The following logindata is valid for CLI and webinterface.")
+
+ from pyload.database import DatabaseBackend
+
+ db = DatabaseBackend(None)
+ db.setup()
+ print _("NOTE: Consider a password of 10 or more symbols if you expect to access to your local network from outside (ex. internet).")
+ print
+ username = self.ask(_("Username"), "User")
+ password = self.ask("", "", password=True)
+ db.addUser(username, password)
+ db.shutdown()
+
+ print
+ print
+ print _("External clients (GUI, CLI or other) need remote access to work over the network.")
+ print _("However, if you only want to use the webinterface you may disable it to save ram.")
+ self.config.set("remote", "activated", self.ask(_("Enable remote access"), self.no, bool=True))
+
+ print
+ langs = sorted(self.config.getMetaData("general", "language")['type'].split(";"))
+ self.config.set("general", "language", self.ask(_("Choose system language"), self.lang, langs))
+
+ print
+ self.config.set("general", "download_folder", self.ask(_("Download folder"), "Downloads"))
+ print
+ self.config.set("download", "max_downloads", self.ask(_("Max parallel downloads"), "3"))
+ print
+ reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True)
+ self.config.set("reconnect", "activated", reconnect)
+ if reconnect:
+ self.config.set("reconnect", "method", self.ask(_("Reconnect script location"), "./reconnect.sh"))
+
+
+ def conf_web(self):
+ print _("## Webinterface Setup ##")
+
+ print
+ print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.")
+ self.config.set("webui", "host", self.ask(_("Address"), "0.0.0.0"))
+ self.config.set("webui", "port", self.ask(_("Port"), "8000"))
+ print
+ print _("pyLoad offers several server backends, now following a short explanation.")
+ print "- auto:", _("Automatically choose the best webserver for your platform.")
+ print "- builtin:", _("First choice if you plan to use pyLoad just for you.")
+ print "- threaded:", _("Support SSL connection and can serve simultaneously more client flawlessly.")
+ print "- fastcgi:", _(
+ "Can be used by apache, lighttpd, etc.; needs to be properly configured before.")
+ if os.name != "nt":
+ print "- lightweight:", _("Very fast alternative to builtin; requires libev and bjoern packages.")
+
+ print
+ print _("NOTE: In some rare cases the builtin server not works correctly, so if you have troubles with the web interface")
+ print _("run this setup assistant again and change the builtin server to the threaded.")
+
+ if os.name == "nt":
+ servers = ["auto", "builtin", "threaded", "fastcgi"]
+ else:
+ servers = ["auto", "builtin", "threaded", "fastcgi", "lightweight"]
+
+ self.config.set("webui", "server", self.ask(_("Choose webserver"), "auto", servers))
+
+
+ def conf_ssl(self):
+ print _("## SSL Setup ##")
+ print
+ print _("Execute these commands from pyLoad config folder to make ssl certificates:")
+ print
+ print "openssl genrsa -out ssl.key 1024"
+ print "openssl req -new -key ssl.key -out ssl.csr"
+ print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt "
+ print
+ print _("If you're done and everything went fine, you can activate ssl now.")
+
+ ssl = self.ask(_("Activate SSL?"), self.yes, bool=True)
+ self.config.set("remote", "ssl", ssl)
+ self.config.set("webui", "ssl", ssl)
+
+
+ def set_user(self):
+ load_translation("setup", self.config.get("general", "language"))
+
+ from pyload.database import DatabaseBackend
+
+ db = DatabaseBackend(None)
+ db.setup()
+
+ noaction = True
+ try:
+ while True:
+ print _("Select action")
+ print _("1 - Create/Edit user")
+ print _("2 - List users")
+ print _("3 - Remove user")
+ print _("4 - Quit")
+ action = raw_input("1/2/3/4: ")
+ if not action in ("1", "2", "3", "4"):
+ continue
+ elif action == "1":
+ print
+ username = self.ask(_("Username"), "User")
+ password = self.ask("", "", password=True)
+ db.addUser(username, password)
+ noaction = False
+ elif action == "2":
+ print
+ print _("Users")
+ print "-----"
+ users = db.listUsers()
+ noaction = False
+ for user in users:
+ print user
+ print "-----"
+ print
+ elif action == "3":
+ print
+ username = self.ask(_("Username"), "")
+ if username:
+ db.removeUser(username)
+ noaction = False
+ elif action == "4":
+ break
+ finally:
+ if not noaction:
+ db.shutdown()
+
+
+ def set_configdir(self, configdir, persistent=False):
+ dirname = path.abspath(configdir)
+ try:
+ if not path.exists(dirname):
+ makedirs(dirname, 0700)
+
+ chdir(dirname)
+
+ if persistent:
+ c = path.join(rootdir, "config", "configdir")
+ if not path.exists(c):
+ makedirs(c, 0700)
+
+ with open(c, "wb") as f:
+ f.write(dirname)
+
+ except IOError:
+ return False
+
+ else:
+ __builtin__.configdir = dirname
+ return dirname #: return always abspath
+
+
+ def conf_path(self):
+ print _("Setting new config path.")
+ print _("NOTE: Current configuration will not be transfered!")
+
+ while True:
+ confdir = self.ask(_("CONFIG PATH"), configdir)
+ confpath = self.set_configdir(confdir)
+ print
+ if not confpath:
+ print _("Failed to change the current CONFIG PATH!")
+ print
+ else:
+ print _("CONFIG PATH successfully changed to: %s") % configdir
+ break
+
+
+ def print_dep(self, name, value, false="MISSING", true="OK"):
+ """ Print Status of dependency """
+ if value and isinstance(value, basestring):
+ info = ", ".join(value)
+ else:
+ info = ""
+
+ print "%(dep)-12s %(bool)s (%(info)s)" % {'dep': name + ':',
+ 'bool': _(true if value else false).upper(),
+ 'info': info}
+
+
+ def check_module(self, module):
+ try:
+ __import__(module)
+ return True
+ except Exception:
+ return False
+
+
+ def check_prog(self, command):
+ pipe = PIPE
+ try:
+ call(command, stdout=pipe, stderr=pipe)
+ return True
+ except Exception:
+ return False
+
+
+ def ask(self, qst, default, answers=[], bool=False, password=False):
+ """ produce one line to asking for input """
+ if answers:
+ info = "("
+
+ for i, answer in enumerate(answers):
+ info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer)
+
+ info += ")"
+ elif bool:
+ if default == self.yes:
+ info = "([%s]/%s)" % (self.yes, self.no)
+ else:
+ info = "(%s/[%s])" % (self.yes, self.no)
+ else:
+ info = "[%s]" % default
+
+ if password:
+ p1 = True
+ p2 = False
+ pwlen = 8
+ while p1 != p2:
+ sys.stdout.write(_("Password: "))
+ p1 = getpass("").strip("\n\r")
+
+ if len(p1) < pwlen:
+ print
+ print _("Password too short! Use at least %s symbols." % pwlen)
+ print
+ continue
+ elif not p1.isalnum():
+ print
+ print _("Password must be alphanumeric.")
+ print
+ continue
+
+ sys.stdout.write(_("Password (again): "))
+ p2 = getpass("").strip("\n\r")
+
+ if p1 == p2:
+ print
+ if self.ask(_("Show password?"), self.no, bool=True):
+ print
+ print _("Your Password is: %s") % p1
+ return p1
+ else:
+ print
+ print _("Passwords did not match.")
+
+ while True:
+ try:
+ input = raw_input(qst + " %s: " % info)
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ sys.exit()
+
+ input = input.decode(self.stdin_encoding)
+
+ if input.strip() == "":
+ input = default
+
+ if bool:
+ # yes, true, t are inputs for booleans with value true
+ if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]:
+ return True
+ # no, false, f are inputs for booleans with value false
+ elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]:
+ return False
+ else:
+ print _("Invalid Input")
+ print
+ continue
+
+ if not answers or input in answers:
+ return input
+ else:
+ print _("Invalid Input")
diff --git a/pyload/config/__init__.py b/pyload/config/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/config/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/config/default.conf b/pyload/config/default.conf
new file mode 100644
index 000000000..17753bb8c
--- /dev/null
+++ b/pyload/config/default.conf
@@ -0,0 +1,76 @@
+version: 1
+
+remote - "Remote":
+ bool activated : "Activated" = True
+ int port : "Port" = 7227
+ ip listenaddr : "Address" = 0.0.0.0
+ bool nolocalauth : "No authentication on local connections" = True
+
+ssl - "SSL":
+ bool activated : "Activated" = False
+ file cert : "SSL Certificate" = ssl.crt
+ file key : "SSL Key" = ssl.key
+
+webui - "Web User Interface":
+ bool activated : "Activated" = True
+ builtin;threaded;fastcgi;lightweight server : "Server" = threaded
+ bool https : "Use HTTPS" = False
+ ip host : "IP" = 0.0.0.0
+ int port : "Port" = 8001
+ Default;Dark;Flat;Next theme : "Theme" = Next
+ str prefix : "Path Prefix" = None
+
+log - "Log":
+ bool file_log : "File Log" = True
+ folder log_folder : "Folder" = Logs
+ int log_count : "Count" = 5
+ int log_size : "Size in KB" = 100
+ bool log_rotate : "Log Rotate" = True
+ bool color_console : "Colored console" = True
+ label;line console_mode : "Colored console mode" = line
+
+general - "General":
+ en; language : "Language" = en
+ folder download_folder : "Download Folder" = Downloads
+ bool debug_mode : "Debug Mode" = False
+ int min_free_space : "Min Free Space in MB" = 200
+ bool folder_per_package : "Create folder for each package" = True
+ int renice : "CPU Priority" = 0
+ auto;common;pyv8;node;rhino;jsc jsengine : "JS Engine" = auto
+
+download - "Download":
+ int chunks : "Max connections for one download" = 3
+ int max_downloads : "Max Parallel Downloads" = 3
+ int max_speed : "Max Download Speed in KB/s" = -1
+ bool limit_speed : "Limit Download Speed" = False
+ str interface : "Download interface to bind (ip or Name)" = None
+ bool ipv6 : "Allow IPv6" = False
+ bool skip_existing : "Skip already existing files" = False
+
+permission - "Permissions":
+ bool change_user : "Change user of running process" = False
+ str user : "Username" = user
+ str folder : "Folder Permission mode" = 0755
+ bool change_file : "Change file mode of downloads" = False
+ str file : "Filemode for Downloads" = 0644
+ bool change_group : "Change group of running process" = False
+ str group : "Groupname" = users
+ bool change_dl : "Change Group and User of Downloads" = False
+
+reconnect - "Reconnect":
+ bool activated : "Activated" = False
+ str method : "Method" = None
+ time startTime : "Start" = 0:00
+ time endTime : "End" = 0:00
+
+downloadTime - "Download Time":
+ time start : "Start" = 0:00
+ time end : "End" = 0:00
+
+proxy - "Proxy":
+ str address : "Address" = "localhost"
+ int port : "Port" = 7070
+ http;socks4;socks5 type : "Protocol" = http
+ str username : "Username" = None
+ password password : "Password" = None
+ bool proxy : "Use Proxy" = False
diff --git a/pyload/database/Backend.py b/pyload/database/Backend.py
new file mode 100644
index 000000000..1fa7a654c
--- /dev/null
+++ b/pyload/database/Backend.py
@@ -0,0 +1,329 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from __future__ import with_statement
+
+from threading import Event, Thread
+from os import remove
+from os.path import exists
+from shutil import move
+from Queue import Queue
+from traceback import print_exc
+
+from pyload.utils import chmod
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except Exception:
+ import sqlite3
+
+DB_VERSION = 4
+
+
+class style(object):
+ db = None
+
+
+ @classmethod
+ def setDB(cls, db):
+ cls.db = db
+
+
+ @classmethod
+ def inner(cls, f):
+
+
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return f(cls.db, *args, **kwargs)
+
+ return x
+
+
+ @classmethod
+ def queue(cls, f):
+
+
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return cls.db.queue(f, *args, **kwargs)
+
+ return x
+
+
+ @classmethod
+ def async(cls, f):
+
+
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return cls.db.async(f, *args, **kwargs)
+ return x
+
+
+class DatabaseJob(object):
+
+ def __init__(self, f, *args, **kwargs):
+ self.done = Event()
+
+ self.f = f
+ self.args = args
+ self.kwargs = kwargs
+
+ self.result = None
+ self.exception = False
+
+ # import inspect
+ # self.frame = inspect.currentframe()
+
+
+ def __repr__(self):
+ from os.path import basename
+
+ frame = self.frame.f_back
+ output = ""
+ for _i in xrange(5):
+ output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name)
+ frame = frame.f_back
+ del frame
+ del self.frame
+
+ return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result)
+
+
+ def processJob(self):
+ try:
+ self.result = self.f(*self.args, **self.kwargs)
+ except Exception, e:
+ print_exc()
+ try:
+ print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e
+ except Exception:
+ pass
+
+ self.exception = e
+ finally:
+ self.done.set()
+
+
+ def wait(self):
+ self.done.wait()
+
+
+class DatabaseBackend(Thread):
+ subs = []
+
+
+ def __init__(self, core):
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+
+ self.jobs = Queue()
+
+ self.setuplock = Event()
+
+ style.setDB(self)
+
+
+ def setup(self):
+ self.start()
+ self.setuplock.wait()
+
+
+ def run(self):
+ """main loop, which executes commands"""
+ convert = self._checkVersion() #: returns None or current version
+
+ self.conn = sqlite3.connect("files.db")
+ chmod("files.db", 0600)
+
+ self.c = self.conn.cursor() #: compatibility
+
+ if convert is not None:
+ self._convertDB(convert)
+
+ self._createTables()
+ self._migrateUser()
+
+ self.conn.commit()
+
+ self.setuplock.set()
+
+ while True:
+ j = self.jobs.get()
+ if j == "quit":
+ self.c.close()
+ self.conn.close()
+ break
+ j.processJob()
+
+
+ @style.queue
+ def shutdown(self):
+ self.conn.commit()
+ self.jobs.put("quit")
+
+
+ def _checkVersion(self):
+ """ check db version and delete it if needed"""
+ if not exists("files.version"):
+ with open("files.version", "wb") as f:
+ f.write(str(DB_VERSION))
+ return
+
+ with open("files.version", "rb") as f:
+ v = int(f.read().strip() or 0)
+
+ if v < DB_VERSION:
+ if v < 2:
+ try:
+ self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version."))
+ except Exception:
+ print "Filedatabase was deleted due to incompatible version."
+ remove("files.version")
+ move("files.db", "files.backup.db")
+
+ with open("files.version", "wb") as f:
+ f.write(str(DB_VERSION))
+
+ return v
+
+
+ def _convertDB(self, v):
+ try:
+ getattr(self, "_convertV%i" % v)()
+ except Exception:
+ try:
+ self.core.log.error(_("Filedatabase could NOT be converted."))
+ except Exception:
+ print "Filedatabase could NOT be converted."
+
+ # convert scripts start ---------------------------------------------------
+
+
+ def _convertV2(self):
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
+ try:
+ self.manager.core.log.info(_("Database was converted from v2 to v3."))
+ except Exception:
+ print "Database was converted from v2 to v3."
+ self._convertV3()
+
+
+ def _convertV3(self):
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
+ try:
+ self.manager.core.log.info(_("Database was converted from v3 to v4."))
+ except Exception:
+ print "Database was converted from v3 to v4."
+
+ # convert scripts end -----------------------------------------------------
+
+
+ def _createTables(self):
+ """create tables for database"""
+
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)')
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))')
+ self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)')
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
+ self.c.execute(
+ 'CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
+
+ self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \
+ SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\
+ FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\
+ (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \
+ FROM packages p JOIN links l ON p.id = l.package AND l.status IN (0, 4, 13) GROUP BY p.id) s ON s.id = p.id \
+ GROUP BY p.id')
+
+ # try to lower ids
+ self.c.execute('SELECT max(id) FROM LINKS')
+ fid = self.c.fetchone()[0]
+ fid = int(fid) if fid else 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links"))
+
+ self.c.execute('SELECT max(id) FROM packages')
+ pid = self.c.fetchone()[0]
+ pid = int(pid) if pid else 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages"))
+
+ self.c.execute('VACUUM')
+
+
+ def _migrateUser(self):
+ if exists("pyload.db"):
+ try:
+ self.core.log.info(_("Converting old Django DB"))
+ except Exception:
+ print "Converting old Django DB"
+ conn = sqlite3.connect('pyload.db')
+ c = conn.cursor()
+ c.execute("SELECT username, password, email FROM auth_user WHERE is_superuser")
+ users = []
+ for r in c:
+ pw = r[1].split("$")
+ users.append((r[0], pw[1] + pw[2], r[2]))
+ c.close()
+ conn.close()
+
+ self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users)
+ move("pyload.db", "pyload.old.db")
+
+
+ def createCursor(self):
+ return self.conn.cursor()
+
+
+ @style.async
+ def commit(self):
+ self.conn.commit()
+
+
+ @style.queue
+ def syncSave(self):
+ self.conn.commit()
+
+
+ @style.async
+ def rollback(self):
+ self.conn.rollback()
+
+
+ def async(self, f, *args, **kwargs):
+ args = (self,) + args
+ job = DatabaseJob(f, *args, **kwargs)
+ self.jobs.put(job)
+
+
+ def queue(self, f, *args, **kwargs):
+ args = (self,) + args
+ job = DatabaseJob(f, *args, **kwargs)
+ self.jobs.put(job)
+ job.wait()
+ return job.result
+
+
+ @classmethod
+ def registerSub(cls, klass):
+ cls.subs.append(klass)
+
+
+ @classmethod
+ def unregisterSub(cls, klass):
+ cls.subs.remove(klass)
+
+
+ def __getattr__(self, attr):
+ for sub in DatabaseBackend.subs:
+ if hasattr(sub, attr):
+ return getattr(sub, attr)
diff --git a/pyload/database/File.py b/pyload/database/File.py
new file mode 100644
index 000000000..3e930ebcc
--- /dev/null
+++ b/pyload/database/File.py
@@ -0,0 +1,986 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from threading import RLock
+
+from pyload.utils import formatSize, lock
+from pyload.manager.Event import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
+from pyload.datatype.Package import PyPackage
+from pyload.datatype.File import PyFile
+from pyload.database import DatabaseBackend, style
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except Exception:
+ import sqlite3
+
+
+class FileHandler(object):
+ """Handles all request made to obtain information,
+ modify status or other request for links or packages"""
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+
+ # translations
+ self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"),
+ _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"),
+ _("downloading"), _("processing"), _("unknown")]
+
+ self.cache = {} #: holds instances for files
+ self.packageCache = {} #: same for packages
+ #@TODO: purge the cache
+
+ self.jobCache = {}
+
+ self.lock = RLock() #@TODO: should be a Lock w/o R
+ # self.lock._Verbose__verbose = True
+
+ self.filecount = -1 #: if an invalid value is set get current value from db
+ self.queuecount = -1 #: number of package to be loaded
+ self.unchanged = False #: determines if any changes was made since last call
+
+ self.db = self.core.db
+
+
+ def change(func):
+
+
+ def new(*args):
+ args[0].unchanged = False
+ args[0].filecount = -1
+ args[0].queuecount = -1
+ args[0].jobCache = {}
+ return func(*args)
+
+ return new
+
+
+ #--------------------------------------------------------------------------
+
+ def save(self):
+ """saves all data to backend"""
+ self.db.commit()
+
+
+ #--------------------------------------------------------------------------
+
+ def syncSave(self):
+ """saves all data to backend and waits until all data are written"""
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ pyfile.sync()
+
+ pypacks = self.packageCache.values()
+ for pypack in pypacks:
+ pypack.sync()
+
+ self.db.syncSave()
+
+
+ @lock
+ def getCompleteData(self, queue=1):
+ """gets a complete data representation"""
+
+ data = self.db.getAllLinks(queue)
+ packs = self.db.getAllPackages(queue)
+
+ data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()])
+
+ for x in self.packageCache.itervalues():
+ if x.queue != queue or x.id not in packs:
+ continue
+ packs[x.id].update(x.toDict()[x.id])
+
+ for key, value in data.iteritems():
+ if value['package'] in packs:
+ packs[value['package']]['links'][key] = value
+
+ return packs
+
+
+ @lock
+ def getInfoData(self, queue=1):
+ """gets a data representation without links"""
+
+ packs = self.db.getAllPackages(queue)
+ for x in self.packageCache.itervalues():
+ if x.queue != queue or x.id not in packs:
+ continue
+ packs[x.id].update(x.toDict()[x.id])
+
+ return packs
+
+
+ @lock
+ @change
+ def addLinks(self, urls, package):
+ """adds links"""
+
+ self.core.addonManager.dispatchEvent("links-added", urls, package)
+
+ data = self.core.pluginManager.parseUrls(urls)
+
+ self.db.addLinks(data, package)
+ self.core.threadManager.createInfoThread(data, package)
+
+ #@TODO: change from reloadAll event to package update event
+ self.core.pullManager.addEvent(ReloadAllEvent("collector"))
+
+
+ #--------------------------------------------------------------------------
+
+ @lock
+ @change
+ def addPackage(self, name, folder, queue=0):
+ """adds a package, default to link collector"""
+ lastID = self.db.addPackage(name, folder, queue)
+ p = self.db.getPackage(lastID)
+ e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue")
+ self.core.pullManager.addEvent(e)
+ return lastID
+
+
+ #--------------------------------------------------------------------------
+
+ @lock
+ @change
+ def deletePackage(self, id):
+ """delete package and all contained links"""
+
+ p = self.getPackage(id)
+ if not p:
+ if id in self.packageCache:
+ del self.packageCache[id]
+ return
+
+ oldorder = p.order
+ queue = p.queue
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+
+ pyfiles = self.cache.values()
+
+ for pyfile in pyfiles:
+ if pyfile.packageid == id:
+ pyfile.abortDownload()
+ pyfile.release()
+
+ self.db.deletePackage(p)
+ self.core.pullManager.addEvent(e)
+ self.core.addonManager.dispatchEvent("package-deleted", id)
+
+ if id in self.packageCache:
+ del self.packageCache[id]
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue == queue and pack.order > oldorder:
+ pack.order -= 1
+ pack.notifyChange()
+
+
+ #--------------------------------------------------------------------------
+
+ @lock
+ @change
+ def deleteLink(self, id):
+ """deletes links"""
+
+ f = self.getFile(id)
+ if not f:
+ return None
+
+ pid = f.packageid
+ e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue")
+
+ oldorder = f.order
+
+ if id in self.core.threadManager.processingIds():
+ self.cache[id].abortDownload()
+
+ if id in self.cache:
+ del self.cache[id]
+
+ self.db.deleteLink(f)
+
+ self.core.pullManager.addEvent(e)
+
+ p = self.getPackage(pid)
+ if not len(p.getChildren()):
+ p.delete()
+
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid == pid and pyfile.order > oldorder:
+ pyfile.order -= 1
+ pyfile.notifyChange()
+
+
+ #--------------------------------------------------------------------------
+
+ def releaseLink(self, id):
+ """removes pyfile from cache"""
+ if id in self.cache:
+ del self.cache[id]
+
+
+ #--------------------------------------------------------------------------
+
+ def releasePackage(self, id):
+ """removes package from cache"""
+ if id in self.packageCache:
+ del self.packageCache[id]
+
+
+ #--------------------------------------------------------------------------
+
+ def updateLink(self, pyfile):
+ """updates link"""
+ self.db.updateLink(pyfile)
+
+ e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ #--------------------------------------------------------------------------
+
+ def updatePackage(self, pypack):
+ """updates a package"""
+ self.db.updatePackage(pypack)
+
+ e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ #--------------------------------------------------------------------------
+
+ def getPackage(self, id):
+ """return package instance"""
+
+ if id in self.packageCache:
+ return self.packageCache[id]
+ else:
+ return self.db.getPackage(id)
+
+
+ #--------------------------------------------------------------------------
+
+ def getPackageData(self, id):
+ """returns dict with package information"""
+ pack = self.getPackage(id)
+
+ if not pack:
+ return None
+
+ pack = pack.toDict()[id]
+
+ data = self.db.getPackageData(id)
+
+ tmplist = []
+
+ cache = self.cache.values()
+ for x in cache:
+ if int(x.toDbDict()[x.id]['package']) == int(id):
+ tmplist.append((x.id, x.toDbDict()[x.id]))
+ data.update(tmplist)
+
+ pack['links'] = data
+
+ return pack
+
+
+ #--------------------------------------------------------------------------
+
+ def getFileData(self, id):
+ """returns dict with file information"""
+ if id in self.cache:
+ return self.cache[id].toDbDict()
+
+ return self.db.getLinkData(id)
+
+
+ #--------------------------------------------------------------------------
+
+ def getFile(self, id):
+ """returns pyfile instance"""
+ if id in self.cache:
+ return self.cache[id]
+ else:
+ return self.db.getFile(id)
+
+
+ #--------------------------------------------------------------------------
+
+ @lock
+ def getJob(self, occ):
+ """get suitable job"""
+
+ #@TODO: clean mess
+ #@TODO: improve selection of valid jobs
+
+ if occ in self.jobCache:
+ if self.jobCache[occ]:
+ id = self.jobCache[occ].pop()
+ if id == "empty":
+ pyfile = None
+ self.jobCache[occ].append("empty")
+ else:
+ pyfile = self.getFile(id)
+ else:
+ jobs = self.db.getJob(occ)
+ jobs.reverse()
+ if not jobs:
+ self.jobCache[occ].append("empty")
+ pyfile = None
+ else:
+ self.jobCache[occ].extend(jobs)
+ pyfile = self.getFile(self.jobCache[occ].pop())
+
+ else:
+ self.jobCache = {} #: better not caching to much
+ jobs = self.db.getJob(occ)
+ jobs.reverse()
+ self.jobCache[occ] = jobs
+
+ if not jobs:
+ self.jobCache[occ].append("empty")
+ pyfile = None
+ else:
+ pyfile = self.getFile(self.jobCache[occ].pop())
+
+ #@TODO: maybe the new job has to be approved...
+
+ # pyfile = self.getFile(self.jobCache[occ].pop())
+ return pyfile
+
+
+ @lock
+ def getDecryptJob(self):
+ """return job for decrypting"""
+ if "decrypt" in self.jobCache:
+ return None
+
+ plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys()
+ plugins = str(tuple(plugins))
+
+ jobs = self.db.getPluginJob(plugins)
+ if jobs:
+ return self.getFile(jobs[0])
+ else:
+ self.jobCache['decrypt'] = "empty"
+ return None
+
+
+ def getFileCount(self):
+ """returns number of files"""
+
+ if self.filecount == -1:
+ self.filecount = self.db.filecount(1)
+
+ return self.filecount
+
+
+ def getQueueCount(self, force=False):
+ """number of files that have to be processed"""
+ if self.queuecount == -1 or force:
+ self.queuecount = self.db.queuecount(1)
+
+ return self.queuecount
+
+
+ def checkAllLinksFinished(self):
+ """checks if all files are finished and dispatch event"""
+
+ if not self.getQueueCount(True):
+ self.core.addonManager.dispatchEvent("all_downloads-finished")
+ self.core.log.debug("All downloads finished")
+ return True
+
+ return False
+
+
+ def checkAllLinksProcessed(self, fid):
+ """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting"""
+
+ # reset count so statistic will update (this is called when dl was processed)
+ self.resetCount()
+
+ if not self.db.processcount(1, fid):
+ self.core.addonManager.dispatchEvent("all_downloads-processed")
+ self.core.log.debug("All downloads processed")
+ return True
+
+ return False
+
+
+ def resetCount(self):
+ self.queuecount = -1
+
+
+ @lock
+ @change
+ def restartPackage(self, id):
+ """restart package"""
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid == id:
+ self.restartFile(pyfile.id)
+
+ self.db.restartPackage(id)
+
+ if id in self.packageCache:
+ self.packageCache[id].setFinished = False
+
+ e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ @lock
+ @change
+ def restartFile(self, id):
+ """ restart file"""
+ if id in self.cache:
+ self.cache[id].status = 3
+ self.cache[id].name = self.cache[id].url
+ self.cache[id].error = ""
+ self.cache[id].abortDownload()
+
+ self.db.restartFile(id)
+
+ e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ @lock
+ @change
+ def setPackageLocation(self, id, queue):
+ """push package to queue"""
+
+ p = self.db.getPackage(id)
+ oldorder = p.order
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ self.db.clearPackageOrder(p)
+
+ p = self.db.getPackage(id)
+
+ p.queue = queue
+ self.db.updatePackage(p)
+
+ self.db.reorderPackage(p, -1, True)
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue != queue and pack.order > oldorder:
+ pack.order -= 1
+ pack.notifyChange()
+
+ self.db.commit()
+ self.releasePackage(id)
+ p = self.getPackage(id)
+
+ e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ @lock
+ @change
+ def reorderPackage(self, id, position):
+ p = self.getPackage(id)
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+ self.db.reorderPackage(p, position)
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue != p.queue or pack.order < 0 or pack == p:
+ continue
+ if p.order > position:
+ if position <= pack.order < p.order:
+ pack.order += 1
+ pack.notifyChange()
+ elif p.order < position:
+ if position >= pack.order > p.order:
+ pack.order -= 1
+ pack.notifyChange()
+
+ p.order = position
+ self.db.commit()
+
+ e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ @lock
+ @change
+ def reorderFile(self, id, position):
+ f = self.getFileData(id)
+ f = f[id]
+
+ e = RemoveEvent("file", id, "collector" if not self.getPackage(f['package']).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ self.db.reorderLink(f, position)
+
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid != f['package'] or pyfile.order < 0:
+ continue
+ if f['order'] > position:
+ if position <= pyfile.order < f['order']:
+ pyfile.order += 1
+ pyfile.notifyChange()
+ elif f['order'] < position:
+ if position >= pyfile.order > f['order']:
+ pyfile.order -= 1
+ pyfile.notifyChange()
+
+ if id in self.cache:
+ self.cache[id].order = position
+
+ self.db.commit()
+
+ e = InsertEvent("file", id, position, "collector" if not self.getPackage(f['package']).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ @change
+ def updateFileInfo(self, data, pid):
+ """ updates file info (name, size, status, url)"""
+ ids = self.db.updateLinkInfo(data)
+ e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+
+ def checkPackageFinished(self, pyfile):
+ """ checks if package is finished and calls AddonManager """
+
+ ids = self.db.getUnfinished(pyfile.packageid)
+ if not ids or (pyfile.id in ids and len(ids) == 1):
+ if not pyfile.package().setFinished:
+ self.core.log.info(_("Package finished: %s") % pyfile.package().name)
+ self.core.addonManager.packageFinished(pyfile.package())
+ pyfile.package().setFinished = True
+
+
+ def reCheckPackage(self, pid):
+ """ recheck links in package """
+ data = self.db.getPackageData(pid)
+
+ urls = []
+
+ for pyfile in data.itervalues():
+ if pyfile['status'] not in (0, 12, 13):
+ urls.append((pyfile['url'], pyfile['plugin']))
+
+ self.core.threadManager.createInfoThread(urls, pid)
+
+
+ @lock
+ @change
+ def deleteFinishedLinks(self):
+ """ deletes finished links and packages, return deleted packages """
+
+ old_packs = self.getInfoData(0)
+ old_packs.update(self.getInfoData(1))
+
+ self.db.deleteFinished()
+
+ new_packs = self.db.getAllPackages(0)
+ new_packs.update(self.db.getAllPackages(1))
+ # get new packages only from db
+
+ deleted = [id for id in old_packs.iterkeys() if id not in new_packs]
+ for id_deleted in deleted:
+ self.deletePackage(int(id_deleted))
+
+ return deleted
+
+
+ @lock
+ @change
+ def restartFailed(self):
+ """ restart all failed links """
+ self.db.restartFailed()
+
+
+class FileMethods(object):
+
+
+ @style.queue
+ def filecount(self, queue):
+ """returns number of files in queue"""
+ self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?",
+ (queue,))
+ return self.c.fetchone()[0]
+
+
+ @style.queue
+ def queuecount(self, queue):
+ """ number of files in queue not finished yet"""
+ self.c.execute(
+ "SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0, 4)",
+ (queue,))
+ return self.c.fetchone()[0]
+
+
+ @style.queue
+ def processcount(self, queue, fid):
+ """ number of files which have to be proccessed """
+ self.c.execute(
+ "SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2, 3, 5, 7, 12) AND l.id != ?",
+ (queue, str(fid)))
+ return self.c.fetchone()[0]
+
+
+ @style.inner
+ def _nextPackageOrder(self, queue=0):
+ self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,))
+ max = self.c.fetchone()[0]
+ if max is not None:
+ return max + 1
+ else:
+ return 0
+
+
+ @style.inner
+ def _nextFileOrder(self, package):
+ self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,))
+ max = self.c.fetchone()[0]
+ if max is not None:
+ return max + 1
+ else:
+ return 0
+
+
+ @style.queue
+ def addLink(self, url, name, plugin, package):
+ order = self._nextFileOrder(package)
+ self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)',
+ (url, name, ".".join(plugintype, pluginname), package, order))
+ return self.c.lastrowid
+
+
+ @style.queue
+ def addLinks(self, links, package):
+ """ links is a list of tupels (url, plugin)"""
+ order = self._nextFileOrder(package)
+ orders = [order + x for x in xrange(len(links))]
+ links = [(x[0], x[0], ".".join((x[1], x[2])), package, o) for x, o in zip(links, orders)]
+ self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links)
+
+
+ @style.queue
+ def addPackage(self, name, folder, queue):
+ order = self._nextPackageOrder(queue)
+ self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)',
+ (name, folder, queue, order))
+ return self.c.lastrowid
+
+
+ @style.queue
+ def deletePackage(self, p):
+ self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),))
+ self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),))
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?',
+ (p.order, p.queue))
+
+
+ @style.queue
+ def deleteLink(self, f):
+ self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),))
+ self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?',
+ (f.order, str(f.packageid)))
+
+
+ @style.queue
+ def getAllLinks(self, q):
+ """return information about all links in queue q
+
+ q0 queue
+ q1 collector
+
+ format:
+
+ {
+ id: {'name': name, ... 'package': id }, ...
+ }
+
+ """
+ self.c.execute(
+ 'SELECT l.id, l.url, l.name, l.size, l.status, l.error, l.plugin, l.package, l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder',
+ (q,))
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': tuple(r[6].split('.')),
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+
+ @style.queue
+ def getAllPackages(self, q):
+ """return information about packages in queue q
+ (only useful in get all data)
+
+ q0 queue
+ q1 collector
+
+ format:
+
+ {
+ id: {'name': name ... 'links': {}}, ...
+ }
+ """
+ self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \
+ FROM packages p JOIN pstats s ON p.id = s.id \
+ WHERE p.queue=? ORDER BY p.packageorder', str(q))
+
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'name': r[1],
+ 'folder': r[2],
+ 'site': r[3],
+ 'password': r[4],
+ 'queue': r[5],
+ 'order': r[6],
+ 'sizetotal': int(r[7]),
+ 'sizedone': r[8] if r[8] else 0, #: these can be None
+ 'linksdone': r[9] if r[9] else 0,
+ 'linkstotal': r[10],
+ 'links': {}
+ }
+
+ return data
+
+
+ @style.queue
+ def getLinkData(self, id):
+ """get link information as dict"""
+ self.c.execute('SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?',
+ (str(id),))
+ data = {}
+ r = self.c.fetchone()
+ if not r:
+ return None
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': tuple(r[6].split('.')),
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+
+ @style.queue
+ def getPackageData(self, id):
+ """get data about links for a package"""
+ self.c.execute(
+ 'SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE package=? ORDER BY linkorder',
+ (str(id),))
+
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': tuple(r[6].split('.')),
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+
+ @style.async
+ def updateLink(self, f):
+ self.c.execute('UPDATE links SET url=?, name=?, size=?, status=?, error=?, package=? WHERE id=?',
+ (f.url, f.name, f.size, f.status, str(f.error), str(f.packageid), str(f.id)))
+
+
+ @style.queue
+ def updatePackage(self, p):
+ self.c.execute('UPDATE packages SET name=?, folder=?, site=?, password=?, queue=? WHERE id=?',
+ (p.name, p.folder, p.site, p.password, p.queue, str(p.id)))
+
+ @style.queue
+ def updateLinkInfo(self, data):
+ """ data is list of tupels (name, size, status, url) """
+ self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1, 2, 3, 14)', data)
+ ids = []
+ self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data]))
+ for r in self.c:
+ ids.append(int(r[0]))
+ return ids
+
+
+ @style.queue
+ def reorderPackage(self, p, position, noMove=False):
+ if position == -1:
+ position = self._nextPackageOrder(p.queue)
+ if not noMove:
+ if p.order > position:
+ self.c.execute(
+ 'UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0',
+ (position, p.order, p.queue))
+ elif p.order < position:
+ self.c.execute(
+ 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0',
+ (position, p.order, p.queue))
+
+ self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id)))
+
+
+ @style.queue
+ def reorderLink(self, f, position):
+ """ reorder link with f as dict for pyfile """
+ if f['order'] > position:
+ self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?',
+ (position, f['order'], f['package']))
+ elif f['order'] < position:
+ self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?',
+ (position, f['order'], f['package']))
+
+ self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f['id']))
+
+
+ @style.queue
+ def clearPackageOrder(self, p):
+ self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id)))
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?',
+ (p.order, p.queue, str(p.id)))
+
+
+ @style.async
+ def restartFile(self, id):
+ self.c.execute('UPDATE links SET status=3, error="" WHERE id=?', (str(id),))
+
+
+ @style.async
+ def restartPackage(self, id):
+ self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),))
+
+
+ @style.queue
+ def getPackage(self, id):
+ """return package instance from id"""
+ self.c.execute("SELECT name, folder, site, password, queue, packageorder FROM packages WHERE id=?", (str(id),))
+ r = self.c.fetchone()
+ if not r:
+ return None
+ return PyPackage(self.manager, id, *r)
+
+
+ #--------------------------------------------------------------------------
+
+ @style.queue
+ def getFile(self, id):
+ """return link instance from id"""
+ self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?",
+ (str(id),))
+ r = self.c.fetchone()
+ if not r:
+ return None
+ r = list(r)
+ r[5] = tuple(r[5].split('.'))
+ return PyFile(self.manager, id, *r)
+
+
+ @style.queue
+ def getJob(self, occ):
+ """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
+
+ #@TODO: improve this hardcoded method
+ pre = "('CCF', 'DLC', 'LinkList', 'RSDF', 'TXT')" #: plugins which are processed in collector
+
+ cmd = "("
+ for i, item in enumerate(occ):
+ if i: cmd += ", "
+ cmd += "'%s'" % item
+
+ cmd += ")"
+
+ cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre)
+
+ self.c.execute(cmd) #: very bad!
+
+ return [x[0] for x in self.c]
+
+
+ @style.queue
+ def getPluginJob(self, plugins):
+ """returns pyfile ids with suited plugins"""
+ cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins
+
+ self.c.execute(cmd) #: very bad!
+
+ return [x[0] for x in self.c]
+
+
+ @style.queue
+ def getUnfinished(self, pid):
+ """return list of max length 3 ids with pyfiles in package not finished or processed"""
+
+ self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),))
+ return [r[0] for r in self.c]
+
+
+ @style.queue
+ def deleteFinished(self):
+ self.c.execute("DELETE FROM links WHERE status IN (0, 4)")
+ self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)")
+
+
+ @style.queue
+ def restartFailed(self):
+ self.c.execute("UPDATE links SET status=3, error='' WHERE status IN (6, 8, 9)")
+
+
+ @style.queue
+ def findDuplicates(self, id, folder, filename):
+ """ checks if filename exists with different id and same package """
+ self.c.execute(
+ "SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?",
+ (folder, id, filename))
+ return self.c.fetchone()
+
+
+ @style.queue
+ def purgeLinks(self):
+ self.c.execute("DELETE FROM links;")
+ self.c.execute("DELETE FROM packages;")
+
+
+DatabaseBackend.registerSub(FileMethods)
diff --git a/pyload/database/Storage.py b/pyload/database/Storage.py
new file mode 100644
index 000000000..70932b55c
--- /dev/null
+++ b/pyload/database/Storage.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from pyload.database import style
+from pyload.database import DatabaseBackend
+
+
+class StorageMethods(object):
+
+
+ @style.queue
+ def setStorage(db, identifier, key, value):
+ db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key))
+ if db.c.fetchone() is not None:
+ db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key))
+ else:
+ db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value))
+
+
+ @style.queue
+ def getStorage(db, identifier, key=None):
+ if key is not None:
+ db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key))
+ row = db.c.fetchone()
+ if row is not None:
+ return row[0]
+ else:
+ db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier,))
+ return {row[0]: row[1] for row in db.c}
+
+
+ @style.queue
+ def delStorage(db, identifier, key):
+ db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key))
+
+
+DatabaseBackend.registerSub(StorageMethods)
diff --git a/pyload/database/User.py b/pyload/database/User.py
new file mode 100644
index 000000000..b5b44c50a
--- /dev/null
+++ b/pyload/database/User.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from hashlib import sha1
+import random
+
+from pyload.database import DatabaseBackend, style
+
+
+class UserMethods(object):
+
+
+ @style.queue
+ def checkAuth(db, user, password):
+ c = db.c
+ c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user,))
+ r = c.fetchone()
+ if not r:
+ return {}
+
+ salt = r[2][:5]
+ pw = r[2][5:]
+ h = sha1(salt + password)
+ if h.hexdigest() == pw:
+ return {"id": r[0], "name": r[1], "role": r[3],
+ "permission": r[4], "template": r[5], "email": r[6]}
+ else:
+ return {}
+
+
+ @style.queue
+ def addUser(db, user, password):
+ salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for _i in xrange(0, 5)])
+ h = sha1(salt + password)
+ password = salt + h.hexdigest()
+
+ c = db.c
+ c.execute('SELECT name FROM users WHERE name=?', (user,))
+ if c.fetchone() is not None:
+ c.execute('UPDATE users SET password=? WHERE name=?', (password, user))
+ else:
+ c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
+
+
+ @style.queue
+ def changePassword(db, user, oldpw, newpw):
+ db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user,))
+ r = db.c.fetchone()
+ if not r:
+ return False
+
+ salt = r[2][:5]
+ pw = r[2][5:]
+ h = sha1(salt + oldpw)
+ if h.hexdigest() == pw:
+ salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for _i in xrange(0, 5)])
+ h = sha1(salt + newpw)
+ password = salt + h.hexdigest()
+
+ db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
+ return True
+
+ return False
+
+
+ @style.async
+ def setPermission(db, user, perms):
+ db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user))
+
+
+ @style.async
+ def setRole(db, user, role):
+ db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
+
+
+ @style.queue
+ def listUsers(db):
+ db.c.execute('SELECT name FROM users')
+ return [row[0] for row in db.c]
+
+
+ @style.queue
+ def getAllUserData(db):
+ db.c.execute("SELECT name, permission, role, template, email FROM users")
+ return {r[0]: {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]} for r in db.c}
+
+
+ @style.queue
+ def removeUser(db, user):
+ db.c.execute('DELETE FROM users WHERE name=?', (user,))
+
+
+DatabaseBackend.registerSub(UserMethods)
diff --git a/pyload/database/__init__.py b/pyload/database/__init__.py
new file mode 100644
index 000000000..4e0edc5d1
--- /dev/null
+++ b/pyload/database/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from pyload.database.Backend import DatabaseBackend, style
+from pyload.database.File import FileHandler
+from pyload.database.Storage import StorageMethods
+from pyload.database.User import UserMethods
diff --git a/pyload/datatype/File.py b/pyload/datatype/File.py
new file mode 100644
index 000000000..05d515fd0
--- /dev/null
+++ b/pyload/datatype/File.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from pyload.manager.Event import UpdateEvent
+from pyload.utils import formatSize, lock
+
+from time import sleep, time
+
+from threading import RLock
+
+statusMap = {
+ "finished" : 0,
+ "offline" : 1,
+ "online" : 2,
+ "queued" : 3,
+ "skipped" : 4,
+ "waiting" : 5,
+ "temp. offline": 6,
+ "starting" : 7,
+ "failed" : 8,
+ "aborted" : 9,
+ "decrypting" : 10,
+ "custom" : 11,
+ "downloading" : 12,
+ "processing" : 13,
+ "unknown" : 14,
+}
+
+
+def setSize(self, value):
+ self._size = int(value)
+
+
+class PyFile(object):
+ """
+ Represents a file object at runtime
+ """
+ __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "plugintype", "pluginname",
+ "packageid", "error", "order", "lock", "plugin", "waitUntil",
+ "active", "abort", "statusname", "reconnected", "progress",
+ "maxprogress", "pluginmodule", "pluginclass")
+
+
+ def __init__(self, manager, id, url, name, size, status, error, plugin, package, order):
+ self.m = manager
+
+ self.id = int(id)
+ self.url = url
+ self.name = name
+ self.size = size
+ self.status = status
+ self.plugintype, self.pluginname = plugin
+ self.packageid = package #: should not be used, use package() instead
+ self.error = error
+ self.order = order
+ # database information ends here
+
+ self.lock = RLock()
+
+ self.plugin = None
+ # self.download = None
+
+ self.waitUntil = 0 #: time() + time to wait
+
+ # status attributes
+ self.active = False #: obsolete?
+ self.abort = False
+ self.reconnected = False
+
+ self.statusname = None
+
+ self.progress = 0
+ self.maxprogress = 100
+
+ self.m.cache[int(id)] = self
+
+ # will convert all sizes to ints
+ size = property(lambda self: self._size, setSize)
+
+
+ def __repr__(self):
+ return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname)
+
+
+ @lock
+ def initPlugin(self):
+ """ inits plugin instance """
+ if not self.plugin:
+ self.pluginmodule = self.m.core.pluginManager.getPlugin(self.plugintype, self.pluginname)
+ self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.plugintype, self.pluginname))
+ self.plugin = self.pluginclass(self)
+
+
+ @lock
+ def hasPlugin(self):
+ """Thread safe way to determine this file has initialized plugin attribute
+
+ :return:
+ """
+ return hasattr(self, "plugin") and self.plugin
+
+
+ def package(self):
+ """ return package instance"""
+ return self.m.getPackage(self.packageid)
+
+
+ def setStatus(self, status):
+ self.status = statusMap[status]
+ self.sync() #@TODO: needed aslong no better job approving exists
+
+
+ def setCustomStatus(self, msg, status="processing"):
+ self.statusname = msg
+ self.setStatus(status)
+
+
+ def getStatusName(self):
+ if self.status not in (13, 14) or not self.statusname:
+ return self.m.statusMsg[self.status]
+ else:
+ return self.statusname
+
+
+ def hasStatus(self, status):
+ return statusMap[status] == self.status
+
+
+ def sync(self):
+ """sync PyFile instance with database"""
+ self.m.updateLink(self)
+
+
+ @lock
+ def release(self):
+ """sync and remove from cache"""
+ # file has valid package
+ if self.packageid > 0:
+ self.sync()
+
+ if hasattr(self, "plugin") and self.plugin:
+ self.plugin.clean()
+ del self.plugin
+
+ self.m.releaseLink(self.id)
+
+
+ def delete(self):
+ """delete pyfile from database"""
+ self.m.deleteLink(self.id)
+
+
+ def toDict(self):
+ """return dict with all information for interface"""
+ return self.toDbDict()
+
+
+ def toDbDict(self):
+ """return data as dict for databse
+
+ format:
+
+ {
+ id: {'url': url, 'name': name ... }
+ }
+
+ """
+ return {
+ self.id: {
+ 'id': self.id,
+ 'url': self.url,
+ 'name': self.name,
+ 'plugin': self.pluginname,
+ 'size': self.getSize(),
+ 'format_size': self.formatSize(),
+ 'status': self.status,
+ 'statusmsg': self.getStatusName(),
+ 'package': self.packageid,
+ 'error': self.error,
+ 'order': self.order
+ }
+ }
+
+
+ def abortDownload(self):
+ """abort pyfile if possible"""
+ while self.id in self.m.core.threadManager.processingIds():
+ self.abort = True
+ if self.plugin and self.plugin.req:
+ self.plugin.req.abortDownloads()
+ sleep(0.1)
+
+ self.abort = False
+ if self.hasPlugin() and self.plugin.req:
+ self.plugin.req.abortDownloads()
+
+ self.release()
+
+
+ def finishIfDone(self):
+ """set status to finish and release file if every thread is finished with it"""
+
+ if self.id in self.m.core.threadManager.processingIds():
+ return False
+
+ self.setStatus("finished")
+ self.release()
+ self.m.checkAllLinksFinished()
+ return True
+
+
+ def checkIfProcessed(self):
+ self.m.checkAllLinksProcessed(self.id)
+
+
+ def formatWait(self):
+ """ formats and return wait time in humanreadable format """
+ seconds = self.waitUntil - time()
+
+ if seconds < 0:
+ return "00:00:00"
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+
+ def formatSize(self):
+ """ formats size to readable format """
+ return formatSize(self.getSize())
+
+
+ def formatETA(self):
+ """ formats eta to readable format """
+ seconds = self.getETA()
+
+ if seconds < 0:
+ return "00:00:00"
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+
+ def getSpeed(self):
+ """ calculates speed """
+ try:
+ return self.plugin.req.speed
+ except Exception:
+ return 0
+
+
+ def getETA(self):
+ """ gets established time of arrival"""
+ try:
+ return self.getBytesLeft() / self.getSpeed()
+ except Exception:
+ return 0
+
+
+ def getBytesLeft(self):
+ """ gets bytes left """
+ try:
+ return self.getSize() - self.plugin.req.arrived
+ except Exception:
+ return 0
+
+
+ def getPercent(self):
+ """ get % of download """
+ if self.status == 12:
+ try:
+ return self.plugin.req.percent
+ except Exception:
+ return 0
+ else:
+ return self.progress
+
+
+ def getSize(self):
+ """ get size of download """
+ try:
+ if self.plugin.req.size:
+ return self.plugin.req.size
+ else:
+ return self.size
+ except Exception:
+ return self.size
+
+
+ def notifyChange(self):
+ e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue")
+ self.m.core.pullManager.addEvent(e)
+
+
+ def setProgress(self, value):
+ if not value == self.progress:
+ self.progress = value
+ self.notifyChange()
diff --git a/pyload/datatype/Package.py b/pyload/datatype/Package.py
new file mode 100644
index 000000000..5ba42f596
--- /dev/null
+++ b/pyload/datatype/Package.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from pyload.manager.Event import UpdateEvent
+from pyload.utils import safe_filename
+
+
+class PyPackage(object):
+ """
+ Represents a package object at runtime
+ """
+
+ def __init__(self, manager, id, name, folder, site, password, queue, order):
+ self.m = manager
+ self.m.packageCache[int(id)] = self
+
+ self.id = int(id)
+ self.name = name
+ self._folder = folder
+ self.site = site
+ self.password = password
+ self.queue = queue
+ self.order = order
+ self.setFinished = False
+
+
+ @property
+ def folder(self):
+ return safe_filename(self._folder)
+
+
+ def toDict(self):
+ """ Returns a dictionary representation of the data.
+
+ :return: dict: {id: { attr: value }}
+ """
+ return {
+ self.id: {
+ 'id': self.id,
+ 'name': self.name,
+ 'folder': self.folder,
+ 'site': self.site,
+ 'password': self.password,
+ 'queue': self.queue,
+ 'order': self.order,
+ 'links': {}
+ }
+ }
+
+
+ def getChildren(self):
+ """get information about contained links"""
+ return self.m.getPackageData(self.id)["links"]
+
+
+ def sync(self):
+ """sync with db"""
+ self.m.updatePackage(self)
+
+
+ def release(self):
+ """sync and delete from cache"""
+ self.sync()
+ self.m.releasePackage(self.id)
+
+
+ def delete(self):
+ self.m.deletePackage(self.id)
+
+
+ def notifyChange(self):
+ e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue")
+ self.m.core.pullManager.addEvent(e)
diff --git a/pyload/datatype/__init__.py b/pyload/datatype/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/datatype/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/manager/Account.py b/pyload/manager/Account.py
new file mode 100644
index 000000000..ac9944134
--- /dev/null
+++ b/pyload/manager/Account.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from os.path import exists
+from shutil import copy
+
+from threading import Lock
+
+from pyload.manager.Event import AccountUpdateEvent
+from pyload.utils import chmod, lock
+
+ACC_VERSION = 1
+
+
+class AccountManager(object):
+ """manages all accounts"""
+
+ def __init__(self, core):
+ """Constructor"""
+
+ self.core = core
+ self.lock = Lock()
+
+ self.initPlugins()
+ self.saveAccounts() #: save to add categories to conf
+
+
+ def initPlugins(self):
+ self.accounts = {} #: key = ( plugin )
+ self.plugins = {}
+
+ self.initAccountPlugins()
+ self.loadAccounts()
+
+
+ def getAccountPlugin(self, plugin):
+ """get account instance for plugin or None if anonymous"""
+ try:
+ if plugin in self.accounts:
+ if plugin not in self.plugins:
+ klass = self.core.pluginManager.loadClass("accounts", plugin)
+ if klass:
+ self.plugins[plugin] = klass(self, self.accounts[plugin])
+ else: #@NOTE: The account class no longer exists (blacklisted plugin). Skipping the account to avoid crash
+ raise
+
+ return self.plugins[plugin]
+ else:
+ raise
+ except Exception:
+ return None
+
+
+ def getAccountPlugins(self):
+ """ get all account instances"""
+
+ plugins = []
+ for plugin in self.accounts.keys():
+ plugins.append(self.getAccountPlugin(plugin))
+
+ return plugins
+
+
+ #----------------------------------------------------------------------
+
+ def loadAccounts(self):
+ """loads all accounts available"""
+
+ try:
+ with open("accounts.conf", "a+") as f:
+ content = f.readlines()
+ version = content[0].split(":")[1].strip() if content else ""
+
+ if not version or int(version) < ACC_VERSION:
+ copy("accounts.conf", "accounts.backup")
+ f.seek(0)
+ f.write("version: " + str(ACC_VERSION))
+
+ self.core.log.warning(_("Account settings deleted, due to new config format"))
+ return
+
+ except IOError, e:
+ self.core.log.error(str(e))
+ return
+
+ plugin = ""
+ name = ""
+
+ for line in content[1:]:
+ line = line.strip()
+
+ if not line:
+ continue
+ if line.startswith("#"):
+ continue
+ if line.startswith("version"):
+ continue
+
+ if line.endswith(":") and line.count(":") == 1:
+ plugin = line[:-1]
+ self.accounts[plugin] = {}
+
+ elif line.startswith("@"):
+ try:
+ option = line[1:].split()
+ self.accounts[plugin][name]['options'][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:])
+ except Exception:
+ pass
+
+ elif ":" in line:
+ name, sep, pw = line.partition(":")
+ self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True}
+
+
+ #----------------------------------------------------------------------
+
+ def saveAccounts(self):
+ """save all account information"""
+
+ try:
+ with open("accounts.conf", "wb") as f:
+ f.write("version: " + str(ACC_VERSION) + "\n")
+
+ for plugin, accounts in self.accounts.iteritems():
+ f.write("\n")
+ f.write(plugin + ":\n")
+
+ for name, data in accounts.iteritems():
+ f.write("\n\t%s:%s\n" % (name, data['password']) )
+ if data['options']:
+ for option, values in data['options'].iteritems():
+ f.write("\t@%s %s\n" % (option, " ".join(values)))
+
+ chmod(f.name, 0600)
+
+ except Exception, e:
+ self.core.log.error(str(e))
+
+
+ #----------------------------------------------------------------------
+
+ def initAccountPlugins(self):
+ """init names"""
+ for name in self.core.pluginManager.getAccountPlugins():
+ self.accounts[name] = {}
+
+
+ @lock
+ def updateAccount(self, plugin, user, password=None, options={}):
+ """add or update account"""
+ if plugin in self.accounts:
+ p = self.getAccountPlugin(plugin)
+ updated = p.updateAccounts(user, password, options)
+ # since accounts is a ref in plugin self.accounts doesnt need to be updated here
+
+ self.saveAccounts()
+ if updated: p.scheduleRefresh(user, force=False)
+
+
+ @lock
+ def removeAccount(self, plugin, user):
+ """remove account"""
+
+ if plugin in self.accounts:
+ p = self.getAccountPlugin(plugin)
+ p.removeAccount(user)
+
+ self.saveAccounts()
+
+
+ @lock
+ def getAccountInfos(self, force=True, refresh=False):
+ data = {}
+
+ if refresh:
+ self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
+ force = False
+
+ for p in self.accounts.keys():
+ if self.accounts[p]:
+ p = self.getAccountPlugin(p)
+ if p:
+ data[p.__class__.__name__] = p.getAllAccounts(force)
+ else: #@NOTE: When an account has been skipped, p is None
+ data[p] = []
+ else:
+ data[p] = []
+ e = AccountUpdateEvent()
+ self.core.pullManager.addEvent(e)
+ return data
+
+
+ def sendChange(self):
+ e = AccountUpdateEvent()
+ self.core.pullManager.addEvent(e)
diff --git a/pyload/manager/Addon.py b/pyload/manager/Addon.py
new file mode 100644
index 000000000..2a3bc4318
--- /dev/null
+++ b/pyload/manager/Addon.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+# @interface-version: 0.1
+
+import __builtin__
+
+import traceback
+from threading import RLock, Thread
+
+from types import MethodType
+
+from pyload.manager.thread.Addon import AddonThread
+from pyload.manager.Plugin import literal_eval
+from pyload.utils import lock
+
+
+class AddonManager(object):
+ """Manages addons, delegates and handles Events.
+
+ Every plugin can define events, \
+ but some very usefull events are called by the Core.
+ Contrary to overwriting addon methods you can use event listener,
+ which provides additional entry point in the control flow.
+ Only do very short tasks or use threads.
+
+ **Known Events:**
+ Most addon methods exists as events. These are the additional known events.
+
+ ======================= ============== ==================================
+ Name Arguments Description
+ ======================= ============== ==================================
+ download-preparing fid A download was just queued and will be prepared now.
+ download-start fid A plugin will immediately starts the download afterwards.
+ links-added links, pid Someone just added links, you are able to modify the links.
+ all_downloads-processed Every link was handled, pyload would idle afterwards.
+ all_downloads-finished Every download in queue is finished.
+ config-changed The config was changed via the api.
+ pluginConfigChanged The plugin config changed, due to api or internal process.
+ ======================= ============== ==================================
+
+ | Notes:
+ | all_downloads-processed is *always* called before all_downloads-finished.
+ | config-changed is *always* called before pluginConfigChanged.
+ """
+
+ def __init__(self, core):
+ self.core = core
+
+ __builtin__.addonManager = self #: needed to let addons register themself
+
+ self.plugins = []
+ self.pluginMap = {}
+ self.methods = {} #: dict of names and list of methods usable by rpc
+
+ self.events = {} #: contains events
+
+ # registering callback for config event
+ self.core.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring) #@TODO: Rename event pluginConfigChanged
+
+ self.addEvent("pluginConfigChanged", self.manageAddon)
+
+ self.lock = RLock()
+ self.createIndex()
+
+
+ def try_catch(func):
+
+
+ def new(*args):
+ try:
+ return func(*args)
+ except Exception, e:
+ args[0].core.log.error(_("Error executing addon: %s") % e)
+ if args[0].core.debug:
+ traceback.print_exc()
+
+ return new
+
+
+ def addRPC(self, plugin, func, doc):
+ plugin = plugin.rpartition(".")[2]
+ doc = doc.strip() if doc else ""
+
+ if plugin in self.methods:
+ self.methods[plugin][func] = doc
+ else:
+ self.methods[plugin] = {func: doc}
+
+
+ def callRPC(self, plugin, func, args, parse):
+ if not args:
+ args = ()
+ if parse:
+ args = tuple([literal_eval(x) for x in args])
+ plugin = self.pluginMap[plugin]
+ f = getattr(plugin, func)
+ return f(*args)
+
+
+ def createIndex(self):
+ plugins = []
+
+ for type in ("addon", "hook"):
+ active = []
+ deactive = []
+ for pluginname in getattr(self.core.pluginManager, "%sPlugins" % type):
+ try:
+ if self.core.config.getPlugin("%s_%s" % (pluginname, type), "activated"):
+ pluginClass = self.core.pluginManager.loadClass(type, pluginname)
+ if not pluginClass:
+ continue
+
+ plugin = pluginClass(self.core, self)
+ plugins.append(plugin)
+ self.pluginMap[pluginClass.__name__] = plugin
+ if plugin.isActivated():
+ active.append(pluginClass.__name__)
+ else:
+ deactive.append(pluginname)
+
+ except Exception:
+ self.core.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.core.log.info(_("Activated %ss: %s") % (type, ", ".join(sorted(active))))
+ self.core.log.info(_("Deactivated %ss: %s") % (type, ", ".join(sorted(deactive))))
+
+ self.plugins = plugins
+
+
+ def manageAddon(self, plugin, name, value):
+ if name == "activated" and value:
+ self.activateAddon(plugin)
+
+ elif name == "activated" and not value:
+ self.deactivateAddon(plugin)
+
+
+ def activateAddon(self, pluginname):
+ # check if already loaded
+ for inst in self.plugins:
+ if inst.__class__.__name__ == pluginname:
+ return
+
+ pluginClass = self.core.pluginManager.loadClass("addon", pluginname)
+
+ if not pluginClass:
+ return
+
+ self.core.log.debug("Activate addon: %s" % pluginname)
+
+ addon = pluginClass(self.core, self)
+ self.plugins.append(addon)
+ self.pluginMap[pluginClass.__name__] = addon
+
+ addon.activate()
+
+
+ def deactivateAddon(self, pluginname):
+ for plugin in self.plugins:
+ if plugin.__class__.__name__ == pluginname:
+ addon = plugin
+ break
+ else:
+ return
+
+ self.core.log.debug("Deactivate addon: %s" % pluginname)
+
+ addon.deactivate()
+
+ # remove periodic call
+ self.core.log.debug("Removed callback: %s" % self.core.scheduler.removeJob(addon.cb))
+
+ self.plugins.remove(addon)
+ del self.pluginMap[addon.__class__.__name__]
+
+
+ @try_catch
+ def coreReady(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.activate()
+
+ self.dispatchEvent("addon-start")
+
+
+ @try_catch
+ def coreExiting(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.exit()
+
+ self.dispatchEvent("addon-exit")
+
+
+ @lock
+ def downloadPreparing(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadPreparing(pyfile)
+
+ self.dispatchEvent("download-preparing", pyfile)
+
+
+ @lock
+ def downloadFinished(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFinished(pyfile)
+
+ self.dispatchEvent("download-finished", pyfile)
+
+
+ @lock
+ @try_catch
+ def downloadFailed(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFailed(pyfile)
+
+ self.dispatchEvent("download-failed", pyfile)
+
+
+ @lock
+ def packageFinished(self, package):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.packageFinished(package)
+
+ self.dispatchEvent("package-finished", package)
+
+
+ @lock
+ def beforeReconnecting(self, ip):
+ for plugin in self.plugins:
+ plugin.beforeReconnecting(ip)
+
+ self.dispatchEvent("beforeReconnecting", ip)
+
+
+ @lock
+ def afterReconnecting(self, ip):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.afterReconnecting(ip)
+
+ self.dispatchEvent("afterReconnecting", ip)
+
+
+ def startThread(self, function, *args, **kwargs):
+ return AddonThread(self.core.threadManager, function, args, kwargs)
+
+
+ def activePlugins(self):
+ """ returns all active plugins """
+ return [x for x in self.plugins if x.isActivated()]
+
+
+ def getAllInfo(self):
+ """returns info stored by addon plugins"""
+ info = {}
+ for name, plugin in self.pluginMap.iteritems():
+ if plugin.info:
+ # copy and convert so str
+ info[name] = dict(
+ [(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()])
+ return info
+
+
+ def getInfo(self, plugin):
+ info = {}
+ if plugin in self.pluginMap and self.pluginMap[plugin].info:
+ info = dict((x, str(y) if not isinstance(y, basestring) else y)
+ for x, y in self.pluginMap[plugin].info.iteritems())
+ return info
+
+
+ def addEvent(self, event, func):
+ """Adds an event listener for event name"""
+ if event in self.events:
+ self.events[event].append(func)
+ else:
+ self.events[event] = [func]
+
+
+ def removeEvent(self, event, func):
+ """removes previously added event listener"""
+ if event in self.events:
+ self.events[event].remove(func)
+
+
+ def dispatchEvent(self, event, *args):
+ """dispatches event with args"""
+ if event in self.events:
+ for f in self.events[event]:
+ try:
+ f(*args)
+ except Exception, e:
+ self.core.log.warning("Error calling event handler %s: %s, %s, %s"
+ % (event, f, args, str(e)))
+ if self.core.debug:
+ traceback.print_exc()
diff --git a/pyload/manager/Captcha.py b/pyload/manager/Captcha.py
new file mode 100644
index 000000000..ab9f79b37
--- /dev/null
+++ b/pyload/manager/Captcha.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from time import time
+from traceback import print_exc
+from threading import Lock
+
+from pyload.utils import encode
+
+
+class CaptchaManager(object):
+
+ def __init__(self, core):
+ self.lock = Lock()
+ self.core = core
+ self.tasks = [] #: task store, for outgoing tasks only
+ self.ids = 0 #: only for internal purpose
+
+
+ def newTask(self, img, format, file, result_type):
+ task = CaptchaTask(self.ids, img, format, file, result_type)
+ self.ids += 1
+ return task
+
+
+ def removeTask(self, task):
+ self.lock.acquire()
+ if task in self.tasks:
+ self.tasks.remove(task)
+ self.lock.release()
+
+
+ def getTask(self):
+ self.lock.acquire()
+ for task in self.tasks:
+ if task.status in ("waiting", "shared-user"):
+ self.lock.release()
+ return task
+ self.lock.release()
+ return None
+
+
+ def getTaskByID(self, tid):
+ self.lock.acquire()
+ for task in self.tasks:
+ if task.id == str(tid): #: task ids are strings
+ self.lock.release()
+ return task
+ self.lock.release()
+ return None
+
+
+ def handleCaptcha(self, task, timeout=50):
+ cli = self.core.isClientConnected()
+
+ if cli: #: client connected -> should solve the captcha
+ task.setWaiting(timeout) #: wait 50 sec for response
+
+ for plugin in self.core.addonManager.activePlugins():
+ try:
+ plugin.captchaTask(task)
+ except Exception:
+ if self.core.debug:
+ print_exc()
+
+ if task.handler or cli: #: the captcha was handled
+ self.tasks.append(task)
+ return True
+ task.error = _("No Client connected for captcha decrypting")
+ return False
+
+
+class CaptchaTask(object):
+
+ def __init__(self, id, img, format, file, result_type='textual'):
+ self.id = str(id)
+ self.captchaImg = img
+ self.captchaFormat = format
+ self.captchaFile = file
+ self.captchaResultType = result_type
+ self.handler = [] #: the hook plugins that will take care of the solution
+ self.result = None
+ self.waitUntil = None
+ self.error = None #: error message
+ self.status = "init"
+ self.data = {} #: handler can store data here
+
+
+ def getCaptcha(self):
+ return self.captchaImg, self.captchaFormat, self.captchaResultType
+
+
+ def setResult(self, text):
+ if self.isTextual():
+ self.result = text
+ if self.isPositional():
+ try:
+ parts = text.split(',')
+ self.result = (int(parts[0]), int(parts[1]))
+ except Exception:
+ self.result = None
+
+
+ def getResult(self):
+ return encode(self.result)
+
+
+ def getStatus(self):
+ return self.status
+
+
+ def setWaiting(self, sec):
+ """ let the captcha wait secs for the solution """
+ self.waitUntil = max(time() + sec, self.waitUntil)
+ self.status = "waiting"
+
+
+ def isWaiting(self):
+ if self.result or self.error or self.timedOut():
+ return False
+ else:
+ return True
+
+
+ def isTextual(self):
+ """ returns if text is written on the captcha """
+ return self.captchaResultType == 'textual'
+
+
+ def isPositional(self):
+ """ returns if user have to click a specific region on the captcha """
+ return self.captchaResultType == 'positional'
+
+
+ def setWatingForUser(self, exclusive):
+ if exclusive:
+ self.status = "user"
+ else:
+ self.status = "shared-user"
+
+
+ def timedOut(self):
+ return time() > self.waitUntil
+
+
+ def invalid(self):
+ """ indicates the captcha was not correct """
+ for x in self.handler:
+ x.captchaInvalid(self)
+
+
+ def correct(self):
+ for x in self.handler:
+ x.captchaCorrect(self)
+
+
+ def __str__(self):
+ return "<CaptchaTask '%s'>" % self.id
diff --git a/pyload/manager/Event.py b/pyload/manager/Event.py
new file mode 100644
index 000000000..b3d22619f
--- /dev/null
+++ b/pyload/manager/Event.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from time import time
+from pyload.utils import uniqify
+
+
+class PullManager(object):
+
+ def __init__(self, core):
+ self.core = core
+ self.clients = []
+
+
+ def newClient(self, uuid):
+ self.clients.append(Client(uuid))
+
+
+ def clean(self):
+ for n, client in enumerate(self.clients):
+ if client.lastActive + 30 < time():
+ del self.clients[n]
+
+
+ def getEvents(self, uuid):
+ events = []
+ validUuid = False
+ for client in self.clients:
+ if client.uuid == uuid:
+ client.lastActive = time()
+ validUuid = True
+ while client.newEvents():
+ events.append(client.popEvent().toList())
+ break
+ if not validUuid:
+ self.newClient(uuid)
+ events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()]
+ return uniqify(events)
+
+
+ def addEvent(self, event):
+ for client in self.clients:
+ client.addEvent(event)
+
+
+class Client(object):
+
+ def __init__(self, uuid):
+ self.uuid = uuid
+ self.lastActive = time()
+ self.events = []
+
+
+ def newEvents(self):
+ return len(self.events) > 0
+
+
+ def popEvent(self):
+ if not len(self.events):
+ return None
+ return self.events.pop(0)
+
+
+ def addEvent(self, event):
+ self.events.append(event)
+
+
+class UpdateEvent(object):
+
+ def __init__(self, itype, iid, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.destination = destination
+
+
+ def toList(self):
+ return ["update", self.destination, self.type, self.id]
+
+
+class RemoveEvent(object):
+
+ def __init__(self, itype, iid, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.destination = destination
+
+
+ def toList(self):
+ return ["remove", self.destination, self.type, self.id]
+
+
+class InsertEvent(object):
+
+ def __init__(self, itype, iid, after, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.after = after
+ self.destination = destination
+
+
+ def toList(self):
+ return ["insert", self.destination, self.type, self.id, self.after]
+
+
+class ReloadAllEvent(object):
+
+ def __init__(self, destination):
+ assert destination == "queue" or destination == "collector"
+ self.destination = destination
+
+
+ def toList(self):
+ return ["reload", self.destination]
+
+
+class AccountUpdateEvent(object):
+
+ def toList(self):
+ return ["account"]
+
+
+class ConfigUpdateEvent(object):
+
+ def toList(self):
+ return ["config"]
diff --git a/pyload/manager/Plugin.py b/pyload/manager/Plugin.py
new file mode 100644
index 000000000..c6ba5e81b
--- /dev/null
+++ b/pyload/manager/Plugin.py
@@ -0,0 +1,404 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+import sys
+
+from itertools import chain
+from os import listdir, makedirs
+from os.path import isdir, isfile, join, exists, abspath
+from sys import version_info
+from traceback import print_exc
+from urllib import unquote
+
+from SafeEval import const_eval as literal_eval
+
+
+class PluginManager(object):
+ ROOT = "pyload.plugin."
+ USERROOT = "userplugins."
+ TYPES = ["account", "addon", "container", "crypter", "extractor", "hook", "hoster", "internal", "ocr"]
+
+ PATTERN = re.compile(r'__pattern\s*=\s*u?r("|\')([^"\']+)')
+ VERSION = re.compile(r'__version\s*=\s*("|\')([\d.]+)')
+ CONFIG = re.compile(r'__config\s*=\s*\[([^\]]+)', re.M)
+ DESC = re.compile(r'__description\s*=\s*("|"""|\')([^"\']+)')
+
+
+ def __init__(self, core):
+ self.core = core
+
+ self.plugins = {}
+ self.createIndex()
+
+ # register for import addon
+ sys.meta_path.append(self)
+
+
+ def loadTypes(self):
+ rootdir = join(pypath, "pyload", "plugin")
+ userdir = "userplugins"
+
+ types = set().union(*[[d for d in listdir(p) if isdir(join(p, d))]
+ for p in (rootdir, userdir) if exists(p)])
+
+ if not types:
+ self.core.log.critical(_("No plugins found!"))
+
+ self.TYPES = list(set(self.TYPES) | types)
+
+
+ def createIndex(self):
+ """create information for all plugins available"""
+
+ sys.path.append(abspath(""))
+
+ self.loadTypes()
+
+ configs = []
+
+ for type in self.TYPES:
+ self.plugins[type] = self.parse(type)
+ setattr(self, "%sPlugins" % type, self.plugins[type])
+ configs.extend("%s_%s" % (p, type) for p in self.plugins[type])
+
+ self.core.config.removeDeletedPlugins(configs)
+
+ self.core.log.debug("Created index of plugins")
+
+
+ def parse(self, folder, rootplugins={}):
+ """
+ returns dict with information
+ home contains parsed plugins from pyload.
+ """
+
+ plugins = {}
+
+ if rootplugins:
+ try:
+ pfolder = join("userplugins", folder)
+ if not exists(pfolder):
+ makedirs(pfolder)
+
+ for ifile in (join("userplugins", "__init__.py"),
+ join(pfolder, "__init__.py")):
+ if not exists(ifile):
+ f = open(ifile, "wb")
+ f.close()
+
+ except IOError, e:
+ self.core.log.critical(str(e))
+ return rootplugins
+
+ else:
+ pfolder = join(pypath, "pyload", "plugin", folder)
+
+ for f in listdir(pfolder):
+ if isfile(join(pfolder, f)) and f.endswith(".py") and not f.startswith("_"):
+
+ try:
+ with open(join(pfolder, f)) as data:
+ content = data.read()
+
+ except IOError, e:
+ self.core.log.error(str(e))
+ continue
+
+ name = f[:-3]
+ if name[-1] == ".":
+ name = name[:-4]
+
+ if not re.search("class\\s+%s\\(" % name, content):
+ self.core.log.error(_("invalid classname: %s ignored") % join(pfolder, f))
+
+ version = self.VERSION.findall(content)
+ if version:
+ version = float(version[0][1])
+ else:
+ version = 0
+
+ if rootplugins and name in rootplugins:
+ if rootplugins[name]['version'] >= version:
+ continue
+
+ plugins[name] = {}
+ plugins[name]['version'] = version
+
+ module = f.replace(".pyc", "").replace(".py", "")
+
+ # the plugin is loaded from user directory
+ plugins[name]['user'] = bool(rootplugins)
+ plugins[name]['name'] = module
+
+ pattern = self.PATTERN.findall(content)
+
+ if pattern:
+ pattern = pattern[0][1]
+
+ try:
+ regexp = re.compile(pattern)
+ except Exception:
+ self.core.log.error(_("%s has a invalid pattern") % name)
+ pattern = r'^unmatchable$'
+ regexp = re.compile(pattern)
+
+ plugins[name]['pattern'] = pattern
+ plugins[name]['re'] = regexp
+
+ # internals have no config
+ if folder == "internal":
+ self.core.config.deleteConfig("internal")
+ continue
+
+ config = self.CONFIG.findall(content)
+ if config:
+ try:
+ config = literal_eval(config[0].strip().replace("\n", "").replace("\r", ""))
+ desc = self.DESC.findall(content)
+ desc = desc[0][1] if desc else ""
+
+ if type(config[0]) == tuple:
+ config = [list(x) for x in config]
+ else:
+ config = [list(config)]
+
+ if folder not in ("account", "internal") and not [True for item in config if item[0] == "activated"]:
+ config.insert(0, ["activated", "bool", "Activated", not folder in ("addon", "hook")])
+
+ self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc)
+ except Exception:
+ self.core.log.error("Invalid config in %s: %s" % (name, config))
+
+ elif folder in ("addon", "hook"): #: force config creation
+ desc = self.DESC.findall(content)
+ desc = desc[0][1] if desc else ""
+ config = (["activated", "bool", "Activated", False],)
+
+ try:
+ self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc)
+ except Exception:
+ self.core.log.error("Invalid config in %s: %s" % (name, config))
+
+ if not rootplugins and plugins: #: Double check
+ plugins.update(self.parse(folder, plugins))
+
+ return plugins
+
+
+ def parseUrls(self, urls):
+ """parse plugins for given list of urls"""
+
+ last = None
+ res = [] #: tupels of (url, plugintype, pluginname)
+
+ for url in urls:
+ if type(url) not in (str, unicode, buffer):
+ continue
+
+ url = unquote(url)
+
+ if last and last[2]['re'].match(url):
+ res.append((url, last[0], last[1]))
+ continue
+
+ for plugintype in self.TYPES:
+ m = None
+ for name, plugin in self.plugins[plugintype].iteritems():
+ try:
+ if 'pattern' in plugin:
+ m = plugin['re'].match(url)
+
+ except KeyError:
+ self.core.log.error(_("Plugin [%(type)s] %(name)s skipped due broken pattern")
+ % {'name': plugin['name'], 'type': plugintype})
+
+ if m:
+ res.append((url, plugintype, name))
+ last = (plugintype, name, plugin)
+ break
+ if m:
+ break
+ else:
+ res.append((url, "internal", "BasePlugin"))
+ print res
+ return res
+
+
+ def findPlugin(self, type, name):
+ if isinstance(type, tuple):
+ for typ in type:
+ if name in self.plugins[typ]:
+ return (self.plugins[typ][name], typ)
+
+ if isinstance(type, tuple) or type not in self.plugins or name not in self.plugins[type]:
+ self.core.log.warning(_("Plugin [%(type)s] %(name)s not found | Using plugin: [internal] BasePlugin")
+ % {'name': name, 'type': type})
+ return self.internalPlugins['BasePlugin']
+
+ else:
+ return self.plugins[type][name]
+
+
+ def getPlugin(self, type, name, original=False):
+ """return plugin module from hoster|decrypter|container"""
+ plugin = self.findPlugin(type, name)
+
+ if plugin is None:
+ return {}
+
+ if "new_module" in plugin and not original:
+ return plugin['new_module']
+ else:
+ return self.loadModule(type, name)
+
+
+ def getPluginName(self, type, name):
+ """ used to obtain new name if other plugin was injected"""
+ plugin = self.findPlugin(type, name)
+
+ if plugin is None:
+ return ""
+
+ if "new_name" in plugin:
+ return plugin['new_name']
+
+ return name
+
+
+ def loadModule(self, type, name):
+ """ Returns loaded module for plugin
+
+ :param type: plugin type, subfolder of pyload.plugins
+ :param name:
+ """
+ plugins = self.plugins[type]
+
+ if name in plugins:
+ if "module" in plugins[name]:
+ return plugins[name]['module']
+
+ try:
+ module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]['name']), globals(), locals(),
+ plugins[name]['name'])
+
+ except Exception, e:
+ self.core.log.error(_("Error importing plugin: [%(type)s] %(name)s (v%(version).2f) | %(errmsg)s")
+ % {'name': name, 'type': type, 'version': plugins[name]['version'], "errmsg": str(e)})
+ if self.core.debug:
+ print_exc()
+
+ else:
+ plugins[name]['module'] = module # : cache import, maybe unneeded
+
+ self.core.log.debug(_("Loaded plugin: [%(type)s] %(name)s (v%(version).2f)")
+ % {'name': name, 'type': type, 'version': plugins[name]['version']})
+ return module
+
+
+ def loadClass(self, type, name):
+ """Returns the class of a plugin with the same name"""
+ module = self.loadModule(type, name)
+ if module:
+ return getattr(module, name)
+ else:
+ return None
+
+
+ def getAccountPlugins(self):
+ """return list of account plugin names"""
+ return self.accountPlugins.keys()
+
+
+ def find_module(self, fullname, path=None):
+ # redirecting imports if necesarry
+ if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #: seperate pyload plugins
+ if fullname.startswith(self.USERROOT): user = 1
+ else: user = 0 #: used as bool and int
+
+ split = fullname.split(".")
+ if len(split) != 4 - user:
+ return
+ type, name = split[2 - user:4 - user]
+
+ if type in self.plugins and name in self.plugins[type]:
+ # userplugin is a newer version
+ if not user and self.plugins[type][name]['user']:
+ return self
+ # imported from userdir, but pyloads is newer
+ if user and not self.plugins[type][name]['user']:
+ return self
+
+
+ def load_module(self, name, replace=True):
+ if name not in sys.modules: #: could be already in modules
+ if replace:
+ if self.ROOT in name:
+ newname = name.replace(self.ROOT, self.USERROOT)
+ else:
+ newname = name.replace(self.USERROOT, self.ROOT)
+ else:
+ newname = name
+
+ base, plugin = newname.rsplit(".", 1)
+
+ self.core.log.debug("Redirected import %s -> %s" % (name, newname))
+
+ module = __import__(newname, globals(), locals(), [plugin])
+ # inject under new an old name
+ sys.modules[name] = module
+ sys.modules[newname] = module
+
+ return sys.modules[name]
+
+
+ def reloadPlugins(self, type_plugins):
+ """ reload and reindex plugins """
+ if not type_plugins:
+ return None
+
+ self.core.log.debug("Request reload of plugins: %s" % type_plugins)
+
+ reloaded = []
+
+ as_dict = {}
+ for t, n in type_plugins:
+ if t in as_dict:
+ as_dict[t].append(n)
+ else:
+ as_dict[t] = [n]
+
+ for type in as_dict.iterkeys():
+ if type in ("addon", "internal"): # : do not reload them because would cause to much side effects
+ self.core.log.debug("Skipping reload for plugins from type: %(type)s" % {'type': type})
+ continue
+
+ for plugin in as_dict[type]:
+ if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]:
+ self.core.log.debug(_("Reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type})
+
+ try:
+ reload(self.plugins[type][plugin]['module'])
+
+ except Exception, e:
+ self.core.log.error(_("Error when reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}, e)
+ continue
+
+ else:
+ reloaded.append((type, plugin))
+
+ # index creation
+ self.plugins[type] = self.parse(type)
+ setattr(self, "%sPlugins" % type, self.plugins[type])
+
+ if "account" in as_dict: #: accounts needs to be reloaded
+ self.core.accountManager.initPlugins()
+ self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
+
+ return reloaded #: return a list of the plugins successfully reloaded
+
+
+ def reloadPlugin(self, type_plugin):
+ """ reload and reindex ONE plugin """
+ return bool(self.reloadPlugins(type_plugin))
diff --git a/pyload/manager/Remote.py b/pyload/manager/Remote.py
new file mode 100644
index 000000000..c2d254932
--- /dev/null
+++ b/pyload/manager/Remote.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from threading import Thread
+from traceback import print_exc
+
+
+class BackendBase(Thread):
+
+ def __init__(self, manager):
+ Thread.__init__(self)
+ self.m = manager
+ self.core = manager.core
+ self.enabled = True
+ self.running = False
+
+
+ def run(self):
+ self.running = True
+ try:
+ self.serve()
+ except Exception, e:
+ self.core.log.error(_("Remote backend error: %s") % e)
+ if self.core.debug:
+ print_exc()
+ finally:
+ self.running = False
+
+
+ def setup(self, host, port):
+ pass
+
+
+ def checkDeps(self):
+ return True
+
+
+ def serve(self):
+ pass
+
+
+ def shutdown(self):
+ pass
+
+
+ def stop(self):
+ self.enabled = False #: set flag and call shutdowm message, so thread can react
+ self.shutdown()
+
+
+class RemoteManager(object):
+ available = []
+
+
+ def __init__(self, core):
+ self.core = core
+ self.backends = []
+
+ if self.core.remote:
+ self.available.append("ThriftBackend")
+ # else:
+ # self.available.append("SocketBackend")
+
+
+ def startBackends(self):
+ host = self.core.config.get("remote", "listenaddr")
+ port = self.core.config.get("remote", "port")
+
+ for b in self.available:
+ klass = getattr(__import__("pyload.remote.%s" % b, globals(), locals(), [b], -1), b)
+ backend = klass(self)
+ if not backend.checkDeps():
+ continue
+ try:
+ backend.setup(host, port)
+ self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port})
+ except Exception, e:
+ self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)})
+ if self.core.debug:
+ print_exc()
+ else:
+ backend.start()
+ self.backends.append(backend)
+
+ port += 1
diff --git a/pyload/manager/Thread.py b/pyload/manager/Thread.py
new file mode 100644
index 000000000..a2a64c38d
--- /dev/null
+++ b/pyload/manager/Thread.py
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from os.path import exists, join
+import re
+from subprocess import Popen
+from threading import Event, Lock
+from time import sleep, time
+from traceback import print_exc
+from random import choice
+
+import pycurl
+
+from pyload.manager.thread.Decrypter import DecrypterThread
+from pyload.manager.thread.Download import DownloadThread
+from pyload.manager.thread.Info import InfoThread
+from pyload.datatype.File import PyFile
+from pyload.network.RequestFactory import getURL
+from pyload.utils import freeSpace, lock
+
+
+class ThreadManager(object):
+ """manages the download threads, assign jobs, reconnect etc"""
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+
+ self.threads = [] #: thread list
+ self.localThreads = [] #: addon+decrypter threads
+
+ self.pause = True
+
+ self.reconnecting = Event()
+ self.reconnecting.clear()
+ self.downloaded = 0 #: number of files downloaded since last cleanup
+
+ self.lock = Lock()
+
+ # some operations require to fetch url info from hoster, so we caching them so it wont be done twice
+ # contains a timestamp and will be purged after timeout
+ self.infoCache = {}
+
+ # pool of ids for online check
+ self.resultIDs = 0
+
+ # threads which are fetching hoster results
+ self.infoResults = {}
+ # timeout for cache purge
+ self.timestamp = 0
+
+ pycurl.global_init(pycurl.GLOBAL_DEFAULT)
+
+ for _i in xrange(0, self.core.config.get("download", "max_downloads")):
+ self.createThread()
+
+
+ def createThread(self):
+ """create a download thread"""
+
+ thread = DownloadThread(self)
+ self.threads.append(thread)
+
+
+ def createInfoThread(self, data, pid):
+ """
+ start a thread whichs fetches online status and other infos
+ data = [ .. () .. ]
+ """
+ self.timestamp = time() + 5 * 60
+
+ InfoThread(self, data, pid)
+
+
+ @lock
+ def createResultThread(self, data, add=False):
+ """ creates a thread to fetch online status, returns result id """
+ self.timestamp = time() + 5 * 60
+
+ rid = self.resultIDs
+ self.resultIDs += 1
+
+ InfoThread(self, data, rid=rid, add=add)
+
+ return rid
+
+
+ @lock
+ def getInfoResult(self, rid):
+ """returns result and clears it"""
+ self.timestamp = time() + 5 * 60
+
+ if rid in self.infoResults:
+ data = self.infoResults[rid]
+ self.infoResults[rid] = {}
+ return data
+ else:
+ return {}
+
+
+ @lock
+ def setInfoResults(self, rid, result):
+ self.infoResults[rid].update(result)
+
+
+ def getActiveFiles(self):
+ active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)]
+
+ for t in self.localThreads:
+ active.extend(t.getActiveFiles())
+
+ return active
+
+
+ def processingIds(self):
+ """get a id list of all pyfiles processed"""
+ return [x.id for x in self.getActiveFiles()]
+
+
+ def work(self):
+ """run all task which have to be done (this is for repetivive call by core)"""
+ try:
+ self.tryReconnect()
+ except Exception, e:
+ self.core.log.error(_("Reconnect Failed: %s") % str(e))
+ self.reconnecting.clear()
+ if self.core.debug:
+ print_exc()
+ self.checkThreadCount()
+
+ try:
+ self.assignJob()
+ except Exception, e:
+ self.core.log.warning("Assign job error", e)
+ if self.core.debug:
+ print_exc()
+
+ sleep(0.5)
+ self.assignJob()
+ # it may be failed non critical so we try it again
+
+ if (self.infoCache or self.infoResults) and self.timestamp < time():
+ self.infoCache.clear()
+ self.infoResults.clear()
+ self.core.log.debug("Cleared Result cache")
+
+
+ #--------------------------------------------------------------------------
+
+ def tryReconnect(self):
+ """checks if reconnect needed"""
+
+ if not (self.core.config.get("reconnect", "activated") and self.core.api.isTimeReconnect()):
+ return False
+
+ active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active]
+
+ if not (0 < active.count(True) == len(active)):
+ return False
+
+ if not exists(self.core.config.get("reconnect", "method")):
+ if exists(join(pypath, self.core.config.get("reconnect", "method"))):
+ self.core.config.set("reconnect", "method", join(pypath, self.core.config.get("reconnect", "method")))
+ else:
+ self.core.config.set("reconnect", "activated", False)
+ self.core.log.warning(_("Reconnect script not found!"))
+ return
+
+ self.reconnecting.set()
+
+ # Do reconnect
+ self.core.log.info(_("Starting reconnect"))
+
+ while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0:
+ sleep(0.25)
+
+ ip = self.getIP()
+
+ self.core.addonManager.beforeReconnecting(ip)
+
+ self.core.log.debug("Old IP: %s" % ip)
+
+ try:
+ reconn = Popen(self.core.config.get("reconnect", "method"), bufsize=-1, shell=True) # , stdout=subprocess.PIPE)
+ except Exception:
+ self.core.log.warning(_("Failed executing reconnect script!"))
+ self.core.config.set("reconnect", "activated", False)
+ self.reconnecting.clear()
+ if self.core.debug:
+ print_exc()
+ return
+
+ reconn.wait()
+ sleep(1)
+ ip = self.getIP()
+ self.core.addonManager.afterReconnecting(ip)
+
+ self.core.log.info(_("Reconnected, new IP: %s") % ip)
+
+ self.reconnecting.clear()
+
+
+ def getIP(self):
+ """retrieve current ip"""
+ services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"),
+ ("http://checkip.dyndns.org/", ".*Current IP Address: (\S+)</body>.*")]
+
+ ip = ""
+ for _i in xrange(10):
+ try:
+ sv = choice(services)
+ ip = getURL(sv[0])
+ ip = re.match(sv[1], ip).group(1)
+ break
+ except Exception:
+ ip = ""
+ sleep(1)
+
+ return ip
+
+
+ #--------------------------------------------------------------------------
+
+ def checkThreadCount(self):
+ """checks if there are need for increasing or reducing thread count"""
+
+ if len(self.threads) == self.core.config.get("download", "max_downloads"):
+ return True
+ elif len(self.threads) < self.core.config.get("download", "max_downloads"):
+ self.createThread()
+ else:
+ free = [x for x in self.threads if not x.active]
+ if free:
+ free[0].put("quit")
+
+
+ def cleanPycurl(self):
+ """ make a global curl cleanup (currently ununused) """
+ if self.processingIds():
+ return False
+ pycurl.global_cleanup()
+ pycurl.global_init(pycurl.GLOBAL_DEFAULT)
+ self.downloaded = 0
+ self.core.log.debug("Cleaned up pycurl")
+ return True
+
+
+ #--------------------------------------------------------------------------
+
+ def assignJob(self):
+ """assing a job to a thread if possible"""
+
+ if self.pause or not self.core.api.isTimeDownload():
+ return
+
+ # if self.downloaded > 20:
+ # if not self.cleanPyCurl():
+ return
+
+ free = [x for x in self.threads if not x.active]
+
+ inuse = set([((x.active.plugintype, x.active.pluginname), self.getLimit(x)) for x in self.threads if x.active and isinstance(x.active, PyFile) and x.active.hasPlugin() and x.active.plugin.account])
+ inuse = map(lambda x: ('.'.join(x[0]), x[1], len([y for y in self.threads if y.active and isinstance(y.active, PyFile) and y.active.plugintype == x[0][0] and y.active.pluginname == x[0][1]])), inuse)
+ onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]]
+
+ occ = [x.active.plugintype + '.' + x.active.pluginname for x in self.threads if x.active and isinstance(x.active, PyFile) and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit
+
+ occ.sort()
+ occ = tuple(set(occ))
+ job = self.core.files.getJob(occ)
+ if job:
+ try:
+ job.initPlugin()
+ except Exception, e:
+ self.core.log.critical(str(e))
+ print_exc()
+ job.setStatus("failed")
+ job.error = str(e)
+ job.release()
+ return
+
+ if job.plugin.getPluginType() == "hoster":
+ spaceLeft = freeSpace(self.core.config.get("general", "download_folder")) / 1024 / 1024
+ if spaceLeft < self.core.config.get("general", "min_free_space"):
+ self.core.log.warning(_("Not enough space left on device"))
+ self.pause = True
+
+ if free and not self.pause:
+ thread = free[0]
+ # self.downloaded += 1
+
+ thread.put(job)
+ else:
+ # put job back
+ if occ not in self.core.files.jobCache:
+ self.core.files.jobCache[occ] = []
+ self.core.files.jobCache[occ].append(job.id)
+
+ # check for decrypt jobs
+ job = self.core.files.getDecryptJob()
+ if job:
+ job.initPlugin()
+ thread = DecrypterThread(self, job)
+ else:
+ thread = DecrypterThread(self, job)
+
+
+ def getLimit(self, thread):
+ limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL", ["0"])[0]
+ return int(limit)
+
+
+ def cleanup(self):
+ """do global cleanup, should be called when finished with pycurl"""
+ pycurl.global_cleanup()
diff --git a/pyload/manager/__init__.py b/pyload/manager/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/manager/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/manager/event/Scheduler.py b/pyload/manager/event/Scheduler.py
new file mode 100644
index 000000000..d67d9063a
--- /dev/null
+++ b/pyload/manager/event/Scheduler.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from time import time
+from heapq import heappop, heappush
+from threading import Lock, Thread
+
+
+class AlreadyCalled(Exception):
+ pass
+
+
+class Deferred(object):
+
+ def __init__(self):
+ self.call = []
+ self.result = ()
+
+
+ def addCallback(self, f, *cargs, **ckwargs):
+ self.call.append((f, cargs, ckwargs))
+
+
+ def callback(self, *args, **kwargs):
+ if self.result:
+ raise AlreadyCalled
+ self.result = (args, kwargs)
+ for f, cargs, ckwargs in self.call:
+ args += tuple(cargs)
+ kwargs.update(ckwargs)
+ f(*args ** kwargs)
+
+
+class Scheduler(object):
+
+ def __init__(self, core):
+ self.core = core
+
+ self.queue = PriorityQueue()
+
+
+ def addJob(self, t, call, args=[], kwargs={}, threaded=True):
+ d = Deferred()
+ t += time()
+ j = Job(t, call, args, kwargs, d, threaded)
+ self.queue.put((t, j))
+ return d
+
+
+ def removeJob(self, d):
+ """
+ :param d: defered object
+ :return: if job was deleted
+ """
+ index = -1
+
+ for i, j in enumerate(self.queue):
+ if j[1].deferred == d:
+ index = i
+
+ if index >= 0:
+ del self.queue[index]
+ return True
+
+ return False
+
+
+ def work(self):
+ while True:
+ t, j = self.queue.get()
+ if not j:
+ break
+ else:
+ if t <= time():
+ j.start()
+ else:
+ self.queue.put((t, j))
+ break
+
+
+class Job(object):
+
+ def __init__(self, time, call, args=[], kwargs={}, deferred=None, threaded=True):
+ self.time = float(time)
+ self.call = call
+ self.args = args
+ self.kwargs = kwargs
+ self.deferred = deferred
+ self.threaded = threaded
+
+
+ def run(self):
+ ret = self.call(*self.args, **self.kwargs)
+ if self.deferred is None:
+ return
+ else:
+ self.deferred.callback(ret)
+
+
+ def start(self):
+ if self.threaded:
+ t = Thread(target=self.run)
+ t.setDaemon(True)
+ t.start()
+ else:
+ self.run()
+
+
+class PriorityQueue(object):
+ """ a non blocking priority queue """
+
+ def __init__(self):
+ self.queue = []
+ self.lock = Lock()
+
+
+ def __iter__(self):
+ return iter(self.queue)
+
+
+ def __delitem__(self, key):
+ del self.queue[key]
+
+
+ def put(self, element):
+ self.lock.acquire()
+ heappush(self.queue, element)
+ self.lock.release()
+
+
+ def get(self):
+ """ return element or None """
+ self.lock.acquire()
+ try:
+ el = heappop(self.queue)
+ return el
+ except IndexError:
+ return None, None
+ finally:
+ self.lock.release()
diff --git a/pyload/manager/event/__init__.py b/pyload/manager/event/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/manager/event/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/manager/thread/Addon.py b/pyload/manager/thread/Addon.py
new file mode 100644
index 000000000..b176e4e0c
--- /dev/null
+++ b/pyload/manager/thread/Addon.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.manager.thread.Plugin import PluginThread
+
+
+class AddonThread(PluginThread):
+ """thread for addons"""
+
+ def __init__(self, m, function, args, kwargs):
+ """Constructor"""
+ PluginThread.__init__(self, m)
+
+ self.f = function
+ self.args = args
+ self.kwargs = kwargs
+
+ self.active = []
+
+ m.localThreads.append(self)
+
+ self.start()
+
+
+ def getActiveFiles(self):
+ return self.active
+
+
+ def addActive(self, pyfile):
+ """ Adds a pyfile to active list and thus will be displayed on overview"""
+ if pyfile not in self.active:
+ self.active.append(pyfile)
+
+
+ def finishFile(self, pyfile):
+ if pyfile in self.active:
+ self.active.remove(pyfile)
+
+ pyfile.finishIfDone()
+
+
+ def run(self):
+ try:
+ try:
+ self.kwargs['thread'] = self
+ self.f(*self.args, **self.kwargs)
+ except TypeError, e:
+ # dirty method to filter out exceptions
+ if "unexpected keyword argument 'thread'" not in e.args[0]:
+ raise
+
+ del self.kwargs['thread']
+ self.f(*self.args, **self.kwargs)
+ finally:
+ local = copy(self.active)
+ for x in local:
+ self.finishFile(x)
+
+ self.m.localThreads.remove(self)
diff --git a/pyload/manager/thread/Decrypter.py b/pyload/manager/thread/Decrypter.py
new file mode 100644
index 000000000..308e94f10
--- /dev/null
+++ b/pyload/manager/thread/Decrypter.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.manager.thread.Plugin import PluginThread
+from pyload.plugin.Plugin import Abort, Fail, Retry
+
+
+class DecrypterThread(PluginThread):
+
+ """thread for decrypting"""
+
+ def __init__(self, manager, pyfile):
+ """constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.active = pyfile
+ manager.localThreads.append(self)
+
+ pyfile.setStatus("decrypting")
+
+ self.start()
+
+
+ def getActiveFiles(self):
+ return [self.active]
+
+
+ def run(self):
+ """run method"""
+
+ pyfile = self.active
+ retry = False
+
+ try:
+ self.m.core.log.info(_("Decrypting starts: %s") % pyfile.name)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ except NotImplementedError:
+ self.m.core.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
+ return
+
+ except Fail, e:
+ msg = e.args[0]
+
+ if msg == "offline":
+ pyfile.setStatus("offline")
+ self.m.core.log.warning(
+ _("Download is offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.core.log.error(
+ _("Decrypting failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ if self.m.core.debug:
+ print_exc()
+ return
+
+ except Abort:
+ self.m.core.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ if self.m.core.debug:
+ print_exc()
+ return
+
+ except Retry:
+ self.m.core.log.info(_("Retrying %s") % pyfile.name)
+ retry = True
+ return self.run()
+
+ except Exception, e:
+ pyfile.setStatus("failed")
+ self.m.core.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {
+ "name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ return
+
+ finally:
+ if not retry:
+ pyfile.release()
+ self.active = False
+ self.m.core.files.save()
+ self.m.localThreads.remove(self)
+ exc_clear()
+
+ if not retry:
+ pyfile.delete()
diff --git a/pyload/manager/thread/Download.py b/pyload/manager/thread/Download.py
new file mode 100644
index 000000000..293014a2e
--- /dev/null
+++ b/pyload/manager/thread/Download.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.manager.thread.Plugin import PluginThread
+from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+
+
+class DownloadThread(PluginThread):
+ """thread for downloading files from 'real' hoster plugins"""
+
+ def __init__(self, manager):
+ """Constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.queue = Queue() #: job queue
+ self.active = False
+
+ self.start()
+
+
+ #--------------------------------------------------------------------------
+
+ def run(self):
+ """run method"""
+ pyfile = None
+
+ while True:
+ del pyfile
+ self.active = False #: sets the thread inactive when it is ready to get next job
+ self.active = self.queue.get()
+ pyfile = self.active
+
+ if self.active == "quit":
+ self.active = False
+ self.m.threads.remove(self)
+ return True
+
+ try:
+ if not pyfile.hasPlugin():
+ continue
+ # this pyfile was deleted while queueing
+
+ pyfile.plugin.checkForSameFiles(starting=True)
+ self.m.core.log.info(_("Download starts: %s" % pyfile.name))
+
+ # start download
+ self.m.core.addonManager.downloadPreparing(pyfile)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ self.m.core.log.info(_("Download finished: %s") % pyfile.name)
+ self.m.core.addonManager.downloadFinished(pyfile)
+ self.m.core.files.checkPackageFinished(pyfile)
+
+ except NotImplementedError:
+ self.m.core.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
+ pyfile.setStatus("failed")
+ pyfile.error = "Plugin does not work"
+ self.clean(pyfile)
+ continue
+
+ except Abort:
+ try:
+ self.m.core.log.info(_("Download aborted: %s") % pyfile.name)
+ except Exception:
+ pass
+
+ pyfile.setStatus("aborted")
+
+ if self.m.core.debug:
+ print_exc()
+
+ self.clean(pyfile)
+ continue
+
+ except Reconnect:
+ self.queue.put(pyfile)
+ # pyfile.req.clearCookies()
+
+ while self.m.reconnecting.isSet():
+ sleep(0.5)
+
+ continue
+
+ except Retry, e:
+ reason = e.args[0]
+ self.m.core.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason})
+ self.queue.put(pyfile)
+ continue
+
+ except Fail, e:
+ msg = e.args[0]
+
+ if msg == "offline":
+ pyfile.setStatus("offline")
+ self.m.core.log.warning(_("Download is offline: %s") % pyfile.name)
+ elif msg == "temp. offline":
+ pyfile.setStatus("temp. offline")
+ self.m.core.log.warning(_("Download is temporary offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ if self.m.core.debug:
+ print_exc()
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ except error, e:
+ if len(e.args) == 2:
+ code, msg = e.args
+ else:
+ code = 0
+ msg = e.args
+
+ self.m.core.log.debug("pycurl exception %s: %s" % (code, msg))
+
+ if code in (7, 18, 28, 52, 56):
+ self.m.core.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry."))
+ wait = time() + 60
+
+ pyfile.waitUntil = wait
+ pyfile.setStatus("waiting")
+ while time() < wait:
+ sleep(1)
+ if pyfile.abort:
+ break
+
+ if pyfile.abort:
+ self.m.core.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ self.clean(pyfile)
+ else:
+ self.queue.put(pyfile)
+
+ continue
+
+ else:
+ pyfile.setStatus("failed")
+ self.m.core.log.error("pycurl error %s: %s" % (code, msg))
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+
+ self.clean(pyfile)
+ continue
+
+ except SkipDownload, e:
+ pyfile.setStatus("skipped")
+
+ self.m.core.log.info(_("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message})
+
+ self.clean(pyfile)
+
+ self.m.core.files.checkPackageFinished(pyfile)
+
+ self.active = False
+ self.m.core.files.save()
+
+ continue
+
+ except Exception, e:
+ pyfile.setStatus("failed")
+ self.m.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ finally:
+ self.m.core.files.save()
+ pyfile.checkIfProcessed()
+ exc_clear()
+
+ # pyfile.plugin.req.clean()
+
+ self.active = False
+ pyfile.finishIfDone()
+ self.m.core.files.save()
+
+
+ def put(self, job):
+ """assing job to thread"""
+ self.queue.put(job)
+
+
+ def stop(self):
+ """stops the thread"""
+ self.put("quit")
diff --git a/pyload/manager/thread/Info.py b/pyload/manager/thread/Info.py
new file mode 100644
index 000000000..9d8a3ef5b
--- /dev/null
+++ b/pyload/manager/thread/Info.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.datatype.File import PyFile
+from pyload.manager.thread.Plugin import PluginThread
+from pyload.api import OnlineStatus
+
+
+class InfoThread(PluginThread):
+
+ def __init__(self, manager, data, pid=-1, rid=-1, add=False):
+ """Constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.data = data
+ self.pid = pid #: package id
+ # [ .. (name, plugin) .. ]
+
+ self.rid = rid #: result id
+ self.add = add #: add packages instead of return result
+
+ self.cache = [] #: accumulated data
+
+ self.start()
+
+
+ def run(self):
+ """run method"""
+
+ plugins = {}
+ container = []
+
+ for url, plugintype, pluginname in self.data:
+ # filter out container plugins
+ if plugintype == 'container':
+ container.appen((pluginname, url))
+ else:
+ if (plugintype, pluginname) in plugins:
+ plugins[(plugintype, pluginname)].append(url)
+ else:
+ plugins[(plugintype, pluginname)] = [url]
+
+ # directly write to database
+ if self.pid > -1:
+ for (plugintype, pluginname), urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
+ self.m.core.files.save()
+
+ elif self.add:
+ for (plugintype, pluginname), urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True)
+
+ else:
+ # generate default result
+ result = [(url, 0, 3, url) for url in urls]
+
+ self.updateCache(pluginname, result)
+
+ packs = parseNames([(name, url) for name, x, y, url in self.cache])
+
+ self.m.log.debug("Fetched and generated %d packages" % len(packs))
+
+ for k, v in packs:
+ self.m.core.api.addPackage(k, v)
+
+ # empty cache
+ del self.cache[:]
+
+ else: #: post the results
+
+ for name, url in container:
+ # attach container content
+ try:
+ data = self.decryptContainer(name, url)
+ except Exception:
+ print_exc()
+ self.m.log.error("Could not decrypt container.")
+ data = []
+
+ for url, plugintype, pluginname in data:
+ try:
+ plugins[plugintype][pluginname].append(url)
+ except Exception:
+ plugins[plugintype][pluginname] = [url]
+
+ self.m.infoResults[self.rid] = {}
+
+ for plugintype, pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True)
+
+ # force to process cache
+ if self.cache:
+ self.updateResult(pluginname, [], True)
+
+ else:
+ # generate default result
+ result = [(url, 0, 3, url) for url in urls]
+
+ self.updateResult(pluginname, result, True)
+
+ self.m.infoResults[self.rid]['ALL_INFO_FETCHED'] = {}
+
+ self.m.timestamp = time() + 5 * 60
+
+
+ def updateDB(self, plugin, result):
+ self.m.core.files.updateFileInfo(result, self.pid)
+
+
+ def updateResult(self, plugin, result, force=False):
+ # parse package name and generate result
+ # accumulate results
+
+ self.cache.extend(result)
+
+ if len(self.cache) >= 20 or force:
+ # used for package generating
+ tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size)))) for name, size, status, url in self.cache]
+
+ data = parseNames(tmp)
+ result = {}
+ for k, v in data.iteritems():
+ for url, status in v:
+ status.packagename = k
+ result[url] = status
+
+ self.m.setInfoResults(self.rid, result)
+
+ self.cache = []
+
+
+ def updateCache(self, plugin, result):
+ self.cache.extend(result)
+
+
+ def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None):
+ try:
+ result = [] #: result loaded from cache
+ process = [] #: urls to process
+ for url in urls:
+ if url in self.m.infoCache:
+ result.append(self.m.infoCache[url])
+ else:
+ process.append(url)
+
+ if result:
+ self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname))
+ cb(pluginname, result)
+
+ if process:
+ self.m.log.debug("Run Info Fetching for %s" % pluginname)
+ for result in plugin.getInfo(process):
+ # result = [ .. (name, size, status, url) .. ]
+ if not type(result) == list:
+ result = [result]
+
+ for res in result:
+ self.m.infoCache[res[3]] = res # : why don't assign res dict directly?
+
+ cb(pluginname, result)
+
+ self.m.log.debug("Finished Info Fetching for %s" % pluginname)
+ except Exception, e:
+ self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") % {"name": pluginname, "err": str(e)})
+ if self.m.core.debug:
+ print_exc()
+
+ # generate default results
+ if err:
+ result = [(url, 0, 3, url) for url in urls]
+ cb(pluginname, result)
+
+
+ def decryptContainer(self, plugin, url):
+ data = []
+ # only works on container plugins
+
+ self.m.log.debug("Pre decrypting %s with %s" % (url, plugin))
+
+ # dummy pyfile
+ pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1)
+
+ pyfile.initPlugin()
+
+ # little plugin lifecycle
+ try:
+ pyfile.plugin.setup()
+ pyfile.plugin.loadToDisk()
+ pyfile.plugin.decrypt(pyfile)
+ pyfile.plugin.deleteTmp()
+
+ for pack in pyfile.plugin.packages:
+ pyfile.plugin.urls.extend(pack[1])
+
+ data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls)
+
+ self.m.log.debug("Got %d links." % len(data))
+
+ except Exception, e:
+ self.m.log.debug("Pre decrypting error: %s" % str(e))
+ finally:
+ pyfile.release()
+
+ return data
diff --git a/pyload/manager/thread/Plugin.py b/pyload/manager/thread/Plugin.py
new file mode 100644
index 000000000..d8319a2ce
--- /dev/null
+++ b/pyload/manager/thread/Plugin.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import fs_join
+from pyload.api import OnlineStatus
+
+
+class PluginThread(Thread):
+ """abstract base class for thread types"""
+
+ def __init__(self, manager):
+ """Constructor"""
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.m = manager #: thread manager
+
+
+ def writeDebugReport(self, pyfile):
+ """ writes a
+ :return:
+ """
+
+ dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S"))
+ dump = self.getDebugDump(pyfile)
+
+ try:
+ import zipfile
+
+ zip = zipfile.ZipFile(dump_name, "w")
+
+ for f in listdir(join("tmp", pyfile.pluginname)):
+ try:
+ # avoid encoding errors
+ zip.write(join("tmp", pyfile.pluginname, f), fs_join(pyfile.pluginname, f))
+ except Exception:
+ pass
+
+ info = zipfile.ZipInfo(fs_join(pyfile.pluginname, "debug_Report.txt"), gmtime())
+ info.external_attr = 0644 << 16L #: change permissions
+
+ zip.writestr(info, dump)
+ zip.close()
+
+ if not stat(dump_name).st_size:
+ raise Exception("Empty Zipfile")
+
+ except Exception, e:
+ self.m.log.debug("Error creating zip file: %s" % e)
+
+ dump_name = dump_name.replace(".zip", ".txt")
+ with open(dump_name, "wb") as f:
+ f.write(dump)
+
+ self.m.core.log.info("Debug Report written to %s" % dump_name)
+
+
+ def getDebugDump(self, pyfile):
+ dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % (
+ self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version, format_exc())
+
+ tb = exc_info()[2]
+ stack = []
+ while tb:
+ stack.append(tb.tb_frame)
+ tb = tb.tb_next
+
+ for frame in stack[1:]:
+ dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name,
+ frame.f_code.co_filename,
+ frame.f_lineno)
+
+ for key, value in frame.f_locals.items():
+ dump += "\t%20s = " % key
+ try:
+ dump += pformat(value) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ del frame
+
+ del stack #: delete it just to be sure...
+
+ dump += "\n\nPLUGIN OBJECT DUMP: \n\n"
+
+ for name in dir(pyfile.plugin):
+ attr = getattr(pyfile.plugin, name)
+ if not name.endswith("__") and type(attr) != MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pformat(attr) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ dump += "\nPYFILE OBJECT DUMP: \n\n"
+
+ for name in dir(pyfile):
+ attr = getattr(pyfile, name)
+ if not name.endswith("__") and type(attr) != MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pformat(attr) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ if pyfile.pluginname in self.m.core.config.plugin:
+ dump += "\n\nCONFIG: \n\n"
+ dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n"
+
+ return dump
+
+
+ def clean(self, pyfile):
+ """ set thread unactive and release pyfile """
+ self.active = True #: release pyfile but lets the thread active
+ pyfile.release()
diff --git a/pyload/manager/thread/Server.py b/pyload/manager/thread/Server.py
new file mode 100644
index 000000000..6eab58ca7
--- /dev/null
+++ b/pyload/manager/thread/Server.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import logging
+import os
+import threading
+
+core = None
+setup = None
+log = logging.getLogger("log")
+
+
+class WebServer(threading.Thread):
+
+ def __init__(self, pycore):
+ global core
+
+ threading.Thread.__init__(self)
+ self.core = pycore
+ core = pycore
+ self.running = True
+ self.server = pycore.config.get("webui", "server")
+ self.https = pycore.config.get("webui", "https")
+ self.cert = pycore.config.get("ssl", "cert")
+ self.key = pycore.config.get("ssl", "key")
+ self.host = pycore.config.get("webui", "host")
+ self.port = pycore.config.get("webui", "port")
+
+ self.setDaemon(True)
+
+
+ def run(self):
+ import pyload.webui as webinterface
+ global webinterface
+
+ reset = False
+
+ if self.https and (not os.exists(self.cert) or not os.exists(self.key)):
+ log.warning(_("SSL certificates not found."))
+ self.https = False
+
+ if self.server in ("lighttpd", "nginx"):
+ log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server)
+ log.warning(_("You can use the threaded server which offers good performance and ssl,"))
+ log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server)
+ log.warning(_("sample configs are located in the pyload/web/servers directory"))
+ reset = True
+ elif self.server == "fastcgi":
+ try:
+ import flup
+ except Exception:
+ log.warning(_("Can't use %(server)s, python-flup is not installed!") % {
+ "server": self.server})
+ reset = True
+
+ if reset or self.server == "lightweight":
+ if os.name != "nt":
+ try:
+ import bjoern
+ except Exception, e:
+ log.error(_("Error importing lightweight server: %s") % e)
+ log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern"))
+ log.warning(_("Copy the boern.so file to lib/Python/Lib or use setup.py install"))
+ log.warning(_("Of course you need to be familiar with linux and know how to compile software"))
+ self.server = "builtin"
+ else:
+ self.core.log.info(_("Server set to threaded, due to known performance problems on windows."))
+ self.core.config.set("webui", "server", "threaded")
+ self.server = "threaded"
+
+ if self.server == "threaded":
+ self.start_threaded()
+ elif self.server == "fastcgi":
+ self.start_fcgi()
+ elif self.server == "lightweight":
+ self.start_lightweight()
+ else:
+ self.start_builtin()
+
+
+ def start_builtin(self):
+ if self.https:
+ log.warning(_("This server offers no SSL, please consider using `threaded` instead"))
+
+ self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ webinterface.run_simple(host=self.host, port=self.port)
+
+
+ def start_threaded(self):
+ if self.https:
+ self.core.log.info(
+ _("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ else:
+ self.cert = ""
+ self.key = ""
+ self.core.log.info(
+ _("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+
+ webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key)
+
+
+ def start_fcgi(self):
+ self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ try:
+ webinterface.run_fcgi(host=self.host, port=self.port)
+
+ except ValueError: #@TODO: Fix https://github.com/pyload/pyload/issues/1145
+ pass
+
+
+ def start_lightweight(self):
+ if self.https:
+ log.warning(_("This server offers no SSL, please consider using `threaded` instead"))
+
+ self.core.log.info(
+ _("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ webinterface.run_lightweight(host=self.host, port=self.port)
+
+
+ def quit(self):
+ self.running = False
diff --git a/pyload/manager/thread/__init__.py b/pyload/manager/thread/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/manager/thread/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py
new file mode 100644
index 000000000..482c2320a
--- /dev/null
+++ b/pyload/network/Browser.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+from logging import getLogger
+
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.network.HTTPDownload import HTTPDownload
+
+
+class Browser(object):
+ __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl")
+
+
+ def __init__(self, bucket=None, options={}):
+ self.log = getLogger("log")
+
+ self.options = options #: holds pycurl options
+ self.bucket = bucket
+
+ self.cj = None #: needs to be setted later
+ self._size = 0
+
+ self.renewHTTPRequest()
+ self.dl = None
+
+
+ def renewHTTPRequest(self):
+ if hasattr(self, "http"):
+ self.http.close()
+ self.http = HTTPRequest(self.cj, self.options)
+
+
+ def setLastURL(self, val):
+ self.http.lastURL = val
+
+ # tunnel some attributes from HTTP Request to Browser
+ lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL)
+ lastURL = property(lambda self: self.http.lastURL, setLastURL)
+ code = property(lambda self: self.http.code)
+ cookieJar = property(lambda self: self.cj)
+
+
+ def setCookieJar(self, cj):
+ self.cj = cj
+ self.http.cj = cj
+
+
+ @property
+ def speed(self):
+ if self.dl:
+ return self.dl.speed
+ return 0
+
+
+ @property
+ def size(self):
+ if self._size:
+ return self._size
+ if self.dl:
+ return self.dl.size
+ return 0
+
+
+ @property
+ def arrived(self):
+ return self.dl.arrived if self.dl else 0
+
+
+ @property
+ def percent(self):
+ return (self.arrived * 100) / self.size if self.size else 0
+
+
+ def clearCookies(self):
+ if self.cj:
+ self.cj.clear()
+ self.http.clearCookies()
+
+
+ def clearReferer(self):
+ self.http.lastURL = None
+
+
+ def abortDownloads(self):
+ self.http.abort = True
+ if self.dl:
+ self._size = self.dl.size
+ self.dl.abort = True
+
+
+ def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False,
+ progressNotify=None, disposition=False):
+ """ this can also download ftp """
+ self._size = 0
+ self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None,
+ self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition)
+ name = self.dl.download(chunks, resume)
+ self._size = self.dl.size
+
+ self.dl = None
+
+ return name
+
+
+ def load(self, *args, **kwargs):
+ """ retrieves page """
+ return self.http.load(*args, **kwargs)
+
+
+ def putHeader(self, name, value):
+ """ add a header to the request """
+ self.http.putHeader(name, value)
+
+
+ def addAuth(self, pwd):
+ """Adds user and pw for http auth
+
+ :param pwd: string, user:password
+ """
+ self.options['auth'] = pwd
+ self.renewHTTPRequest() #: we need a new request
+
+
+ def removeAuth(self):
+ if "auth" in self.options:
+ del self.options['auth']
+ self.renewHTTPRequest()
+
+
+ def setOption(self, name, value):
+ """Adds an option to the request, see HTTPRequest for existing ones"""
+ self.options[name] = value
+
+
+ def deleteOption(self, name):
+ if name in self.options:
+ del self.options[name]
+
+
+ def clearHeaders(self):
+ self.http.clearHeaders()
+
+
+ def close(self):
+ """ cleanup """
+ if hasattr(self, "http"):
+ self.http.close()
+ del self.http
+ if hasattr(self, "dl"):
+ del self.dl
+ if hasattr(self, "cj"):
+ del self.cj
diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py
new file mode 100644
index 000000000..2f957fcad
--- /dev/null
+++ b/pyload/network/Bucket.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from time import time
+from threading import Lock
+
+MIN_RATE = 10240 #: 10kb minimum rate
+
+
+class Bucket(object):
+
+ def __init__(self):
+ self.rate = 0 #: bytes per second, maximum targeted throughput
+ self.tokens = 0
+ self.timestamp = time()
+ self.lock = Lock()
+
+
+ def __nonzero__(self):
+ return self.rate >= MIN_RATE
+
+
+ def setRate(self, rate):
+ self.lock.acquire()
+ self.rate = int(rate)
+ self.lock.release()
+
+
+ def consumed(self, amount):
+ """ return the time the process has to sleep, after it consumed a specified amount """
+ if self.rate < MIN_RATE:
+ return 0 #: May become unresponsive otherwise
+
+ self.lock.acquire()
+ self.calc_tokens()
+ self.tokens -= amount
+
+ time = -self.tokens / float(self.rate) if self.tokens < 0 else 0
+
+ self.lock.release()
+ return time
+
+
+ def calc_tokens(self):
+ if self.tokens < self.rate:
+ now = time()
+ delta = self.rate * (now - self.timestamp)
+ self.tokens = min(self.rate, self.tokens + delta)
+ self.timestamp = now
diff --git a/pyload/network/CookieJar.py b/pyload/network/CookieJar.py
new file mode 100644
index 000000000..a970a08e5
--- /dev/null
+++ b/pyload/network/CookieJar.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Cookie
+
+from datetime import datetime, timedelta
+from time import time
+
+
+# monkey patch for 32 bit systems
+def _getdate(future=0, weekdayname=Cookie._weekdayname, monthname=Cookie._monthname):
+ dt = datetime.now() + timedelta(seconds=int(future))
+ return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \
+ (weekdayname[dt.weekday()], dt.day, monthname[dt.month], dt.year, dt.hour, dt.minute, dt.second)
+
+Cookie._getdate = _getdate
+
+
+class CookieJar(Cookie.SimpleCookie):
+
+ def getCookie(self, name):
+ return self[name].value
+
+
+ def setCookie(self, domain, name, value, path="/", exp=None, secure="FALSE"):
+ self[name] = value
+ self[name]['domain'] = domain
+ self[name]['path'] = path
+
+ # Value of expires should be integer if possible
+ # otherwise the cookie won't be used
+ if not exp:
+ expires = time() + 3600 * 24 * 180
+ else:
+ try:
+ expires = int(exp)
+ except ValueError:
+ expires = exp
+
+ self[name]['expires'] = expires
+
+ if secure == "TRUE":
+ self[name]['secure'] = secure
diff --git a/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py
new file mode 100644
index 000000000..85c20d519
--- /dev/null
+++ b/pyload/network/HTTPChunk.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from os import remove, stat, fsync
+from os.path import exists
+from time import sleep
+from re import search
+from pyload.utils import fs_encode
+import codecs
+import pycurl
+import urllib
+
+from pyload.network.HTTPRequest import HTTPRequest
+
+
+class WrongFormat(Exception):
+ pass
+
+
+class ChunkInfo(object):
+
+ def __init__(self, name):
+ self.name = unicode(name)
+ self.size = 0
+ self.resume = False
+ self.chunks = []
+
+
+ def __repr__(self):
+ ret = "ChunkInfo: %s, %s\n" % (self.name, self.size)
+ for i, c in enumerate(self.chunks):
+ ret += "%s# %s\n" % (i, c[1])
+ return ret
+
+
+ def setSize(self, size):
+ self.size = int(size)
+
+
+ def addChunk(self, name, range):
+ self.chunks.append((name, range))
+
+
+ def clear(self):
+ self.chunks = []
+
+
+ def createChunks(self, chunks):
+ self.clear()
+ chunk_size = self.size / chunks
+
+ current = 0
+ for i in xrange(chunks):
+ end = self.size - 1 if (i == chunks - 1) else current + chunk_size
+ self.addChunk("%s.chunk%s" % (self.name, i), (current, end))
+ current += chunk_size + 1
+
+
+ def save(self):
+ fs_name = fs_encode("%s.chunks" % self.name)
+ fh = codecs.open(fs_name, "w", "utf_8")
+ fh.write("name:%s\n" % self.name)
+ fh.write("size:%s\n" % self.size)
+ for i, c in enumerate(self.chunks):
+ fh.write("#%d:\n" % i)
+ fh.write("\tname:%s\n" % c[0])
+ fh.write("\trange:%i-%i\n" % c[1])
+ fh.close()
+
+
+ @staticmethod
+ def load(name):
+ fs_name = fs_encode("%s.chunks" % name)
+ if not exists(fs_name):
+ raise IOError()
+ fh = codecs.open(fs_name, "r", "utf_8")
+ name = fh.readline()[:-1]
+ size = fh.readline()[:-1]
+ if name.startswith("name:") and size.startswith("size:"):
+ name = name[5:]
+ size = size[5:]
+ else:
+ fh.close()
+ raise WrongFormat()
+ ci = ChunkInfo(name)
+ ci.loaded = True
+ ci.setSize(size)
+ while True:
+ if not fh.readline(): #: skip line
+ break
+ name = fh.readline()[1:-1]
+ range = fh.readline()[1:-1]
+ if name.startswith("name:") and range.startswith("range:"):
+ name = name[5:]
+ range = range[6:].split("-")
+ else:
+ raise WrongFormat()
+
+ ci.addChunk(name, (long(range[0]), long(range[1])))
+ fh.close()
+ return ci
+
+
+ def remove(self):
+ fs_name = fs_encode("%s.chunks" % self.name)
+ if exists(fs_name): remove(fs_name)
+
+
+ def getCount(self):
+ return len(self.chunks)
+
+
+ def getChunkName(self, index):
+ return self.chunks[index][0]
+
+
+ def getChunkRange(self, index):
+ return self.chunks[index][1]
+
+
+class HTTPChunk(HTTPRequest):
+
+ def __init__(self, id, parent, range=None, resume=False):
+ self.id = id
+ self.p = parent #: HTTPDownload instance
+ self.range = range #: tuple (start, end)
+ self.resume = resume
+ self.log = parent.log
+
+ self.size = range[1] - range[0] if range else -1
+ self.arrived = 0
+ self.lastURL = self.p.referer
+
+ self.c = pycurl.Curl()
+
+ self.header = ""
+ self.headerParsed = False #: indicates if the header has been processed
+
+ self.fp = None #: file handle
+
+ self.initHandle()
+ self.setInterface(self.p.options)
+
+ self.BOMChecked = False #: check and remove byte order mark
+
+ self.rep = None
+
+ self.sleep = 0.000
+ self.lastSize = 0
+
+
+ def __repr__(self):
+ return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived)
+
+
+ @property
+ def cj(self):
+ return self.p.cj
+
+
+ def getHandle(self):
+ """ returns a Curl handle ready to use for perform/multiperform """
+
+ self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj)
+ self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody)
+ self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
+
+ # request all bytes, since some servers in russia seems to have a defect arihmetic unit
+
+ fs_name = fs_encode(self.p.info.getChunkName(self.id))
+ if self.resume:
+ self.fp = open(fs_name, "ab")
+ self.arrived = self.fp.tell()
+ if not self.arrived:
+ self.arrived = stat(fs_name).st_size
+
+ if self.range:
+ # do nothing if chunk already finished
+ if self.arrived + self.range[0] >= self.range[1]:
+ return None
+
+ if self.id == len(self.p.info.chunks) - 1: #: as last chunk dont set end range, so we get everything
+ range = "%i-" % (self.arrived + self.range[0])
+ else:
+ range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1))
+
+ self.log.debug("Chunked resume with range %s" % range)
+ self.c.setopt(pycurl.RANGE, range)
+ else:
+ self.log.debug("Resume File from %i" % self.arrived)
+ self.c.setopt(pycurl.RESUME_FROM, self.arrived)
+
+ else:
+ if self.range:
+ if self.id == len(self.p.info.chunks) - 1: #: see above
+ range = "%i-" % self.range[0]
+ else:
+ range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1))
+
+ self.log.debug("Chunked with range %s" % range)
+ self.c.setopt(pycurl.RANGE, range)
+
+ self.fp = open(fs_name, "wb")
+
+ return self.c
+
+
+ def writeHeader(self, buf):
+ self.header += buf
+ #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
+ # as first chunk, we will parse the headers
+ if not self.range and self.header.endswith("\r\n\r\n"):
+ self.parseHeader()
+ elif not self.range and buf.startswith("150") and "data connection" in buf.lower(): #: ftp file size parsing
+ size = search(r"(\d+) bytes", buf)
+ if size:
+ self.p.size = int(size.group(1))
+ self.p.chunkSupport = True
+
+ self.headerParsed = True
+
+
+ def writeBody(self, buf):
+ # ignore BOM, it confuses unrar
+ if not self.BOMChecked:
+ if [ord(b) for b in buf[:3]] == [239, 187, 191]:
+ buf = buf[3:]
+ self.BOMChecked = True
+
+ size = len(buf)
+
+ self.arrived += size
+
+ self.fp.write(buf)
+
+ if self.p.bucket:
+ sleep(self.p.bucket.consumed(size))
+ else:
+ # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller
+ # otherwise reduce sleep time percentual (values are based on tests)
+ # So in general cpu time is saved without reducing bandwith too much
+
+ if size < self.lastSize:
+ self.sleep += 0.002
+ else:
+ self.sleep *= 0.7
+
+ self.lastSize = size
+
+ sleep(self.sleep)
+
+ if self.range and self.arrived > self.size:
+ return 0 #: close if we have enough data
+
+
+ def parseHeader(self):
+ """parse data from recieved header"""
+ for orgline in self.decodeResponse(self.header).splitlines():
+ line = orgline.strip().lower()
+
+ if line.startswith("accept-ranges") and "bytes" in line:
+ self.p.chunkSupport = True
+
+ if line.startswith("content-disposition") and ("filename=" in line or "filename*=" in line):
+ if "filename*=" in line:
+ # extended version according to RFC 6266 and RFC 5987.
+ encoding = line.partition("*=")[2].partition("''")[0]
+ name = orgline.partition("''")[2]
+ name = urllib.unquote(str(name)).decode(charEnc(encoding))
+ else:
+ # basic version, US-ASCII only
+ name = orgline.partition("filename=")[2]
+
+ name = name.replace('"', "").replace("'", "").replace(";", "").replace("/", "_").strip()
+ self.p.nameDisposition = name
+
+ self.log.debug("Content-Disposition: %s" % name)
+
+ if not self.resume and line.startswith("content-length"):
+ self.p.size = int(line.split(":")[1])
+
+ self.headerParsed = True
+
+
+ def stop(self):
+ """The download will not proceed after next call of writeBody"""
+ self.range = [0, 0]
+ self.size = 0
+
+
+ def resetRange(self):
+ """ Reset the range, so the download will load all data available """
+ self.range = None
+
+
+ def setRange(self, range):
+ self.range = range
+ self.size = range[1] - range[0]
+
+
+ def flushFile(self):
+ """ flush and close file """
+ self.fp.flush()
+ fsync(self.fp.fileno()) #: make sure everything was written to disk
+ self.fp.close() #: needs to be closed, or merging chunks will fail
+
+
+ def close(self):
+ """ closes everything, unusable after this """
+ if self.fp: self.fp.close()
+ self.c.close()
+ if hasattr(self, "p"):
+ del self.p
+
+
+def charEnc(enc):
+ return {'utf-8' : "utf_8",
+ 'iso-8859-1': "latin_1"}.get(enc, "unknown")
diff --git a/pyload/network/HTTPDownload.py b/pyload/network/HTTPDownload.py
new file mode 100644
index 000000000..1e74d4476
--- /dev/null
+++ b/pyload/network/HTTPDownload.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import pycurl
+
+from os import remove, fsync
+from os.path import dirname
+from time import sleep, time
+from shutil import move
+from logging import getLogger
+
+from pyload.network.HTTPChunk import ChunkInfo, HTTPChunk
+from pyload.network.HTTPRequest import BadHeader
+
+from pyload.plugin.Plugin import Abort
+from pyload.utils import fs_join, fs_encode
+
+
+class HTTPDownload(object):
+ """ loads a url http + ftp """
+
+ def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
+ options={}, progress=None, disposition=False):
+ self.url = url
+ self.filename = filename #: complete file destination, not only name
+ self.get = get
+ self.post = post
+ self.referer = referer
+ self.cj = cj #: cookiejar if cookies are needed
+ self.bucket = bucket
+ self.options = options
+ self.disposition = disposition
+ # all arguments
+
+ self.abort = False
+ self.size = 0
+ self.nameDisposition = None #: will be parsed from content disposition
+
+ self.chunks = []
+
+ self.log = getLogger("log")
+
+ try:
+ self.info = ChunkInfo.load(filename)
+ self.info.resume = True #: resume is only possible with valid info file
+ self.size = self.info.size
+ self.infoSaved = True
+ except IOError:
+ self.info = ChunkInfo(filename)
+
+ self.chunkSupport = True
+ self.m = pycurl.CurlMulti()
+
+ # needed for speed calculation
+ self.lastArrived = []
+ self.speeds = []
+ self.lastSpeeds = [0, 0]
+
+ self.progress = progress
+
+
+ @property
+ def speed(self):
+ last = [sum(x) for x in self.lastSpeeds if x]
+ return (sum(self.speeds) + sum(last)) / (1 + len(last))
+
+
+ @property
+ def arrived(self):
+ return sum([c.arrived for c in self.chunks])
+
+
+ @property
+ def percent(self):
+ return (self.arrived * 100) / self.size if self.size else 0
+
+ def _copyChunks(self):
+ init = fs_encode(self.info.getChunkName(0)) #: initial chunk name
+
+ if self.info.getCount() > 1:
+ with open(init, "rb+") as fo: #: first chunkfile
+ for i in xrange(1, self.info.getCount()):
+ # input file
+ fo.seek(
+ self.info.getChunkRange(i - 1)[1] + 1) #: seek to beginning of chunk, to get rid of overlapping chunks
+ fname = fs_encode("%s.chunk%d" % (self.filename, i))
+ with open(fname, "rb") as fi:
+ buf = 32 * 1024
+ while True: #: copy in chunks, consumes less memory
+ data = fi.read(buf)
+ if not data:
+ break
+ fo.write(data)
+ if fo.tell() < self.info.getChunkRange(i)[1]:
+ remove(init)
+ self.info.remove() #: there are probably invalid chunks
+ raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.")
+ remove(fname) #: remove chunk
+
+ if self.nameDisposition and self.disposition:
+ self.filename = fs_join(dirname(self.filename), self.nameDisposition)
+
+ move(init, fs_encode(self.filename))
+ self.info.remove() #: remove info file
+
+
+ def download(self, chunks=1, resume=False):
+ """ returns new filename or None """
+
+ chunks = max(1, chunks)
+ resume = self.info.resume and resume
+
+ try:
+ self._download(chunks, resume)
+ except pycurl.error, e:
+ # code 33 - no resume
+ code = e.args[0]
+ if resume is True and code == 33:
+ # try again without resume
+ self.log.debug("Errno 33 -> Restart without resume")
+
+ # remove old handles
+ for chunk in self.chunks:
+ self.closeChunk(chunk)
+
+ return self._download(chunks, False)
+ else:
+ raise
+ finally:
+ self.close()
+
+ if self.nameDisposition and self.disposition:
+ return self.nameDisposition
+ return None
+
+
+ def _download(self, chunks, resume):
+ if not resume:
+ self.info.clear()
+ self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #: create an initial entry
+ self.info.save()
+
+ self.chunks = []
+
+ init = HTTPChunk(0, self, None, resume) #: initial chunk that will load complete file (if needed)
+
+ self.chunks.append(init)
+ self.m.add_handle(init.getHandle())
+
+ lastFinishCheck = 0
+ lastTimeCheck = 0
+ chunksDone = set() #: list of curl handles that are finished
+ chunksCreated = False
+ done = False
+ if self.info.getCount() is 0: #: This is a resume, if we were chunked originally assume still can
+ self.chunkSupport = False
+
+ while 1:
+ # need to create chunks
+ if not chunksCreated and self.chunkSupport and self.size: #: will be setted later by first chunk
+
+ if not resume:
+ self.info.setSize(self.size)
+ self.info.createChunks(chunks)
+ self.info.save()
+
+ chunks = self.info.getCount()
+
+ init.setRange(self.info.getChunkRange(0))
+
+ for i in xrange(1, chunks):
+ c = HTTPChunk(i, self, self.info.getChunkRange(i), resume)
+
+ handle = c.getHandle()
+ if handle:
+ self.chunks.append(c)
+ self.m.add_handle(handle)
+ else:
+ # close immediatly
+ self.log.debug("Invalid curl handle -> closed")
+ c.close()
+
+ chunksCreated = True
+
+ while 1:
+ ret, num_handles = self.m.perform()
+ if ret != pycurl.E_CALL_MULTI_PERFORM:
+ break
+
+ t = time()
+
+ # reduce these calls
+ while lastFinishCheck + 0.5 < t:
+ # list of failed curl handles
+ failed = []
+ ex = None #: save only last exception, we can only raise one anyway
+
+ num_q, ok_list, err_list = self.m.info_read()
+ for c in ok_list:
+ chunk = self.findChunk(c)
+ try: #: check if the header implies success, else add it to failed list
+ chunk.verifyHeader()
+ except BadHeader, e:
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
+ failed.append(chunk)
+ ex = e
+ else:
+ chunksDone.add(c)
+
+ for c in err_list:
+ curl, errno, msg = c
+ chunk = self.findChunk(curl)
+ # test if chunk was finished
+ if errno != 23 or "0 !=" not in msg:
+ failed.append(chunk)
+ ex = pycurl.error(errno, msg)
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex)))
+ continue
+
+ try: #: check if the header implies success, else add it to failed list
+ chunk.verifyHeader()
+ except BadHeader, e:
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
+ failed.append(chunk)
+ ex = e
+ else:
+ chunksDone.add(curl)
+ if not num_q: #: no more infos to get
+
+ # check if init is not finished so we reset download connections
+ # note that other chunks are closed and downloaded with init too
+ if failed and init not in failed and init.c not in chunksDone:
+ self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex))))
+
+ # list of chunks to clean and remove
+ to_clean = filter(lambda x: x is not init, self.chunks)
+ for chunk in to_clean:
+ self.closeChunk(chunk)
+ self.chunks.remove(chunk)
+ remove(fs_encode(self.info.getChunkName(chunk.id)))
+
+ # let first chunk load the rest and update the info file
+ init.resetRange()
+ self.info.clear()
+ self.info.addChunk("%s.chunk0" % self.filename, (0, self.size))
+ self.info.save()
+ elif failed:
+ raise ex
+
+ lastFinishCheck = t
+
+ if len(chunksDone) >= len(self.chunks):
+ if len(chunksDone) > len(self.chunks):
+ self.log.warning("Finished download chunks size incorrect, please report bug.")
+ done = True #: all chunks loaded
+
+ break
+
+ if done:
+ break #: all chunks loaded
+
+ # calc speed once per second, averaging over 3 seconds
+ if lastTimeCheck + 1 < t:
+ diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in
+ enumerate(self.chunks)]
+
+ self.lastSpeeds[1] = self.lastSpeeds[0]
+ self.lastSpeeds[0] = self.speeds
+ self.speeds = [float(a) / (t - lastTimeCheck) for a in diff]
+ self.lastArrived = [c.arrived for c in self.chunks]
+ lastTimeCheck = t
+ self.updateProgress()
+
+ if self.abort:
+ raise Abort
+
+ # sleep(0.003) #: supress busy waiting - limits dl speed to (1 / x) * buffersize
+ self.m.select(1)
+
+ for chunk in self.chunks:
+ chunk.flushFile() #: make sure downloads are written to disk
+
+ self._copyChunks()
+
+
+ def updateProgress(self):
+ if self.progress:
+ self.progress(self.percent)
+
+
+ def findChunk(self, handle):
+ """ linear search to find a chunk (should be ok since chunk size is usually low) """
+ for chunk in self.chunks:
+ if chunk.c == handle:
+ return chunk
+
+
+ def closeChunk(self, chunk):
+ try:
+ self.m.remove_handle(chunk.c)
+ except pycurl.error, e:
+ self.log.debug("Error removing chunk: %s" % str(e))
+ finally:
+ chunk.close()
+
+
+ def close(self):
+ """ cleanup """
+ for chunk in self.chunks:
+ self.closeChunk(chunk)
+
+ self.chunks = []
+ if hasattr(self, "m"):
+ self.m.close()
+ del self.m
+ if hasattr(self, "cj"):
+ del self.cj
+ if hasattr(self, "info"):
+ del self.info
diff --git a/pyload/network/HTTPRequest.py b/pyload/network/HTTPRequest.py
new file mode 100644
index 000000000..74b83cf12
--- /dev/null
+++ b/pyload/network/HTTPRequest.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import pycurl
+
+from codecs import getincrementaldecoder, lookup, BOM_UTF8
+from urllib import quote, urlencode
+from httplib import responses
+from logging import getLogger
+from cStringIO import StringIO
+
+from pyload.plugin.Plugin import Abort, Fail
+
+from pyload.utils import encode
+
+
+def myquote(url):
+ return quote(encode(url), safe="%/:=&?~#+!$,;'@()*[]")
+
+
+def myurlencode(data):
+ data = dict(data)
+ return urlencode(dict((encode(x), encode(y)) for x, y in data.iteritems()))
+
+bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
+
+
+class BadHeader(Exception):
+
+ def __init__(self, code, content=""):
+ Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)]))
+ self.code = code
+ self.content = content
+
+
+class HTTPRequest(object):
+
+ def __init__(self, cookies=None, options=None):
+ self.c = pycurl.Curl()
+ self.rep = StringIO()
+
+ self.cj = cookies #: cookiejar
+
+ self.lastURL = None
+ self.lastEffectiveURL = None
+ self.abort = False
+ self.code = 0 #: last http code
+
+ self.header = ""
+
+ self.headers = [] #: temporary request header
+
+ self.initHandle()
+ self.setInterface(options)
+
+ self.c.setopt(pycurl.WRITEFUNCTION, self.write)
+ self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
+
+ self.log = getLogger("log")
+
+
+ def initHandle(self):
+ """ sets common options to curl handle """
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.MAXREDIRS, 10)
+ self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
+ self.c.setopt(pycurl.NOSIGNAL, 1)
+ self.c.setopt(pycurl.NOPROGRESS, 1)
+ if hasattr(pycurl, "AUTOREFERER"):
+ self.c.setopt(pycurl.AUTOREFERER, 1)
+ self.c.setopt(pycurl.SSL_VERIFYPEER, 0)
+ self.c.setopt(pycurl.LOW_SPEED_TIME, 60)
+ self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
+ if hasattr(pycurl, "USE_SSL"):
+ self.c.setopt(pycurl.USE_SSL, pycurl.CURLUSESSL_TRY)
+
+ # self.c.setopt(pycurl.VERBOSE, 1)
+
+ self.c.setopt(pycurl.USERAGENT,
+ "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")
+
+ if pycurl.version_info()[7]:
+ self.c.setopt(pycurl.ENCODING, "gzip, deflate")
+ self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*",
+ "Accept-Language: en-US, en",
+ "Accept-Charset: ISO-8859-1, utf-8;q=0.7,*;q=0.7",
+ "Connection: keep-alive",
+ "Keep-Alive: 300",
+ "Expect:"])
+
+
+ def setInterface(self, options):
+
+ interface, proxy, ipv6 = options['interface'], options['proxies'], options['ipv6']
+
+ if interface and interface.lower() != "none":
+ self.c.setopt(pycurl.INTERFACE, str(interface))
+
+ if proxy:
+ if proxy['type'] == "socks4":
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
+ elif proxy['type'] == "socks5":
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
+ else:
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
+
+ self.c.setopt(pycurl.PROXY, str(proxy['address']))
+ self.c.setopt(pycurl.PROXYPORT, proxy['port'])
+
+ if proxy['username']:
+ self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy['username'], proxy['password'])))
+
+ if ipv6:
+ self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)
+ else:
+ self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
+
+ if "auth" in options:
+ self.c.setopt(pycurl.USERPWD, str(options['auth']))
+
+ if "timeout" in options:
+ self.c.setopt(pycurl.LOW_SPEED_TIME, options['timeout'])
+
+
+ def addCookies(self):
+ """ put cookies from curl handle to cj """
+ if self.cj:
+ self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST))
+
+
+ def getCookies(self):
+ """ add cookies from cj to curl handle """
+ if self.cj:
+ for c in self.cj.getCookies():
+ self.c.setopt(pycurl.COOKIELIST, c)
+ return
+
+
+ def clearCookies(self):
+ self.c.setopt(pycurl.COOKIELIST, "")
+
+
+ def setRequestContext(self, url, get, post, referer, cookies, multipart=False):
+ """ sets everything needed for the request """
+
+ url = myquote(url)
+
+ if get:
+ get = urlencode(get)
+ url = "%s?%s" % (url, get)
+
+ self.c.setopt(pycurl.URL, url)
+ self.c.lastUrl = url
+
+ if post:
+ self.c.setopt(pycurl.POST, 1)
+ if not multipart:
+ if type(post) == unicode:
+ post = str(post) #: unicode not allowed
+ elif type(post) == str:
+ pass
+ else:
+ post = myurlencode(post)
+
+ self.c.setopt(pycurl.POSTFIELDS, post)
+ else:
+ post = [(x, encode(y)) for x, y in post.iteritems()]
+ self.c.setopt(pycurl.HTTPPOST, post)
+ else:
+ self.c.setopt(pycurl.POST, 0)
+
+ if referer and self.lastURL:
+ self.c.setopt(pycurl.REFERER, str(self.lastURL))
+
+ if cookies:
+ self.c.setopt(pycurl.COOKIEFILE, "")
+ self.c.setopt(pycurl.COOKIEJAR, "")
+ self.getCookies()
+
+
+ def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False, follow_location=True, save_cookies=True):
+ """ load and returns a given page """
+
+ self.setRequestContext(url, get, post, referer, cookies, multipart)
+
+ self.header = ""
+
+ self.c.setopt(pycurl.HTTPHEADER, self.headers)
+
+ if post:
+ self.c.setopt(pycurl.POST, 1)
+ else:
+ self.c.setopt(pycurl.HTTPGET, 1)
+
+ if not follow_location:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 0)
+
+ if just_header:
+ self.c.setopt(pycurl.NOBODY, 1)
+
+ self.c.perform()
+ rep = self.header if just_header else self.getResponse()
+
+ if not follow_location:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+ if just_header:
+ self.c.setopt(pycurl.NOBODY, 0)
+
+ self.c.setopt(pycurl.POSTFIELDS, "")
+ self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL)
+ self.code = self.verifyHeader()
+
+ if save_cookies:
+ self.addCookies()
+
+ if decode:
+ rep = self.decodeResponse(rep)
+
+ return rep
+
+
+ def verifyHeader(self):
+ """ raise an exceptions on bad headers """
+ code = int(self.c.getinfo(pycurl.RESPONSE_CODE))
+ if code in bad_headers:
+ # 404 will NOT raise an exception
+ raise BadHeader(code, self.getResponse())
+ return code
+
+
+ def checkHeader(self):
+ """ check if header indicates failure"""
+ return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers
+
+
+ def getResponse(self):
+ """ retrieve response from string io """
+ if self.rep is None:
+ return ""
+ else:
+ value = self.rep.getvalue()
+ self.rep.close()
+ self.rep = StringIO()
+ return value
+
+
+ def decodeResponse(self, rep):
+ """ decode with correct encoding, relies on header """
+ header = self.header.splitlines()
+ encoding = "utf8" #: default encoding
+
+ for line in header:
+ line = line.lower().replace(" ", "")
+ if not line.startswith("content-type:") or \
+ ("text" not in line and "application" not in line):
+ continue
+
+ none, delemiter, charset = line.rpartition("charset=")
+ if delemiter:
+ charset = charset.split(";")
+ if charset:
+ encoding = charset[0]
+
+ try:
+ # self.log.debug("Decoded %s" % encoding )
+ if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8):
+ encoding = 'utf-8-sig'
+
+ decoder = getincrementaldecoder(encoding)("replace")
+ rep = decoder.decode(rep, True)
+
+ # TODO: html_unescape as default
+
+ except LookupError:
+ self.log.debug("No Decoder foung for %s" % encoding)
+
+ except Exception:
+ self.log.debug("Error when decoding string from %s." % encoding)
+
+ return rep
+
+
+ def write(self, buf):
+ """ writes response """
+ if self.rep.tell() > 1000000 or self.abort:
+ rep = self.getResponse()
+
+ if self.abort:
+ raise Abort
+
+ with open("response.dump", "wb") as f:
+ f.write(rep)
+ raise Fail("Loaded url exceeded size limit")
+ else:
+ self.rep.write(buf)
+
+
+ def writeHeader(self, buf):
+ """ writes header """
+ self.header += buf
+
+
+ def putHeader(self, name, value):
+ self.headers.append("%s: %s" % (name, value))
+
+
+ def clearHeaders(self):
+ self.headers = []
+
+
+ def close(self):
+ """ cleanup, unusable after this """
+ self.rep.close()
+ if hasattr(self, "cj"):
+ del self.cj
+ if hasattr(self, "c"):
+ self.c.close()
+ del self.c
diff --git a/pyload/network/JsEngine.py b/pyload/network/JsEngine.py
new file mode 100644
index 000000000..b59d07dc4
--- /dev/null
+++ b/pyload/network/JsEngine.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+import subprocess
+import sys
+
+from os import path
+from urllib import quote
+
+from pyload.utils import encode, decode, uniqify
+
+
+class JsEngine(object):
+ """ JS Engine superclass """
+
+ def __init__(self, core, engine=None):
+ self.core = core
+ self.engine = None #: Engine Instance
+
+ if not engine:
+ engine = self.core.config.get("general", "jsengine")
+
+ if engine != "auto" and self.set(engine) is False:
+ engine = "auto"
+ self.core.log.warning("JS Engine set to \"auto\" for safely")
+
+ if engine == "auto":
+ for E in self.find():
+ if self.set(E) is True:
+ break
+ else:
+ self.core.log.error("No JS Engine available")
+
+
+ @classmethod
+ def find(cls):
+ """ Check if there is any engine available """
+ return [E for E in ENGINES if E.find()]
+
+
+ def get(self, engine=None):
+ """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """
+ if not engine:
+ JSE = self.engine
+
+ elif isinstance(engine, basestring):
+ engine_name = engine.lower()
+ for E in ENGINES:
+ if E._name == engine_name: #: doesn't check if E(NGINE) is available, just convert string to class
+ JSE = E
+ break
+ else:
+ raise ValueError("JSE")
+
+ elif issubclass(engine, AbstractEngine):
+ JSE = engine
+
+ else:
+ raise TypeError("engine")
+
+ return JSE
+
+
+ def set(self, engine):
+ """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """
+ if isinstance(engine, basestring):
+ return self.set(self.get(engine))
+
+ elif issubclass(engine, AbstractEngine) and engine.find():
+ self.engine = engine
+ return True
+
+ else:
+ return False
+
+
+ def eval(self, script, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class"""
+ JSE = self.get(engine)
+
+ if not JSE:
+ raise TypeError("engine")
+
+ script = encode(script)
+
+ out, err = JSE.eval(script)
+
+ results = [out]
+
+ if self.core.config.get("general", "debug"):
+ if err:
+ self.core.log.debug(JSE._name + ":", err)
+
+ engines = self.find()
+ engines.remove(JSE)
+ for E in engines:
+ out, err = E.eval(script)
+ res = err or out
+ self.core.log.debug(E._name + ":", res)
+ results.append(res)
+
+ if len(results) > 1 and len(uniqify(results)) > 1:
+ self.core.log.warning("JS output of two or more engines mismatch")
+
+ return results[0]
+
+
+class AbstractEngine(object):
+ """ JSE base class """
+
+ _name = ""
+
+
+ def __init__(self, force=False):
+ self.setup()
+ self.available = force or self.find()
+
+
+ def setup(self):
+ pass
+
+
+ @classmethod
+ def find(cls):
+ """ Check if the engine is available """
+ try:
+ __import__(cls._name)
+ except Exception:
+ try:
+ out, err = cls(True).eval("23+19")
+ except Exception:
+ res = False
+ else:
+ res = out == "42"
+ else:
+ res = True
+
+ return res
+
+
+ def _eval(self, args):
+ if not self.available:
+ return None, "JS Engine \"%s\" not found" % self._name
+
+ try:
+ p = subprocess.Popen(args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=-1)
+ return map(lambda x: x.strip(), p.communicate())
+ except Exception, e:
+ return None, e
+
+
+ def eval(self, script):
+ raise NotImplementedError
+
+
+class Pyv8Engine(AbstractEngine):
+
+ _name = "PyV8"
+
+
+ def eval(self, script):
+ if not self.available:
+ return None, "JS Engine \"%s\" not found" % self._name
+
+ try:
+ rt = PyV8.JSContext()
+ rt.enter()
+ res = rt.eval(script), None #@TODO: parse stderr
+ except Exception, e:
+ res = None, e
+
+ return res
+
+
+class CommonEngine(AbstractEngine):
+
+ _name = "js"
+
+
+ def setup(self):
+ # subprocess.Popen(["js", "-v"], bufsize=-1).communicate()
+ pass
+
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = ["js", "-e", script]
+ return self._eval(args)
+
+
+class NodeEngine(AbstractEngine):
+
+ _name = "nodejs"
+
+
+ def setup(self):
+ subprocess.Popen(["node", "-v"], bufsize=-1).communicate()
+
+
+ def eval(self, script):
+ script = "console.log(eval(unescape('%s')))" % quote(script)
+ args = ["node", "-e", script]
+ return self._eval(args)
+
+
+class RhinoEngine(AbstractEngine):
+
+ _name = "rhino"
+
+
+ def setup(self):
+ jspath = [
+ "/usr/share/java/js.jar",
+ "js.jar",
+ path.join(pypath, "js.jar")
+ ]
+ for p in jspath:
+ if path.exists(p):
+ self.path = p
+ break
+ else:
+ self.path = ""
+
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = ["java", "-cp", self.path, "org.mozilla.javascript.tools.shell.Main", "-e", script]
+ res = decode(self._eval(args))
+ try:
+ return res.encode("ISO-8859-1")
+ finally:
+ return res
+
+
+class JscEngine(AbstractEngine):
+
+ _name = "javascriptcore"
+
+
+ def setup(self):
+ jspath = "/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc"
+ self.path = jspath if path.exists(jspath) else ""
+
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = [self.path, "-e", script]
+ return self._eval(args)
+
+
+#@NOTE: Priority ordered
+ENGINES = [CommonEngine, Pyv8Engine, NodeEngine, RhinoEngine]
+
+if sys.platform == "darwin":
+ ENGINES.insert(JscEngine)
diff --git a/pyload/network/RequestFactory.py b/pyload/network/RequestFactory.py
new file mode 100644
index 000000000..5e2c15f4b
--- /dev/null
+++ b/pyload/network/RequestFactory.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from threading import Lock
+
+from pyload.network.Browser import Browser
+from pyload.network.Bucket import Bucket
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.network.CookieJar import CookieJar
+from pyload.network.XDCCRequest import XDCCRequest
+
+
+class RequestFactory(object):
+
+ def __init__(self, core):
+ self.lock = Lock()
+ self.core = core
+ self.bucket = Bucket()
+ self.updateBucket()
+ self.cookiejars = {}
+
+
+ def iface(self):
+ return self.core.config.get("download", "interface")
+
+
+ def getRequest(self, pluginName, account=None, type="HTTP"):
+ self.lock.acquire()
+
+ if type == "XDCC":
+ return XDCCRequest(proxies=self.getProxies())
+
+ req = Browser(self.bucket, self.getOptions())
+
+ if account:
+ cj = self.getCookieJar(pluginName, account)
+ req.setCookieJar(cj)
+ else:
+ req.setCookieJar(CookieJar(pluginName))
+
+ self.lock.release()
+ return req
+
+
+ def getHTTPRequest(self, **kwargs):
+ """ returns a http request, dont forget to close it ! """
+ options = self.getOptions()
+ options.update(kwargs) #: submit kwargs as additional options
+ return HTTPRequest(CookieJar(None), options)
+
+
+ def getURL(self, *args, **kwargs):
+ """ see HTTPRequest for argument list """
+ cj = None
+
+ if 'cookies' in kwargs:
+ if isinstance(kwargs['cookies'], CookieJar):
+ cj = kwargs['cookies']
+ elif isinstance(kwargs['cookies'], list):
+ cj = CookieJar(None)
+ for cookie in kwargs['cookies']:
+ if isinstance(cookie, tuple) and len(cookie) == 3:
+ cj.setCookie(*cookie)
+
+ h = HTTPRequest(cj, self.getOptions())
+ try:
+ rep = h.load(*args, **kwargs)
+ finally:
+ h.close()
+
+ return rep
+
+
+ def getCookieJar(self, pluginName, account=None):
+ if (pluginName, account) in self.cookiejars:
+ return self.cookiejars[(pluginName, account)]
+
+ cj = CookieJar(pluginName, account)
+ self.cookiejars[(pluginName, account)] = cj
+ return cj
+
+
+ def getProxies(self):
+ """ returns a proxy list for the request classes """
+ if not self.core.config.get("proxy", "proxy"):
+ return {}
+ else:
+ type = "http"
+ setting = self.core.config.get("proxy", "type").lower()
+ if setting == "socks4":
+ type = "socks4"
+ elif setting == "socks5":
+ type = "socks5"
+
+ username = None
+ if self.core.config.get("proxy", "username") and self.core.config.get("proxy", "username").lower() != "none":
+ username = self.core.config.get("proxy", "username")
+
+ pw = None
+ if self.core.config.get("proxy", "password") and self.core.config.get("proxy", "password").lower() != "none":
+ pw = self.core.config.get("proxy", "password")
+
+ return {
+ "type": type,
+ "address": self.core.config.get("proxy", "address"),
+ "port": self.core.config.get("proxy", "port"),
+ "username": username,
+ "password": pw,
+ }
+
+
+ def getOptions(self):
+ """returns options needed for pycurl"""
+ return {"interface": self.iface(),
+ "proxies": self.getProxies(),
+ "ipv6": self.core.config.get("download", "ipv6")}
+
+
+ def updateBucket(self):
+ """ set values in the bucket according to settings"""
+ if not self.core.config.get("download", "limit_speed"):
+ self.bucket.setRate(-1)
+ else:
+ self.bucket.setRate(self.core.config.get("download", "max_speed") * 1024)
+
+
+# needs pyreq in global namespace
+def getURL(*args, **kwargs):
+ return pyreq.getURL(*args, **kwargs)
+
+
+def getRequest(*args, **kwargs):
+ return pyreq.getHTTPRequest()
diff --git a/pyload/network/XDCCRequest.py b/pyload/network/XDCCRequest.py
new file mode 100644
index 000000000..24146ccaa
--- /dev/null
+++ b/pyload/network/XDCCRequest.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+# @author: jeix
+
+import socket
+import struct
+
+from os import remove
+from os.path import exists
+from select import select
+from time import time
+
+from pyload.plugin.Plugin import Abort
+
+
+class XDCCRequest(object):
+
+ def __init__(self, timeout=30, proxies={}):
+
+ self.proxies = proxies
+ self.timeout = timeout
+
+ self.filesize = 0
+ self.recv = 0
+ self.speed = 0
+
+ self.abort = False
+
+
+ def createSocket(self):
+ # proxytype = None
+ # proxy = None
+ # if self.proxies.has_key("socks5"):
+ # proxytype = socks.PROXY_TYPE_SOCKS5
+ # proxy = self.proxies['socks5']
+ # elif self.proxies.has_key("socks4"):
+ # proxytype = socks.PROXY_TYPE_SOCKS4
+ # proxy = self.proxies['socks4']
+ # if proxytype:
+ # sock = socks.socksocket()
+ # t = _parse_proxy(proxy)
+ # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2])
+ # else:
+ # sock = socket.socket()
+ # return sock
+
+ return socket.socket()
+
+
+ def download(self, ip, port, filename, irc, progress=None):
+
+ ircbuffer = ""
+ lastUpdate = time()
+ cumRecvLen = 0
+
+ dccsock = self.createSocket()
+
+ dccsock.settimeout(self.timeout)
+ dccsock.connect((ip, port))
+
+ if exists(filename):
+ i = 0
+ nameParts = filename.rpartition(".")
+ while True:
+ newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2])
+ i += 1
+
+ if not exists(newfilename):
+ filename = newfilename
+ break
+
+ fh = open(filename, "wb")
+
+ # recv loop for dcc socket
+ while True:
+ if self.abort:
+ dccsock.close()
+ fh.close()
+ remove(filename)
+ raise Abort
+
+ self._keepAlive(irc, ircbuffer)
+
+ data = dccsock.recv(4096)
+ dataLen = len(data)
+ self.recv += dataLen
+
+ cumRecvLen += dataLen
+
+ now = time()
+ timespan = now - lastUpdate
+ if timespan > 1:
+ self.speed = cumRecvLen / timespan
+ cumRecvLen = 0
+ lastUpdate = now
+
+ if progress:
+ progress(self.percent)
+
+ if not data:
+ break
+
+ fh.write(data)
+
+ # acknowledge data by sending number of recceived bytes
+ dccsock.send(struct.pack('!I', self.recv))
+
+ dccsock.close()
+ fh.close()
+
+ return filename
+
+
+ def _keepAlive(self, sock, *readbuffer):
+ fdset = select([sock], [], [], 0)
+ if sock not in fdset[0]:
+ return
+
+ readbuffer += sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ line = line.rstrip()
+ first = line.split()
+ if first[0] == "PING":
+ sock.send("PONG %s\r\n" % first[1])
+
+
+ def abortDownloads(self):
+ self.abort = True
+
+
+ @property
+ def size(self):
+ return self.filesize
+
+
+ @property
+ def arrived(self):
+ return self.recv
+
+
+ @property
+ def percent(self):
+ return (self.recv * 100) / self.filesize if elf.filesize else 0
+
+
+ def close(self):
+ pass
diff --git a/pyload/network/__init__.py b/pyload/network/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/network/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/Account.py b/pyload/plugin/Account.py
new file mode 100644
index 000000000..bb8f7d59a
--- /dev/null
+++ b/pyload/plugin/Account.py
@@ -0,0 +1,308 @@
+# -*- coding: utf-8 -*-
+
+from random import choice
+from time import time
+from traceback import print_exc
+from threading import RLock
+
+from pyload.plugin.Plugin import Base
+from pyload.utils import compare_time, parseFileSize, lock
+
+
+class WrongPassword(Exception):
+ pass
+
+
+class Account(Base):
+ """
+ Base class for every Account plugin.
+ Just overwrite `login` and cookies will be stored and account becomes accessible in\
+ associated hoster plugin. Plugin should also provide `loadAccountInfo`
+ """
+ __name = "Account"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Base account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ #: after that time (in minutes) pyload will relogin the account
+ login_timeout = 10 * 60
+ #: after that time (in minutes) account data will be reloaded
+ info_threshold = 10 * 60
+
+
+ def __init__(self, manager, accounts):
+ Base.__init__(self, manager.core)
+
+ self.manager = manager
+ self.accounts = {}
+ self.infos = {} #: cache for account information
+ self.lock = RLock()
+ self.timestamps = {}
+
+ self.init()
+
+ self.setAccounts(accounts)
+
+
+ def init(self):
+ pass
+
+
+ def login(self, user, data, req):
+ """login into account, the cookies will be saved so user can be recognized
+
+ :param user: loginname
+ :param data: data dictionary
+ :param req: `Request` instance
+ """
+ pass
+
+
+ @lock
+ def _login(self, user, data):
+ # set timestamp for login
+ self.timestamps[user] = time()
+
+ req = self.getAccountRequest(user)
+ try:
+ self.login(user, data, req)
+ except WrongPassword:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": user,
+ "msg": _("Wrong Password")})
+ success = data['valid'] = False
+ except Exception, e:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": user,
+ "msg": e})
+ success = data['valid'] = False
+ if self.core.debug:
+ print_exc()
+ else:
+ success = True
+ finally:
+ if req:
+ req.close()
+ return success
+
+
+ def relogin(self, user):
+ req = self.getAccountRequest(user)
+ if req:
+ req.cj.clear()
+ req.close()
+ if user in self.infos:
+ del self.infos[user] #: delete old information
+
+ return self._login(user, self.accounts[user])
+
+
+ def setAccounts(self, accounts):
+ self.accounts = accounts
+ for user, data in self.accounts.iteritems():
+ self._login(user, data)
+ self.infos[user] = {}
+
+
+ def updateAccounts(self, user, password=None, options={}):
+ """ updates account and return true if anything changed """
+
+ if user in self.accounts:
+ self.accounts[user]['valid'] = True #: do not remove or accounts will not login
+ if password:
+ self.accounts[user]['password'] = password
+ self.relogin(user)
+ return True
+ if options:
+ before = self.accounts[user]['options']
+ self.accounts[user]['options'].update(options)
+ return self.accounts[user]['options'] != before
+ else:
+ self.accounts[user] = {"password": password, "options": options, "valid": True}
+ self._login(user, self.accounts[user])
+ return True
+
+
+ def removeAccount(self, user):
+ if user in self.accounts:
+ del self.accounts[user]
+ if user in self.infos:
+ del self.infos[user]
+ if user in self.timestamps:
+ del self.timestamps[user]
+
+
+ @lock
+ def getAccountInfo(self, name, force=False):
+ """retrieve account infos for an user, do **not** overwrite this method!\\
+ just use it to retrieve infos in hoster plugins. see `loadAccountInfo`
+
+ :param name: username
+ :param force: reloads cached account information
+ :return: dictionary with information
+ """
+ data = Account.loadAccountInfo(self, name)
+
+ if force or name not in self.infos:
+ self.logDebug("Get Account Info for %s" % name)
+ req = self.getAccountRequest(name)
+
+ try:
+ infos = self.loadAccountInfo(name, req)
+ if not type(infos) == dict:
+ raise Exception("Wrong return format")
+ except Exception, e:
+ infos = {"error": str(e)}
+ print_exc()
+
+ if req:
+ req.close()
+
+ self.logDebug("Account Info: %s" % infos)
+
+ infos['timestamp'] = time()
+ self.infos[name] = infos
+ elif "timestamp" in self.infos[name] and self.infos[name]['timestamp'] + self.info_threshold * 60 < time():
+ self.logDebug("Reached timeout for account data")
+ self.scheduleRefresh(name)
+
+ data.update(self.infos[name])
+ return data
+
+
+ def isPremium(self, user):
+ info = self.getAccountInfo(user)
+ return info['premium']
+
+
+ def loadAccountInfo(self, name, req=None):
+ """this should be overwritten in account plugin,\
+ and retrieving account information for user
+
+ :param name:
+ :param req: `Request` instance
+ :return:
+ """
+ return {"validuntil" : None, #: -1 for unlimited
+ "login" : name,
+ # "password" : self.accounts[name]['password'], #: commented due security reason
+ "options" : self.accounts[name]['options'],
+ "valid" : self.accounts[name]['valid'],
+ "trafficleft": None, #: in bytes, -1 for unlimited
+ "maxtraffic" : None,
+ "premium" : None,
+ "timestamp" : 0, #: time this info was retrieved
+ "type" : self.getClassName()}
+
+
+ def getAllAccounts(self, force=False):
+ return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()]
+
+
+ def getAccountRequest(self, user=None):
+ if not user:
+ user, data = self.selectAccount()
+ if not user:
+ return None
+
+ req = self.core.requestFactory.getRequest(self.getClassName(), user)
+ return req
+
+
+ def getAccountCookies(self, user=None):
+ if not user:
+ user, data = self.selectAccount()
+ if not user:
+ return None
+
+ cj = self.core.requestFactory.getCookieJar(self.getClassName(), user)
+ return cj
+
+
+ def getAccountData(self, user):
+ return self.accounts[user]
+
+
+ def selectAccount(self):
+ """ returns an valid account name and data"""
+ usable = []
+ for user, data in self.accounts.iteritems():
+ if not data['valid']:
+ continue
+
+ if "time" in data['options'] and data['options']['time']:
+ time_data = ""
+ try:
+ time_data = data['options']['time'][0]
+ start, end = time_data.split("-")
+ if not compare_time(start.split(":"), end.split(":")):
+ continue
+ except Exception:
+ self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data)
+
+ if user in self.infos:
+ if "validuntil" in self.infos[user]:
+ if self.infos[user]['validuntil'] > 0 and time() > self.infos[user]['validuntil']:
+ continue
+ if "trafficleft" in self.infos[user]:
+ if self.infos[user]['trafficleft'] == 0:
+ continue
+
+ usable.append((user, data))
+
+ if not usable:
+ return None, None
+ return choice(usable)
+
+
+ def canUse(self):
+ return self.selectAccount() != (None, None)
+
+
+ def parseTraffic(self, value, unit=None): #: return bytes
+ if not unit and not isinstance(value, basestring):
+ unit = "KB"
+ return parseFileSize(value, unit)
+
+
+ def wrongPassword(self):
+ raise WrongPassword
+
+
+ def empty(self, user):
+ if user in self.infos:
+ self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user)
+
+ self.infos[user].update({"trafficleft": 0})
+ self.scheduleRefresh(user, 30 * 60)
+
+
+ def expired(self, user):
+ if user in self.infos:
+ self.logWarning(_("Account %s is expired, checking again in 1h") % user)
+
+ self.infos[user].update({"validuntil": time() - 1})
+ self.scheduleRefresh(user, 60 * 60)
+
+
+ def scheduleRefresh(self, user, time=0, force=True):
+ """ add task to refresh account info to sheduler """
+ self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time))
+ self.core.scheduler.addJob(time, self.getAccountInfo, [user, force])
+
+
+ @lock
+ def checkLogin(self, user):
+ """ checks if user is still logged in """
+ if user in self.timestamps:
+ if self.login_timeout > 0 and self.timestamps[user] + self.login_timeout * 60 < time():
+ self.logDebug("Reached login timeout for %s" % user)
+ return self.relogin(user)
+ else:
+ return True
+ else:
+ return False
diff --git a/pyload/plugin/Addon.py b/pyload/plugin/Addon.py
new file mode 100644
index 000000000..66f2c6379
--- /dev/null
+++ b/pyload/plugin/Addon.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+
+from traceback import print_exc
+
+from pyload.plugin.Plugin import Base
+from pyload.utils import has_method
+
+
+class Expose(object):
+ """ used for decoration to declare rpc services """
+
+ def __new__(cls, f, *args, **kwargs):
+ addonManager.addRPC(f.__module__, f.func_name, f.func_doc)
+ return f
+
+
+def threaded(fn):
+
+ def run(*args, **kwargs):
+ addonManager.startThread(fn, *args, **kwargs)
+
+ return run
+
+
+class Addon(Base):
+ __name = "Addon"
+ __type = "addon"
+ __version = "0.01"
+
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base addon plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de"),
+ ("RaNaN", "RaNaN@pyload.org")]
+
+
+ #: automatically register event listeners for functions, attribute will be deleted dont use it yourself
+ event_map = {}
+
+ # Deprecated alternative to event_map
+ #: List of events the plugin can handle, name the functions exactly like eventname.
+ event_list = [] #@NOTE: dont make duplicate entries in event_map
+
+
+ def __init__(self, core, manager):
+ Base.__init__(self, core)
+
+ #: Provide information in dict here, usable by API `getInfo`
+ self.info = {}
+
+ #: Callback of periodical job task, used by AddonManager
+ self.cb = None
+ self.interval = 60
+
+ #: `AddonManager`
+ self.manager = manager
+
+ # register events
+ if self.event_map:
+ for event, funcs in self.event_map.iteritems():
+ if type(funcs) in (list, tuple):
+ for f in funcs:
+ self.manager.addEvent(event, getattr(self, f))
+ else:
+ self.manager.addEvent(event, getattr(self, funcs))
+
+ # delete for various reasons
+ self.event_map = None
+
+ if self.event_list:
+ self.logWarning(_("Plugin used deprecated `event_list`, use `event_map` instead"))
+
+ for f in self.event_list:
+ self.manager.addEvent(f, getattr(self, f))
+
+ self.event_list = None
+
+ self.setup()
+
+
+ def initPeriodical(self, delay=0, threaded=False):
+ self.cb = self.core.scheduler.addJob(max(0, delay), self._periodical, [threaded], threaded=threaded)
+
+
+ def _periodical(self, threaded):
+ if self.interval < 0:
+ self.cb = None
+ return
+
+ try:
+ self.periodical()
+
+ except Exception, e:
+ self.logError(_("Error executing addon: %s") % e)
+ if self.core.debug:
+ print_exc()
+
+ self.cb = self.core.scheduler.addJob(self.interval, self._periodical, [threaded], threaded=threaded)
+
+
+ def __repr__(self):
+ return "<Addon %s>" % self.getClassName()
+
+
+ def setup(self):
+ """ more init stuff if needed """
+ pass
+
+
+ def deactivate(self):
+ """ called when addon was deactivated """
+ if has_method(self.__class__, "unload"):
+ self.logWarning(_("Deprecated method `unload`, use `deactivate` instead"))
+ self.unload()
+
+
+ def unload(self): #: Deprecated, use method `deactivate` instead
+ pass
+
+
+ def isActivated(self):
+ """ checks if addon is activated"""
+ return self.getConfig("activated")
+
+
+ # Event methods - overwrite these if needed
+
+
+ def activate(self):
+ """ called when addon was activated """
+ if has_method(self.__class__, "coreReady"):
+ self.logWarning(_("Deprecated method `coreReady`, use `activate` instead"))
+ self.coreReady()
+
+
+ def coreReady(self): #: Deprecated, use method `activate` instead
+ pass
+
+
+ def exit(self):
+ """ called by core.shutdown just before pyLoad exit """
+ if has_method(self.__class__, "coreExiting"):
+ self.coreExiting()
+
+
+ def coreExiting(self): #: Deprecated, use method `exit` instead
+ pass
+
+
+ def downloadPreparing(self, pyfile):
+ pass
+
+
+ def downloadFinished(self, pyfile):
+ pass
+
+
+ def downloadFailed(self, pyfile):
+ pass
+
+
+ def packageFinished(self, pypack):
+ pass
+
+
+ def beforeReconnecting(self, ip):
+ pass
+
+
+ def afterReconnecting(self, ip):
+ pass
+
+
+ def periodical(self):
+ pass
+
+
+ def captchaTask(self, task):
+ """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """
+ pass
+
+
+ def captchaCorrect(self, task):
+ pass
+
+
+ def captchaInvalid(self, task):
+ pass
diff --git a/pyload/plugin/Captcha.py b/pyload/plugin/Captcha.py
new file mode 100644
index 000000000..d7a506979
--- /dev/null
+++ b/pyload/plugin/Captcha.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Plugin import Base
+
+
+#@TODO: Extend Plugin class; remove all `html` args
+class Captcha(Base):
+ __name = "Captcha"
+ __type = "captcha"
+ __version = "0.25"
+
+ __description = """Base captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.key = None #: last key detected
+ super(Captcha, self).__init__(plugin.core)
+
+
+ def detect_key(self, html=None):
+ raise NotImplementedError
+
+
+ def challenge(self, key=None, html=None):
+ raise NotImplementedError
+
+
+ def result(self, server, challenge):
+ raise NotImplementedError
diff --git a/pyload/plugin/Container.py b/pyload/plugin/Container.py
new file mode 100644
index 000000000..e8e42bb2b
--- /dev/null
+++ b/pyload/plugin/Container.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from os import remove
+from os.path import basename, exists
+
+from pyload.plugin.internal.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class Container(Crypter):
+ __name = "Container"
+ __type = "container"
+ __version = "0.01"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def preprocessing(self, thread):
+ """prepare"""
+
+ self.setup()
+ self.thread = thread
+
+ self.loadToDisk()
+
+ self.decrypt(self.pyfile)
+ self.deleteTmp()
+
+ self.createPackages()
+
+
+ def loadToDisk(self):
+ """loads container to disk if its stored remotely and overwrite url,
+ or check existent on several places at disk"""
+
+ if self.pyfile.url.startswith("http"):
+ self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1]
+ content = self.load(self.pyfile.url)
+ self.pyfile.url = fs_join(self.core.config.get("general", "download_folder"), self.pyfile.name)
+ try:
+ with open(self.pyfile.url, "wb") as f:
+ f.write(content)
+ except IOError, e:
+ self.fail(str(e))
+
+ else:
+ self.pyfile.name = basename(self.pyfile.url)
+ if not exists(self.pyfile.url):
+ if exists(fs_join(pypath, self.pyfile.url)):
+ self.pyfile.url = fs_join(pypath, self.pyfile.url)
+ else:
+ self.fail(_("File not exists"))
+
+
+ def deleteTmp(self):
+ if self.pyfile.name.startswith("tmp_"):
+ remove(self.pyfile.url)
diff --git a/pyload/plugin/Crypter.py b/pyload/plugin/Crypter.py
new file mode 100644
index 000000000..3c1413f09
--- /dev/null
+++ b/pyload/plugin/Crypter.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+from urlparse import urlparse
+
+from pyload.plugin.Plugin import Plugin
+from pyload.utils import decode, safe_filename
+
+
+class Crypter(Plugin):
+ __name = "Crypter"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder", "bool", "Save package to subfolder", True), #: Overrides core.config.get("general", "folder_per_package")
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Base decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ html = None #: last html loaded
+
+
+ def __init__(self, pyfile):
+ #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder )
+ self.packages = []
+
+ #: List of urls, pyLoad will generate packagenames
+ self.urls = []
+
+ Plugin.__init__(self, pyfile)
+
+
+ def process(self, pyfile):
+ """ main method """
+
+ self.decrypt(pyfile)
+
+ if self.urls:
+ self.generatePackages()
+
+ elif not self.packages:
+ self.error(_("No link grabbed"), "decrypt")
+
+ self.createPackages()
+
+
+ def decrypt(self, pyfile):
+ raise NotImplementedError
+
+
+ def generatePackages(self):
+ """ generate new packages from self.urls """
+
+ packages = map(lambda name, links: (name, links, None), self.core.api.generatePackages(self.urls).iteritems())
+ self.packages.extend(packages)
+
+
+ def createPackages(self):
+ """ create new packages from self.packages """
+
+ package_folder = self.pyfile.package().folder
+ package_password = self.pyfile.package().password
+ package_queue = self.pyfile.package().queue
+
+ folder_per_package = self.core.config.get("general", "folder_per_package")
+ try:
+ use_subfolder = self.getConfig('use_subfolder')
+ except Exception:
+ use_subfolder = folder_per_package
+ try:
+ subfolder_per_package = self.getConfig('subfolder_per_package')
+ except Exception:
+ subfolder_per_package = True
+
+ for pack in self.packages:
+ name, links, folder = pack
+
+ self.logDebug("Parsed package: %s" % name,
+ "%d links" % len(links),
+ "Saved to folder: %s" % folder if folder else "Saved to download folder")
+
+ links = map(decode, links)
+
+ pid = self.core.api.addPackage(name, links, package_queue)
+
+ if package_password:
+ self.core.api.setPackageData(pid, {"password": package_password})
+
+ setFolder = lambda x: self.core.api.setPackageData(pid, {"folder": x or ""}) #: Workaround to do not break API addPackage method
+
+ if use_subfolder:
+ if not subfolder_per_package:
+ setFolder(package_folder)
+ self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder})
+
+ elif not folder_per_package or name != folder:
+ if not folder:
+ folder = urlparse(name).path.split("/")[-1]
+
+ setFolder(safe_filename(folder))
+ self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder})
+
+ elif folder_per_package:
+ setFolder(None)
diff --git a/pyload/plugin/Extractor.py b/pyload/plugin/Extractor.py
new file mode 100644
index 000000000..539ab624d
--- /dev/null
+++ b/pyload/plugin/Extractor.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Plugin import Base
+
+
+class ArchiveError(Exception):
+ pass
+
+
+class CRCError(Exception):
+ pass
+
+
+class PasswordError(Exception):
+ pass
+
+
+class Extractor:
+ __name = "Extractor"
+ __type = "extractor"
+ __version = "0.24"
+
+ __description = """Base extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz" , "immenz@gmx.net")]
+
+
+ EXTENSIONS = []
+ NAME = __name__
+ VERSION = ""
+ REPAIR = False
+
+
+ @classmethod
+ def isArchive(cls, filename):
+ name = os.path.basename(filename).lower()
+ return any(name.endswith(ext) for ext in cls.EXTENSIONS)
+
+
+ @classmethod
+ def isMultipart(cls, filename):
+ return False
+
+
+ @classmethod
+ def isUsable(cls):
+ """ Check if system statisfy dependencies
+ :return: boolean
+ """
+ return None
+
+
+ @classmethod
+ def getTargets(cls, files_ids):
+ """ Filter suited targets from list of filename id tuple list
+ :param files_ids: List of filepathes
+ :return: List of targets, id tuple list
+ """
+ targets = []
+ processed = []
+
+ for fname, id, fout in files_ids:
+ if cls.isArchive(fname):
+ pname = re.sub(cls.re_multipart, '', fname) if cls.isMultipart(fname) else os.path.splitext(fname)[0]
+ if pname not in processed:
+ processed.append(pname)
+ targets.append((fname, id, fout))
+ return targets
+
+
+ def __init__(self, manager, filename, out,
+ fullpath=True,
+ overwrite=False,
+ excludefiles=[],
+ renice=0,
+ delete='No',
+ keepbroken=False,
+ fid=None):
+ """ Initialize extractor for specific file """
+ self.manager = manager
+ self.filename = filename
+ self.out = out
+ self.fullpath = fullpath
+ self.overwrite = overwrite
+ self.excludefiles = excludefiles
+ self.renice = renice
+ self.delete = delete
+ self.keepbroken = keepbroken
+ self.files = [] #: Store extracted files here
+
+ pyfile = self.manager.core.files.getFile(fid) if fid else None
+ self.notifyProgress = lambda x: pyfile.setProgress(x) if pyfile else lambda x: None
+
+
+ def init(self):
+ """ Initialize additional data structures """
+ pass
+
+
+ def check(self):
+ """Quick Check by listing content of archive.
+ Raises error if password is needed, integrity is questionable or else.
+
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ """
+ raise NotImplementedError
+
+
+ def verify(self):
+ """Testing with Extractors buildt-in method
+ Raises error if password is needed, integrity is questionable or else.
+
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ """
+ raise NotImplementedError
+
+
+ def repair(self):
+ return None
+
+
+ def extract(self, password=None):
+ """Extract the archive. Raise specific errors in case of failure.
+
+ :param progress: Progress function, call this to update status
+ :param password password to use
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ :return:
+ """
+ raise NotImplementedError
+
+
+ def getDeleteFiles(self):
+ """Return list of files to delete, do *not* delete them here.
+
+ :return: List with paths of files to delete
+ """
+ return [self.filename]
+
+
+ def list(self, password=None):
+ """Populate self.files at some point while extracting"""
+ return self.files
diff --git a/pyload/plugin/Hook.py b/pyload/plugin/Hook.py
new file mode 100644
index 000000000..ccd4d635b
--- /dev/null
+++ b/pyload/plugin/Hook.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Addon import Addon, threaded
+
+
+class Hook(Addon):
+ __name = "Hook"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/Hoster.py b/pyload/plugin/Hoster.py
new file mode 100644
index 000000000..64c635c45
--- /dev/null
+++ b/pyload/plugin/Hoster.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Plugin import Plugin
+
+
+def getInfo(self):
+ # result = [ .. (name, size, status, url) .. ]
+ return
+
+
+class Hoster(Plugin):
+ __name = "Hoster"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/OCR.py b/pyload/plugin/OCR.py
new file mode 100644
index 000000000..126283f01
--- /dev/null
+++ b/pyload/plugin/OCR.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+try:
+ from PIL import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
+
+except ImportError:
+ import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
+
+import logging
+import os
+import subprocess
+# import tempfile
+
+from pyload.plugin.Plugin import Base
+from pyload.utils import fs_join
+
+
+class OCR(Base):
+ __name = "OCR"
+ __type = "ocr"
+ __version = "0.12"
+
+ __description = """OCR base plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ self.logger = logging.getLogger("log")
+
+
+ def load_image(self, image):
+ self.image = Image.open(image)
+ self.pixels = self.image.load()
+ self.result_captcha = ''
+
+
+ def deactivate(self):
+ """delete all tmp images"""
+ pass
+
+
+ def threshold(self, value):
+ self.image = self.image.point(lambda a: a * value + 10)
+
+
+ def run(self, command):
+ """Run a command"""
+
+ popen = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ popen.wait()
+ output = popen.stdout.read() + " | " + popen.stderr.read()
+ popen.stdout.close()
+ popen.stderr.close()
+ self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output))
+
+
+ def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True, pagesegmode=None):
+ # tmpTif = tempfile.NamedTemporaryFile(suffix=".tif")
+ try:
+ tmpTif = open(fs_join("tmp", "tmpTif_%s.tif" % self.getClassName()), "wb")
+ tmpTif.close()
+
+ # tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
+ tmpTxt = open(fs_join("tmp", "tmpTxt_%s.txt" % self.getClassName()), "wb")
+ tmpTxt.close()
+
+ except IOError, e:
+ self.logError(e)
+ return
+
+ self.logger.debug("save tiff")
+ self.image.save(tmpTif.name, 'TIFF')
+
+ if os.name == "nt":
+ tessparams = [os.path.join(pypath, "tesseract", "tesseract.exe")]
+ else:
+ tessparams = ["tesseract"]
+
+ tessparams.extend([os.path.abspath(tmpTif.name), os.path.abspath(tmpTxt.name).replace(".txt", "")])
+
+ if pagesegmode:
+ tessparams.extend(["-psm", str(pagesegmode)])
+
+ if subset and (digits or lowercase or uppercase):
+ # tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
+ with open(fs_join("tmp", "tmpSub_%s.subset" % self.getClassName()), "wb") as tmpSub:
+ tmpSub.write("tessedit_char_whitelist ")
+
+ if digits:
+ tmpSub.write("0123456789")
+ if lowercase:
+ tmpSub.write("abcdefghijklmnopqrstuvwxyz")
+ if uppercase:
+ tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ tmpSub.write("\n")
+ tessparams.append("nobatch")
+ tessparams.append(os.path.abspath(tmpSub.name))
+
+ self.logger.debug("run tesseract")
+ self.run(tessparams)
+ self.logger.debug("read txt")
+
+ try:
+ with open(tmpTxt.name, 'r') as f:
+ self.result_captcha = f.read().replace("\n", "")
+ except Exception:
+ self.result_captcha = ""
+
+ self.logger.debug(self.result_captcha)
+ try:
+ os.remove(tmpTif.name)
+ os.remove(tmpTxt.name)
+ if subset and (digits or lowercase or uppercase):
+ os.remove(tmpSub.name)
+ except Exception:
+ pass
+
+
+ def get_captcha(self, name):
+ raise NotImplementedError
+
+
+ def to_greyscale(self):
+ if self.image.mode != 'L':
+ self.image = self.image.convert('L')
+
+ self.pixels = self.image.load()
+
+
+ def eval_black_white(self, limit):
+ self.pixels = self.image.load()
+ w, h = self.image.size
+ for x in xrange(w):
+ for y in xrange(h):
+ if self.pixels[x, y] > limit:
+ self.pixels[x, y] = 255
+ else:
+ self.pixels[x, y] = 0
+
+
+ def clean(self, allowed):
+ pixels = self.pixels
+
+ w, h = self.image.size
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 255:
+ continue
+ # No point in processing white pixels since we only want to remove black pixel
+ count = 0
+
+ try:
+ if pixels[x - 1, y - 1] != 255:
+ count += 1
+ if pixels[x - 1, y] != 255:
+ count += 1
+ if pixels[x - 1, y + 1] != 255:
+ count += 1
+ if pixels[x, y + 1] != 255:
+ count += 1
+ if pixels[x + 1, y + 1] != 255:
+ count += 1
+ if pixels[x + 1, y] != 255:
+ count += 1
+ if pixels[x + 1, y - 1] != 255:
+ count += 1
+ if pixels[x, y - 1] != 255:
+ count += 1
+ except Exception:
+ pass
+
+ # not enough neighbors are dark pixels so mark this pixel
+ # to be changed to white
+ if count < allowed:
+ pixels[x, y] = 1
+
+ # second pass: this time set all 1's to 255 (white)
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 1:
+ pixels[x, y] = 255
+
+ self.pixels = pixels
+
+
+ def derotate_by_average(self):
+ """rotate by checking each angle and guess most suitable"""
+
+ w, h = self.image.size
+ pixels = self.pixels
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 155
+
+ highest = {}
+ counts = {}
+
+ for angle in xrange(-45, 45):
+
+ tmpimage = self.image.rotate(angle)
+
+ pixels = tmpimage.load()
+
+ w, h = self.image.size
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 255
+
+ count = {}
+
+ for x in xrange(w):
+ count[x] = 0
+ for y in xrange(h):
+ if pixels[x, y] == 155:
+ count[x] += 1
+
+ sum = 0
+ cnt = 0
+
+ for x in count.values():
+ if x != 0:
+ sum += x
+ cnt += 1
+
+ avg = sum / cnt
+ counts[angle] = cnt
+ highest[angle] = 0
+ for x in count.values():
+ if x > highest[angle]:
+ highest[angle] = x
+
+ highest[angle] = highest[angle] - avg
+
+ hkey = 0
+ hvalue = 0
+
+ for key, value in highest.iteritems():
+ if value > hvalue:
+ hkey = key
+ hvalue = value
+
+ self.image = self.image.rotate(hkey)
+ pixels = self.image.load()
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 255
+
+ if pixels[x, y] == 155:
+ pixels[x, y] = 0
+
+ self.pixels = pixels
+
+
+ def split_captcha_letters(self):
+ captcha = self.image
+ started = False
+ letters = []
+ width, height = captcha.size
+ bottomY, topY = 0, height
+ pixels = captcha.load()
+
+ for x in xrange(width):
+ black_pixel_in_col = False
+ for y in xrange(height):
+ if pixels[x, y] != 255:
+ if not started:
+ started = True
+ firstX = x
+ lastX = x
+
+ if y > bottomY:
+ bottomY = y
+ if y < topY:
+ topY = y
+ if x > lastX:
+ lastX = x
+
+ black_pixel_in_col = True
+
+ if black_pixel_in_col is False and started is True:
+ rect = (firstX, topY, lastX, bottomY)
+ new_captcha = captcha.crop(rect)
+
+ w, h = new_captcha.size
+ if w > 5 and h > 5:
+ letters.append(new_captcha)
+
+ started = False
+ bottomY, topY = 0, height
+
+ return letters
+
+
+ def correct(self, values, var=None):
+ if var:
+ result = var
+ else:
+ result = self.result_captcha
+
+ for key, item in values.iteritems():
+
+ if key.__class__ == str:
+ result = result.replace(key, item)
+ else:
+ for expr in key:
+ result = result.replace(expr, item)
+
+ if var:
+ return result
+ else:
+ self.result_captcha = result
diff --git a/pyload/plugin/Plugin.py b/pyload/plugin/Plugin.py
new file mode 100644
index 000000000..a14bb1e9c
--- /dev/null
+++ b/pyload/plugin/Plugin.py
@@ -0,0 +1,777 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from time import time, sleep
+from random import randint
+
+import os
+import re
+import urllib
+import urlparse
+
+from os import remove, makedirs, chmod, stat
+from os.path import exists, join
+
+if os.name != "nt":
+ from os import chown
+ from pwd import getpwnam
+ from grp import getgrnam
+
+from itertools import islice
+from traceback import print_exc
+
+from pyload.utils import fs_decode, fs_encode, safe_filename, fs_join, encode
+
+
+def chunks(iterable, size):
+ it = iter(iterable)
+ item = list(islice(it, size))
+ while item:
+ yield item
+ item = list(islice(it, size))
+
+
+class Abort(Exception):
+ """ raised when aborted """
+
+
+class Fail(Exception):
+ """ raised when failed """
+
+
+class Reconnect(Exception):
+ """ raised when reconnected """
+
+
+class Retry(Exception):
+ """ raised when start again from beginning """
+
+
+class SkipDownload(Exception):
+ """ raised when download should be skipped """
+
+
+class Base(object):
+ """
+ A Base class with log/config/db methods *all* plugin types can use
+ """
+
+
+ def __init__(self, core):
+ #: Core instance
+ self.core = core
+
+
+ def _log(self, type, args):
+ msg = " | ".join([encode(str(a)).strip() for a in args if a])
+ logger = getattr(self.core.log, type)
+ logger("%s: %s" % (self.getClassName(), msg or _("%s MARK" % type.upper())))
+
+
+ def logDebug(self, *args):
+ if self.core.debug:
+ return self._log("debug", args)
+
+
+ def logInfo(self, *args):
+ return self._log("info", args)
+
+
+ def logWarning(self, *args):
+ return self._log("warning", args)
+
+
+ def logError(self, *args):
+ return self._log("error", args)
+
+
+ def logCritical(self, *args):
+ return self._log("critical", args)
+
+
+ def getPluginType(self):
+ return getattr(self, "_%s__type" % self.getClassName())
+
+
+ @classmethod
+ def getClassName(cls):
+ return cls.__name__
+
+
+ def getPluginConfSection(self):
+ return "%s_%s" % (self.getClassName(), getattr(self, "_%s__type" % self.getClassName()))
+
+ #: Deprecated method
+
+
+ def setConf(self, option, value):
+ """ see `setConfig` """
+ self.setConfig(option, value)
+
+
+ def setConfig(self, option, value):
+ """ Set config value for current plugin
+
+ :param option:
+ :param value:
+ :return:
+ """
+ self.core.config.setPlugin(self.getPluginConfSection(), option, value)
+
+ #: Deprecated method
+
+
+ def getConf(self, option):
+ """ see `getConfig` """
+ return self.core.config.getPlugin(self.getPluginConfSection(), option)
+
+
+ def getConfig(self, option):
+ """ Returns config value for current plugin
+
+ :param option:
+ :return:
+ """
+ return self.core.config.getPlugin(self.getPluginConfSection(), option)
+
+
+ def setStorage(self, key, value):
+ """ Saves a value persistently to the database """
+ self.core.db.setStorage(self.getPluginConfSection(), key, value)
+
+
+ def store(self, key, value):
+ """ same as `setStorage` """
+ self.core.db.setStorage(self.getPluginConfSection(), key, value)
+
+
+ def getStorage(self, key=None, default=None):
+ """ Retrieves saved value or dict of all saved entries if key is None """
+ if key:
+ return self.core.db.getStorage(self.getPluginConfSection(), key) or default
+ return self.core.db.getStorage(self.getPluginConfSection(), 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.getClassName(), key)
+
+
+class Plugin(Base):
+ """
+ Base plugin for hoster/crypter.
+ Overwrite `process` / `decrypt` in your subclassed plugin.
+ """
+ __name = "Plugin"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de")]
+
+ info = {} #: file info dict
+
+
+ def __init__(self, pyfile):
+ Base.__init__(self, pyfile.m.core)
+
+ #: engage wan reconnection
+ self.wantReconnect = False
+
+ #: enable simultaneous processing of multiple downloads
+ self.multiDL = True
+ self.limitDL = 0
+
+ #: chunk limit
+ self.chunkLimit = 1
+ self.resumeDownload = False
+
+ #: time() + wait in seconds
+ self.waitUntil = 0
+ self.waiting = False
+
+ #: captcha reader instance
+ self.ocr = None
+
+ #: account handler instance, see :py:class:`Account`
+ self.account = pyfile.m.core.accountManager.getAccountPlugin(self.getClassName())
+
+ #: premium status
+ self.premium = False
+ #: username/login
+ self.user = None
+
+ if self.account and not self.account.canUse():
+ self.account = None
+
+ if self.account:
+ self.user, data = self.account.selectAccount()
+ #: Browser instance, see `network.Browser`
+ self.req = self.account.getAccountRequest(self.user)
+ self.chunkLimit = -1 #: chunk limit, -1 for unlimited
+ #: enables resume (will be ignored if server dont accept chunks)
+ self.resumeDownload = True
+ self.multiDL = True #: every hoster with account should provide multiple downloads
+ #: premium status
+ self.premium = self.account.isPremium(self.user)
+ else:
+ self.req = pyfile.m.core.requestFactory.getRequest(self.getClassName())
+
+ #: associated pyfile instance, see `PyFile`
+ self.pyfile = pyfile
+
+ self.thread = None #: holds thread in future
+
+ #: location where the last call to download was saved
+ self.lastDownload = ""
+ #: re match of the last call to `checkDownload`
+ self.lastCheck = None
+
+ #: js engine, see `JsEngine`
+ self.js = self.core.js
+
+ #: captcha task
+ self.cTask = None
+
+ self.html = None #@TODO: Move to hoster class in 0.4.10
+ self.retries = 0
+
+ self.init()
+
+
+ def getChunkCount(self):
+ if self.chunkLimit <= 0:
+ return self.core.config.get("download", "chunks")
+ return min(self.core.config.get("download", "chunks"), self.chunkLimit)
+
+
+ def __call__(self):
+ return self.getClassName()
+
+
+ def init(self):
+ """initialize the plugin (in addition to `__init__`)"""
+ pass
+
+
+ def setup(self):
+ """ setup for enviroment and other things, called before downloading (possibly more than one time)"""
+ pass
+
+
+ def preprocessing(self, thread):
+ """ handles important things to do before starting """
+ self.thread = thread
+
+ if self.account:
+ self.account.checkLogin(self.user)
+ else:
+ self.req.clearCookies()
+
+ self.setup()
+
+ self.pyfile.setStatus("starting")
+
+ return self.process(self.pyfile)
+
+
+ def process(self, pyfile):
+ """the 'main' method of every plugin, you **have to** overwrite it"""
+ raise NotImplementedError
+
+
+ def resetAccount(self):
+ """ dont use account and retry download """
+ self.account = None
+ self.req = self.core.requestFactory.getRequest(self.getClassName())
+ self.retry()
+
+
+ def checksum(self, local_file=None):
+ """
+ return codes:
+ 0 - checksum ok
+ 1 - checksum wrong
+ 5 - can't get checksum
+ 10 - not implemented
+ 20 - unknown error
+ """
+ #@TODO checksum check addon
+
+ return True, 10
+
+
+ def setReconnect(self, reconnect):
+ reconnect = bool(reconnect)
+ self.logDebug("Set wantReconnect to: %s (previous: %s)" % (reconnect, self.wantReconnect))
+ self.wantReconnect = reconnect
+
+
+ def setWait(self, seconds, reconnect=None):
+ """Set a specific wait time later used with `wait`
+
+ :param seconds: wait time in seconds
+ :param reconnect: True if a reconnect would avoid wait time
+ """
+ wait_time = int(seconds) + 1
+ wait_until = time() + wait_time
+
+ self.logDebug("Set waitUntil to: %f (previous: %f)" % (wait_until, self.pyfile.waitUntil),
+ "Wait: %d seconds" % wait_time)
+
+ self.pyfile.waitUntil = wait_until
+
+ if reconnect is not None:
+ self.setReconnect(reconnect)
+
+
+ def wait(self, seconds=None, reconnect=None):
+ """ waits the time previously set """
+
+ pyfile = self.pyfile
+
+ if seconds is not None:
+ self.setWait(seconds)
+
+ if reconnect is not None:
+ self.setReconnect(reconnect)
+
+ self.waiting = True
+
+ status = pyfile.status
+ pyfile.setStatus("waiting")
+
+ self.logInfo(_("Wait: %d seconds") % (pyfile.waitUntil - time()),
+ _("Reconnect: %s") % self.wantReconnect)
+
+ if self.account:
+ self.logDebug("Ignore reconnection due account logged")
+
+ while pyfile.waitUntil > time():
+ if pyfile.abort:
+ self.abort()
+
+ sleep(1)
+ else:
+ while pyfile.waitUntil > time():
+ self.thread.m.reconnecting.wait(2)
+
+ if pyfile.abort:
+ self.abort()
+
+ if self.thread.m.reconnecting.isSet():
+ self.waiting = False
+ self.wantReconnect = False
+ raise Reconnect
+
+ sleep(1)
+
+ self.waiting = False
+
+ pyfile.status = status
+
+
+ def fail(self, reason):
+ """ fail and give reason """
+ raise Fail(reason)
+
+
+ def abort(self, reason=""):
+ """ abort and give reason """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Abort
+
+
+ def error(self, reason="", type=""):
+ if not reason and not type:
+ type = "unknown"
+
+ msg = _("%s error") % _(type.strip().capitalize()) if type else _("Error")
+ msg += ": " + reason.strip() if reason else ""
+ msg += _(" | Plugin may be out of date")
+
+ raise Fail(msg)
+
+
+ def offline(self, reason=""):
+ """ fail and indicate file is offline """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Fail("offline")
+
+
+ def tempOffline(self, reason=""):
+ """ fail and indicates file ist temporary offline, the core may take consequences """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Fail("temp. offline")
+
+
+ def retry(self, max_tries=5, wait_time=1, reason=""):
+ """Retries and begin again from the beginning
+
+ :param max_tries: number of maximum retries
+ :param wait_time: time to wait in seconds
+ :param reason: reason for retrying, will be passed to fail if max_tries reached
+ """
+ if 0 < max_tries <= self.retries:
+ self.error(reason or _("Max retries reached"), "retry")
+
+ self.wait(wait_time, False)
+
+ self.retries += 1
+ raise Retry(reason)
+
+
+ def invalidCaptcha(self):
+ self.logError(_("Invalid captcha"))
+ if self.cTask:
+ self.cTask.invalid()
+
+
+ def correctCaptcha(self):
+ self.logInfo(_("Correct captcha"))
+ if self.cTask:
+ self.cTask.correct()
+
+
+ def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
+ result_type='textual', timeout=290):
+ """ 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(".", "")
+
+ with open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.getClassName(), id, imgtype)), "wb") as tmpCaptcha:
+ tmpCaptcha.write(img)
+
+ has_plugin = self.getClassName() in self.core.pluginManager.ocrPlugins
+
+ if self.core.captcha:
+ Ocr = self.core.pluginManager.loadClass("ocr", self.getClassName())
+ else:
+ Ocr = None
+
+ if Ocr and not forceUser:
+ sleep(randint(3000, 5000) / 1000.0)
+ if self.pyfile.abort:
+ self.abort()
+
+ ocr = Ocr()
+ result = ocr.get_captcha(tmpCaptcha.name)
+ else:
+ captchaManager = self.core.captchaManager
+ task = captchaManager.newTask(img, imgtype, tmpCaptcha.name, result_type)
+ self.cTask = task
+ captchaManager.handleCaptcha(task, timeout)
+
+ while task.isWaiting():
+ if self.pyfile.abort:
+ captchaManager.removeTask(task)
+ self.abort()
+ sleep(1)
+
+ captchaManager.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 appropiate time by any of the plugins"))
+
+ result = task.result
+ self.logDebug("Received captcha result: %s" % result)
+
+ if not self.core.debug:
+ try:
+ remove(tmpCaptcha.name)
+ except Exception:
+ pass
+
+ return result
+
+
+ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False, follow_location=True, save_cookies=True):
+ """Load content at url and returns it
+
+ :param url:
+ :param get:
+ :param post:
+ :param ref:
+ :param cookies:
+ :param just_header: If True only the header will be retrieved and returned as dict
+ :param decode: Wether to decode the output according to http header, should be True in most cases
+ :param follow_location: If True follow location else not
+ :param save_cookies: If True saves received cookies else discard them
+ :return: Loaded content
+ """
+ if self.pyfile.abort:
+ self.abort()
+
+ if not url:
+ self.fail(_("No url given"))
+
+ url = urllib.unquote(encode(url).strip()) #@NOTE: utf8 vs decode -> please use decode attribute in all future plugins
+
+ if self.core.debug:
+ self.logDebug("Load url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")])
+
+ res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode, follow_location=follow_location, save_cookies=save_cookies)
+
+ if decode:
+ res = encode(res)
+
+ if self.core.debug:
+ from inspect import currentframe
+
+ frame = currentframe()
+ framefile = fs_join("tmp", self.getClassName(), "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
+ try:
+ if not exists(join("tmp", self.getClassName())):
+ makedirs(join("tmp", self.getClassName()))
+
+ with open(framefile, "wb") as f:
+ del frame #: delete the frame or it wont be cleaned
+ f.write(res)
+ except IOError, e:
+ self.logError(e)
+
+ 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.strip().lower()
+ 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 download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False):
+ """Downloads the content at url to download folder
+
+ :param url:
+ :param get:
+ :param post:
+ :param ref:
+ :param cookies:
+ :param disposition: if True and server provides content-disposition header\
+ the filename will be changed if needed
+ :return: The location where the file was saved
+ """
+ if self.pyfile.abort:
+ self.abort()
+
+ if not url:
+ self.fail(_("No url given"))
+
+ url = urllib.unquote(encode(url).strip())
+
+ if self.core.debug:
+ self.logDebug("Download url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")])
+
+ self.checkForSameFiles()
+
+ self.pyfile.setStatus("downloading")
+
+ if disposition:
+ self.pyfile.name = urlparse.urlparse(url).path.split('/')[-1] or self.pyfile.name
+
+ download_folder = self.core.config.get("general", "download_folder")
+
+ location = fs_join(download_folder, self.pyfile.package().folder)
+
+ if not exists(location):
+ try:
+ makedirs(location, int(self.core.config.get("permission", "folder"), 8))
+
+ if self.core.config.get("permission", "change_dl") and os.name != "nt":
+ uid = getpwnam(self.core.config.get("permission", "user"))[2]
+ gid = getgrnam(self.core.config.get("permission", "group"))[2]
+ chown(location, uid, gid)
+
+ except Exception, e:
+ self.fail(e)
+
+ # convert back to unicode
+ location = fs_decode(location)
+ name = safe_filename(self.pyfile.name)
+
+ filename = join(location, name)
+
+ self.core.addonManager.dispatchEvent("download-start", self.pyfile, url, filename)
+
+ try:
+ newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies,
+ chunks=self.getChunkCount(), resume=self.resumeDownload,
+ progressNotify=self.pyfile.setProgress, disposition=disposition)
+ finally:
+ self.pyfile.size = self.req.size
+
+ if newname:
+ newname = urlparse.urlparse(newname).path.split('/')[-1]
+
+ if disposition and newname != name:
+ self.logInfo(_("%(name)s saved as %(newname)s") % {"name": name, "newname": newname})
+ self.pyfile.name = newname
+ filename = join(location, newname)
+
+ fs_filename = fs_encode(filename)
+
+ if self.core.config.get("permission", "change_file"):
+ try:
+ chmod(fs_filename, int(self.core.config.get("permission", "file"), 8))
+ except Exception, e:
+ self.logWarning(_("Setting file mode failed"), e)
+
+ if self.core.config.get("permission", "change_dl") and os.name != "nt":
+ try:
+ uid = getpwnam(self.core.config.get("permission", "user"))[2]
+ gid = getgrnam(self.core.config.get("permission", "group"))[2]
+ chown(fs_filename, uid, gid)
+
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), e)
+
+ self.lastDownload = filename
+ return self.lastDownload
+
+
+ def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0):
+ """ checks the content of the last downloaded file, re match is saved to `lastCheck`
+
+ :param rules: dict with names and rules to match (compiled regexp or strings)
+ :param api_size: expected file size
+ :param max_size: if the file is larger then it wont be checked
+ :param delete: delete if matched
+ :param read_size: amount of bytes to read from files larger then max_size
+ :return: dictionary key of the first rule that matched
+ """
+ lastDownload = fs_encode(self.lastDownload)
+ if not exists(lastDownload):
+ return None
+
+ size = stat(lastDownload)
+ size = size.st_size
+
+ if api_size and api_size <= size:
+ return None
+ elif size > max_size and not read_size:
+ return None
+ self.logDebug("Download Check triggered")
+
+ with open(lastDownload, "rb") as f:
+ content = f.read(read_size if read_size else -1)
+
+ # produces encoding errors, better log to other file in the future?
+ # self.logDebug("Content: %s" % content)
+ for name, rule in rules.iteritems():
+ if isinstance(rule, basestring):
+ if rule in content:
+ if delete:
+ remove(lastDownload)
+ return name
+ elif hasattr(rule, "search"):
+ m = rule.search(content)
+ if m:
+ if delete:
+ remove(lastDownload)
+ self.lastCheck = m
+ return name
+
+
+ def getPassword(self):
+ """ get the password the user provided in the package"""
+ password = self.pyfile.package().password
+ if not password:
+ return ""
+ return password
+
+
+ def checkForSameFiles(self, starting=False):
+ """ checks if same file was/is downloaded within same package
+
+ :param starting: indicates that the current download is going to start
+ :raises SkipDownload:
+ """
+
+ pack = self.pyfile.package()
+
+ for pyfile in self.core.files.cache.values():
+ if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder:
+ if pyfile.status in (0, 12): #: finished or downloading
+ raise SkipDownload(pyfile.pluginname)
+ elif pyfile.status in (5, 7) and starting: #: a download is waiting/starting and was appenrently started before
+ raise SkipDownload(pyfile.pluginname)
+
+ download_folder = self.core.config.get("general", "download_folder")
+ location = fs_join(download_folder, pack.folder, self.pyfile.name)
+
+ if starting and self.core.config.get("download", "skip_existing") and exists(location):
+ size = os.stat(location).st_size
+ if size >= self.pyfile.size:
+ raise SkipDownload("File exists")
+
+ pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name)
+ if pyfile:
+ if exists(location):
+ raise SkipDownload(pyfile[0])
+
+ self.logDebug("File %s not skipped, because it does not exists." % self.pyfile.name)
+
+
+ def clean(self):
+ """ clean everything and remove references """
+ if hasattr(self, "pyfile"):
+ del self.pyfile
+
+ if hasattr(self, "req"):
+ self.req.close()
+ del self.req
+
+ if hasattr(self, "thread"):
+ del self.thread
+
+ if hasattr(self, "html"):
+ del self.html
diff --git a/pyload/plugin/__init__.py b/pyload/plugin/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/account/AlldebridCom.py b/pyload/plugin/account/AlldebridCom.py
new file mode 100644
index 000000000..efc5753f8
--- /dev/null
+++ b/pyload/plugin/account/AlldebridCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import xml.dom.minidom as dom
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugin.Account import Account
+
+
+class AlldebridCom(Account):
+ __name = "AlldebridCom"
+ __type = "account"
+ __version = "0.23"
+
+ __description = """AllDebrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ html = req.load("http://www.alldebrid.com/account/")
+ soup = BeautifulSoup(html)
+
+ # Try to parse expiration date directly from the control panel page (better accuracy)
+ try:
+ time_text = soup.find('div', attrs={'class': 'remaining_time_text'}).strong.string
+
+ self.logDebug("Account expires in: %s" % time_text)
+
+ p = re.compile('\d+')
+ exp_data = p.findall(time_text)
+ exp_time = time.time() + int(exp_data[0]) * 24 * 60 * 60 + int(
+ exp_data[1]) * 60 * 60 + (int(exp_data[2]) - 1) * 60
+
+ # Get expiration date from API
+ except Exception:
+ data = self.getAccountData(user)
+ html = req.load("http://www.alldebrid.com/api.php",
+ get={'action': "info_user", 'login': user, 'pw': data['password']})
+
+ self.logDebug(html)
+
+ xml = dom.parseString(html)
+ exp_time = time.time() + int(xml.getElementsByTagName("date")[0].childNodes[0].nodeValue) * 24 * 60 * 60
+
+ account_info = {"validuntil": exp_time, "trafficleft": -1}
+ return account_info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.alldebrid.com/register/",
+ get={'action' : "login",
+ 'login_login' : user,
+ 'login_password': data['password']},
+ decode=True)
+
+ if "This login doesn't exist" in html \
+ or "The password is not valid" in html \
+ or "Invalid captcha" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/BackinNet.py b/pyload/plugin/account/BackinNet.py
new file mode 100644
index 000000000..79d46c761
--- /dev/null
+++ b/pyload/plugin/account/BackinNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class BackinNet(XFSAccount):
+ __name = "BackinNet"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Backin.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "backin.net"
diff --git a/pyload/plugin/account/BillionuploadsCom.py b/pyload/plugin/account/BillionuploadsCom.py
new file mode 100644
index 000000000..982a2cc34
--- /dev/null
+++ b/pyload/plugin/account/BillionuploadsCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class BillionuploadsCom(XFSAccount):
+ __name = "BillionuploadsCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Billionuploads.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "billionuploads.com"
diff --git a/pyload/plugin/account/BitshareCom.py b/pyload/plugin/account/BitshareCom.py
new file mode 100644
index 000000000..8a64d3b4c
--- /dev/null
+++ b/pyload/plugin/account/BitshareCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class BitshareCom(Account):
+ __name = "BitshareCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Bitshare account plugin"""
+ __license = "GPLv3"
+ __authors = [("Paul King", "")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://bitshare.com/mysettings.html")
+
+ if "\"http://bitshare.com/myupgrade.html\">Free" in html:
+ return {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ if not '<input type="checkbox" name="directdownload" checked="checked" />' in html:
+ self.logWarning(_("Activate direct Download in your Bitshare Account"))
+
+ return {"validuntil": -1, "trafficleft": -1, "premium": True}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://bitshare.com/login.html",
+ post={"user": user, "password": data['password'], "submit": "Login"},
+ decode=True)
+
+ if "login" in req.lastEffectiveURL:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CatShareNet.py b/pyload/plugin/account/CatShareNet.py
new file mode 100644
index 000000000..8f821413f
--- /dev/null
+++ b/pyload/plugin/account/CatShareNet.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class CatShareNet(Account):
+ __name = "CatShareNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """CatShareNet account plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", "")]
+
+
+ PREMIUM_PATTERN = r'<a href="/premium">Konto:[\s\n]*Premium'
+ VALID_UNTIL_PATTERN = r'>Konto premium.*?<strong>(.*?)</strong></span>'
+ TRAFFIC_LEFT_PATTERN = r'<a href="/premium">([0-9.]+ [kMG]B)'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = -1
+ trafficleft = -1
+
+ html = req.load("http://catshare.net/", decode=True)
+
+ if re.search(self.PREMIUM_PATTERN, html):
+ premium = True
+
+ try:
+ expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
+
+ except Exception:
+ pass
+
+ try:
+ trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
+
+ except Exception:
+ pass
+
+ return {'premium': premium, 'trafficleft': trafficleft, 'validuntil': validuntil}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://catshare.net/login",
+ post={'user_email': user,
+ 'user_password': data['password'],
+ 'remindPassword': 0,
+ 'user[submit]': "Login"},
+ decode=True)
+
+ if not '<a href="/logout">Wyloguj</a>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CloudzillaTo.py b/pyload/plugin/account/CloudzillaTo.py
new file mode 100644
index 000000000..bee7c5a17
--- /dev/null
+++ b/pyload/plugin/account/CloudzillaTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class CloudzillaTo(Account):
+ __name = "CloudzillaTo"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Cloudzilla.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'<h2>account type</h2>\s*Premium Account'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.cloudzilla.to/")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ return {'validuntil': -1, 'trafficleft': -1, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.cloudzilla.to/",
+ post={'lusername': user,
+ 'lpassword': data['password'],
+ 'w' : "dologin"},
+ decode=True)
+
+ if "ERROR" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CramitIn.py b/pyload/plugin/account/CramitIn.py
new file mode 100644
index 000000000..ccd291776
--- /dev/null
+++ b/pyload/plugin/account/CramitIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class CramitIn(XFSAccount):
+ __name = "CramitIn"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Cramit.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "cramit.in"
diff --git a/pyload/plugin/account/CzshareCom.py b/pyload/plugin/account/CzshareCom.py
new file mode 100644
index 000000000..4913ed88f
--- /dev/null
+++ b/pyload/plugin/account/CzshareCom.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class CzshareCom(Account):
+ __name = "CzshareCom"
+ __type = "account"
+ __version = "0.18"
+
+ __description = """Czshare.com account plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([\d ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ html = req.load("http://sdilej.cz/prehled_kreditu/")
+
+ try:
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ trafficleft = self.parseTraffic(m.group(1).replace(' ', '').replace(',', '.')) + m.group(2)
+ validuntil = time.mktime(time.strptime(m.group(3), '%d.%m.%y %H:%M'))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ premium = True
+
+ return {'premium' : premium,
+ 'validuntil' : validuntil,
+ 'trafficleft': trafficleft}
+
+
+ def login(self, user, data, req):
+ html = req.load('https://sdilej.cz/index.php',
+ post={"Prihlasit": "Prihlasit",
+ "login-password": data['password'],
+ "login-name": user},
+ decode=True)
+
+ if '<div class="login' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DebridItaliaCom.py b/pyload/plugin/account/DebridItaliaCom.py
new file mode 100644
index 000000000..49959d5c3
--- /dev/null
+++ b/pyload/plugin/account/DebridItaliaCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class DebridItaliaCom(Account):
+ __name = "DebridItaliaCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Debriditalia.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ WALID_UNTIL_PATTERN = r'Premium valid till: (.+?) \|'
+
+
+ def loadAccountInfo(self, user, req):
+ info = {'premium': False, 'validuntil': None, 'trafficleft': None}
+ html = req.load("http://debriditalia.com/")
+
+ if 'Account premium not activated' not in html:
+ m = re.search(self.WALID_UNTIL_PATTERN, html)
+ if m:
+ validuntil = time.mktime(time.strptime(m.group(1), "%d/%m/%Y %H:%M"))
+ info = {'premium': True, 'validuntil': validuntil, 'trafficleft': -1}
+ else:
+ self.logError(_("Unable to retrieve account information"))
+
+ return info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://debriditalia.com/login.php",
+ get={'u': user, 'p': data['password']},
+ decode=True)
+
+ if 'NO' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DepositfilesCom.py b/pyload/plugin/account/DepositfilesCom.py
new file mode 100644
index 000000000..4e09ee2ed
--- /dev/null
+++ b/pyload/plugin/account/DepositfilesCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class DepositfilesCom(Account):
+ __name = "DepositfilesCom"
+ __type = "account"
+ __version = "0.32"
+
+ __description = """Depositfiles.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://dfiles.eu/de/gold/")
+ validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", html).group(1)
+
+ validuntil = time.mktime(time.strptime(validuntil, "%Y-%m-%d %H:%M:%S"))
+
+ return {"validuntil": validuntil, "trafficleft": -1}
+
+
+ def login(self, user, data, req):
+ html = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
+ post={"login": user, "password": data['password']},
+ decode=True)
+
+ if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DropboxCom.py b/pyload/plugin/account/DropboxCom.py
new file mode 100644
index 000000000..80dfd6cea
--- /dev/null
+++ b/pyload/plugin/account/DropboxCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DropboxCom(SimpleHoster):
+ __name = "DropboxCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?dropbox\.com/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dropbox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<title>Dropbox - (?P<N>.+?)<'
+ SIZE_PATTERN = r'&nbsp;&middot;&nbsp; (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'<title>Dropbox - (404|Shared link error)<'
+
+ COOKIES = [("dropbox.com", "lang", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ self.download(pyfile.url, get={'dl': "1"})
diff --git a/pyload/plugin/account/EasybytezCom.py b/pyload/plugin/account/EasybytezCom.py
new file mode 100644
index 000000000..c1f641669
--- /dev/null
+++ b/pyload/plugin/account/EasybytezCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class EasybytezCom(XFSAccount):
+ __name = "EasybytezCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """EasyBytez.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "easybytez.com"
diff --git a/pyload/plugin/account/EuroshareEu.py b/pyload/plugin/account/EuroshareEu.py
new file mode 100644
index 000000000..e1e037bf9
--- /dev/null
+++ b/pyload/plugin/account/EuroshareEu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class EuroshareEu(Account):
+ __name = "EuroshareEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Euroshare.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ self.relogin(user)
+ html = req.load("http://euroshare.eu/customer-zone/settings/")
+
+ m = re.search('id="input_expire_date" value="(\d+\.\d+\.\d+ \d+:\d+)"', html)
+ if m is None:
+ premium, validuntil = False, -1
+ else:
+ premium = True
+ validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y %H:%M"))
+
+ return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load('http://euroshare.eu/customer-zone/login/',
+ post={"trvale": "1",
+ "login": user,
+ "password": data['password']},
+ decode=True)
+
+ if u">Nesprávne prihlasovacie meno alebo heslo" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/ExashareCom.py b/pyload/plugin/account/ExashareCom.py
new file mode 100644
index 000000000..e61c2f12d
--- /dev/null
+++ b/pyload/plugin/account/ExashareCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class ExashareCom(XFSAccount):
+ __name = "ExashareCom"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Exashare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "exashare.com"
diff --git a/pyload/plugin/account/FastixRu.py b/pyload/plugin/account/FastixRu.py
new file mode 100644
index 000000000..ead4e63aa
--- /dev/null
+++ b/pyload/plugin/account/FastixRu.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FastixRu(Account):
+ __name = "FastixRu"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Fastix account plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ html = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"}))
+
+ points = html['points']
+ trafficleft = float(points) * 1024 ** 2 / 1000
+
+ if points > 0:
+ account_info = {"validuntil": -1, "trafficleft": trafficleft}
+ else:
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://fastix.ru/api_v2/",
+ get={'sub': "get_apikey", 'email': user, 'password': data['password']})
+
+ api = json_loads(html)
+ api = api['apikey']
+
+ data['api'] = api
+
+ if "error_code" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FastshareCz.py b/pyload/plugin/account/FastshareCz.py
new file mode 100644
index 000000000..f1ed9d634
--- /dev/null
+++ b/pyload/plugin/account/FastshareCz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+from pyload.utils import parseFileSize
+
+
+class FastshareCz(Account):
+ __name = "FastshareCz"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Fastshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CREDIT_PATTERN = r'Credit\s*:\s*</td>\s*<td>(.+?)\s*<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = -1
+ trafficleft = None
+ premium = False
+
+ html = req.load("http://www.fastshare.cz/user", decode=True)
+
+ m = re.search(self.CREDIT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ premium = bool(trafficleft)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("fastshare.cz", "lang", "en")
+
+ req.load('http://www.fastshare.cz/login') #: Do not remove or it will not login
+
+ html = req.load("http://www.fastshare.cz/sql.php",
+ post={'login': user, 'heslo': data['password']},
+ decode=True)
+
+ if ">Wrong username or password" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/File4SafeCom.py b/pyload/plugin/account/File4SafeCom.py
new file mode 100644
index 000000000..1c7e00fcf
--- /dev/null
+++ b/pyload/plugin/account/File4SafeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class File4SafeCom(XFSAccount):
+ __name = "File4SafeCom"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """File4Safe.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ HOSTER_DOMAIN = "file4safe.com"
+
+ LOGIN_FAIL_PATTERN = r'input_login'
diff --git a/pyload/plugin/account/FileParadoxIn.py b/pyload/plugin/account/FileParadoxIn.py
new file mode 100644
index 000000000..0e103c4e7
--- /dev/null
+++ b/pyload/plugin/account/FileParadoxIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FileParadoxIn(XFSAccount):
+ __name = "FileParadoxIn"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """FileParadox.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "fileparadox.in"
diff --git a/pyload/plugin/account/FilecloudIo.py b/pyload/plugin/account/FilecloudIo.py
new file mode 100644
index 000000000..b07fe981a
--- /dev/null
+++ b/pyload/plugin/account/FilecloudIo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FilecloudIo(Account):
+ __name = "FilecloudIo"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """FilecloudIo account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ # It looks like the first API request always fails, so we retry 5 times, it should work on the second try
+ for _i in xrange(5):
+ rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
+ post={"username": user, "password": self.getAccountData(user)['password']})
+ rep = json_loads(rep)
+ if rep['status'] == 'ok':
+ break
+ elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password':
+ self.logError(_("Wrong username or password"))
+ return {"valid": False, "premium": False}
+ else:
+ return {"premium": False}
+
+ akey = rep['akey']
+ self.accounts[user]['akey'] = akey #: Saved for hoster plugin
+ rep = req.load("http://api.filecloud.io/api-fetch_account_details.api",
+ post={"akey": akey})
+ rep = json_loads(rep)
+
+ if rep['is_premium'] == 1:
+ return {"validuntil": float(rep['premium_until']), "trafficleft": -1}
+ else:
+ return {"premium": False}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("secure.filecloud.io", "lang", "en")
+ html = req.load('https://secure.filecloud.io/user-login.html')
+
+ if not hasattr(self, "form_data"):
+ self.form_data = {}
+
+ self.form_data['username'] = user
+ self.form_data['password'] = data['password']
+
+ html = req.load('https://secure.filecloud.io/user-login_p.html',
+ post=self.form_data,
+ multipart=True)
+
+ if "you have successfully logged in" not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FilefactoryCom.py b/pyload/plugin/account/FilefactoryCom.py
new file mode 100644
index 000000000..5c80c6e1c
--- /dev/null
+++ b/pyload/plugin/account/FilefactoryCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilefactoryCom(Account):
+ __name = "FilefactoryCom"
+ __type = "account"
+ __version = "0.15"
+
+ __description = """Filefactory.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ VALID_UNTIL_PATTERN = r'Premium valid until: <strong>(?P<D>\d{1,2})\w{1,2} (?P<M>\w{3}), (?P<Y>\d{4})</strong>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.filefactory.com/account/")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<D> \g<M> \g<Y>', m.group(0))
+ validuntil = time.mktime(time.strptime(validuntil, "%d %b %Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+
+ def login(self, user, data, req):
+ req.http.c.setopt(pycurl.REFERER, "http://www.filefactory.com/member/login.php")
+
+ html = req.load("http://www.filefactory.com/member/signin.php",
+ post={"loginEmail" : user,
+ "loginPassword": data['password'],
+ "Submit" : "Sign In"})
+
+ if req.lastEffectiveURL != "http://www.filefactory.com/account/":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FilejungleCom.py b/pyload/plugin/account/FilejungleCom.py
new file mode 100644
index 000000000..19d343372
--- /dev/null
+++ b/pyload/plugin/account/FilejungleCom.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilejungleCom(Account):
+ __name = "FilejungleCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """Filejungle.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = 60
+
+ URL = "http://filejungle.com/"
+ TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ \w+ \d+)<br'
+ LOGIN_FAILED_PATTERN = r'<span htmlfor="loginUser(Name|Password)" generated="true" class="fail_info">'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load(self.URL + "dashboard.php")
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = time.mktime(time.strptime(m.group(1), "%d %b %Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+
+ def login(self, user, data, req):
+ html = req.load(self.URL + "login.php",
+ post={"loginUserName": user,
+ "loginUserPassword": data['password'],
+ "loginFormSubmit": "Login",
+ "recaptcha_challenge_field": "",
+ "recaptcha_response_field": "",
+ "recaptcha_shortencode_field": ""},
+ decode=True)
+
+ if re.search(self.LOGIN_FAILED_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FileomCom.py b/pyload/plugin/account/FileomCom.py
new file mode 100644
index 000000000..2868e49e6
--- /dev/null
+++ b/pyload/plugin/account/FileomCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FileomCom(XFSAccount):
+ __name = "FileomCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Fileom.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "fileom.com"
diff --git a/pyload/plugin/account/FilerNet.py b/pyload/plugin/account/FilerNet.py
new file mode 100644
index 000000000..b6baddacc
--- /dev/null
+++ b/pyload/plugin/account/FilerNet.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilerNet(Account):
+ __name = "FilerNet"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Filer.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ TOKEN_PATTERN = r'_csrf_token" value="(.+?)" />'
+ WALID_UNTIL_PATTERN = r'Der Premium-Zugang ist gÃŒltig bis (.+)\.\s*</td>'
+ TRAFFIC_PATTERN = r'Traffic</th>\s*<td>([^<]+)</td>'
+ FREE_PATTERN = r'Account Status</th>\s*<td>\s*Free'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://filer.net/profile")
+
+ # Free user
+ if re.search(self.FREE_PATTERN, html):
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+ until = re.search(self.WALID_UNTIL_PATTERN, html)
+ traffic = re.search(self.TRAFFIC_PATTERN, html)
+
+ if until and traffic:
+ validuntil = time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S"))
+ trafficleft = self.parseTraffic(traffic.group(1))
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+ else:
+ self.logError(_("Unable to retrieve account information"))
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+
+ def login(self, user, data, req):
+ html = req.load("https://filer.net/login")
+
+ token = re.search(self.TOKEN_PATTERN, html).group(1)
+
+ html = req.load("https://filer.net/login_check",
+ post={"_username": user,
+ "_password": data['password'],
+ "_remember_me": "on",
+ "_csrf_token": token,
+ "_target_path": "https://filer.net/"},
+ decode=True)
+
+ if 'Logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FilerioCom.py b/pyload/plugin/account/FilerioCom.py
new file mode 100644
index 000000000..d222fa78b
--- /dev/null
+++ b/pyload/plugin/account/FilerioCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FilerioCom(XFSAccount):
+ __name = "FilerioCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """FileRio.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "filerio.in"
diff --git a/pyload/plugin/account/FilesMailRu.py b/pyload/plugin/account/FilesMailRu.py
new file mode 100644
index 000000000..03b56be44
--- /dev/null
+++ b/pyload/plugin/account/FilesMailRu.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class FilesMailRu(Account):
+ __name = "FilesMailRu"
+ __type = "account"
+ __version = "0.11"
+
+ __description = """Filesmail.ru account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ return {"validuntil": None, "trafficleft": None}
+
+
+ def login(self, user, data, req):
+ user, domain = user.split("@")
+
+ html = req.load("http://swa.mail.ru/cgi-bin/auth",
+ post={"Domain": domain,
+ "Login": user,
+ "Password": data['password'],
+ "Page": "http://files.mail.ru/"},
+ decode=True)
+
+ if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FileserveCom.py b/pyload/plugin/account/FileserveCom.py
new file mode 100644
index 000000000..d68285a33
--- /dev/null
+++ b/pyload/plugin/account/FileserveCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FileserveCom(Account):
+ __name = "FileserveCom"
+ __type = "account"
+ __version = "0.20"
+
+ __description = """Fileserve.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+
+ html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(html)
+
+ if res['type'] == "premium":
+ validuntil = time.mktime(time.strptime(res['expireTime'], "%Y-%m-%d %H:%M:%S"))
+ return {"trafficleft": res['traffic'], "validuntil": validuntil}
+ else:
+ return {"premium": False, "trafficleft": None, "validuntil": None}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(html)
+
+ if not res['type']:
+ self.wrongPassword()
+
+ # login at fileserv html
+ req.load("http://www.fileserve.com/login.php",
+ post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
+ "loginFormSubmit": "Login"})
diff --git a/pyload/plugin/account/FourSharedCom.py b/pyload/plugin/account/FourSharedCom.py
new file mode 100644
index 000000000..a04c9bc46
--- /dev/null
+++ b/pyload/plugin/account/FourSharedCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FourSharedCom(Account):
+ __name = "FourSharedCom"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """FourShared.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ # Free mode only for now
+ return {"premium": False}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("4shared.com", "4langcookie", "en")
+
+ res = req.load("http://www.4shared.com/web/login",
+ post={'login' : user,
+ 'password' : data['password'],
+ 'remember' : "on",
+ '_remember': "on",
+ 'returnTo' : "http://www.4shared.com/account/home.jsp"},
+ decode=True)
+
+ if 'Please log in to access your 4shared account' in res:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FreakshareCom.py b/pyload/plugin/account/FreakshareCom.py
new file mode 100644
index 000000000..5bfcf9bcd
--- /dev/null
+++ b/pyload/plugin/account/FreakshareCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FreakshareCom(Account):
+ __name = "FreakshareCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Freakshare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ html = req.load("http://freakshare.com/")
+
+ try:
+ m = re.search(r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>', html, re.M)
+ validuntil = time.mktime(time.strptime(m.group(1).strip(), "%d.%m.%Y - %H:%M"))
+
+ except Exception:
+ pass
+
+ try:
+ m = re.search(r'Traffic verbleibend:</td>\s*<td>([^<]+)', html, re.M)
+ trafficleft = self.parseTraffic(m.group(1))
+
+ except Exception:
+ pass
+
+ return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ req.load("http://freakshare.com/index.php?language=EN")
+
+ html = req.load("http://freakshare.com/login.html",
+ post={"submit": "Login", "user": user, "pass": data['password']},
+ decode=True)
+
+ if ">Wrong Username or Password" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FreeWayMe.py b/pyload/plugin/account/FreeWayMe.py
new file mode 100644
index 000000000..e139b25a6
--- /dev/null
+++ b/pyload/plugin/account/FreeWayMe.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FreeWayMe(Account):
+ __name = "FreeWayMe"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """FreeWayMe account plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ status = self.getAccountStatus(user, req)
+
+ self.logDebug(status)
+
+ account_info = {"validuntil": -1, "premium": False}
+ if status['premium'] == "Free":
+ account_info['trafficleft'] = self.parseTraffic(status['guthaben'] + "MB")
+ elif status['premium'] == "Spender":
+ account_info['trafficleft'] = -1
+ elif status['premium'] == "Flatrate":
+ account_info = {"validuntil": float(status['Flatrate']),
+ "trafficleft": -1,
+ "premium": True}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ status = self.getAccountStatus(user, req)
+
+ # Check if user and password are valid
+ if not status:
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ answer = req.load("https://www.free-way.me/ajax/jd.php",
+ get={"id": 4, "user": user, "pass": self.getAccountData(user)['password']})
+
+ self.logDebug("Login: %s" % answer)
+
+ if answer == "Invalid login":
+ self.wrongPassword()
+
+ return json_loads(answer)
diff --git a/pyload/plugin/account/FshareVn.py b/pyload/plugin/account/FshareVn.py
new file mode 100644
index 000000000..9f0beec84
--- /dev/null
+++ b/pyload/plugin/account/FshareVn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FshareVn(Account):
+ __name = "FshareVn"
+ __type = "account"
+ __version = "0.09"
+
+ __description = """Fshare.vn account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ VALID_UNTIL_PATTERN = ur'<dt>Thời hạn dùng:</dt>\s*<dd>([^<]+)</dd>'
+ LIFETIME_PATTERN = ur'<dt>Lần đăng nhập trước:</dt>\s*<dd>.+?</dd>'
+ TRAFFIC_LEFT_PATTERN = ur'<dt>Tổng Dung Lượng Tài Khoản</dt>\s*<dd.*?>([\d.]+) ([kKMG])B</dd>'
+ DIRECT_DOWNLOAD_PATTERN = ur'<input type="checkbox"\s*([^=>]*)[^>]*/>Kích hoạt download trực tiếp</dt>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.fshare.vn/account_info.php", decode=True)
+
+ if re.search(self.LIFETIME_PATTERN, html):
+ self.logDebug("Lifetime membership detected")
+ trafficleft = self.getTrafficLeft()
+ return {"validuntil": -1, "trafficleft": trafficleft, "premium": True}
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = time.mktime(time.strptime(m.group(1), '%I:%M:%S %p %d-%m-%Y'))
+ trafficleft = self.getTrafficLeft()
+ else:
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load("https://www.fshare.vn/login.php",
+ post={'LoginForm[email]' : user,
+ 'LoginForm[password]' : data['password'],
+ 'LoginForm[rememberMe]': 1,
+ 'yt0' : "Login"},
+ referer=True,
+ decode=True)
+
+ if not re.search(r'<img\s+alt="VIP"', html):
+ self.wrongPassword()
+
+
+ def getTrafficLeft(self):
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ return self.parseTraffic(m.group(1) + m.group(2)) if m else 0
diff --git a/pyload/plugin/account/Ftp.py b/pyload/plugin/account/Ftp.py
new file mode 100644
index 000000000..c7983b0c2
--- /dev/null
+++ b/pyload/plugin/account/Ftp.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class Ftp(Account):
+ __name = "Ftp"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Ftp dummy account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = -1 #: Unlimited
+ info_threshold = -1 #: Unlimited
diff --git a/pyload/plugin/account/HellshareCz.py b/pyload/plugin/account/HellshareCz.py
new file mode 100644
index 000000000..68843ee80
--- /dev/null
+++ b/pyload/plugin/account/HellshareCz.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class HellshareCz(Account):
+ __name = "HellshareCz"
+ __type = "account"
+ __version = "0.16"
+
+ __description = """Hellshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ CREDIT_LEFT_PATTERN = r'<div class="credit-link">\s*<table>\s*<tr>\s*<th>(\d+|\d\d\.\d\d\.)</th>'
+
+
+ def loadAccountInfo(self, user, req):
+ self.relogin(user)
+ html = req.load("http://www.hellshare.com/")
+
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ if m is None:
+ trafficleft = None
+ validuntil = None
+ premium = False
+ else:
+ credit = m.group(1)
+ premium = True
+ try:
+ if "." in credit:
+ # Time-based account
+ vt = [int(x) for x in credit.split('.')[:2]]
+ lt = time.localtime()
+ year = lt.tm_year + int(vt[1] < lt.tm_mon or (vt[1] == lt.tm_mon and vt[0] < lt.tm_mday))
+ validuntil = time.mktime(time.strptime("%s%d 23:59:59" % (credit, year), "%d.%m.%Y %H:%M:%S"))
+ trafficleft = -1
+ else:
+ # Traffic-based account
+ trafficleft = self.parseTraffic(credit + "MB")
+ validuntil = -1
+ except Exception, e:
+ self.logError(_("Unable to parse credit info"), e)
+ validuntil = -1
+ trafficleft = -1
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load('http://www.hellshare.com/', decode=True)
+ if req.lastEffectiveURL != 'http://www.hellshare.com/':
+ # Switch to English
+ self.logDebug("Switch lang - URL: %s" % req.lastEffectiveURL)
+
+ json = req.load("%s?do=locRouter-show" % req.lastEffectiveURL)
+ hash = re.search(r"(\-\-[0-9a-f]+\-)", json).group(1)
+
+ self.logDebug("Switch lang - HASH: %s" % hash)
+
+ html = req.load('http://www.hellshare.com/%s/' % hash, decode=True)
+
+ if re.search(self.CREDIT_LEFT_PATTERN, html):
+ self.logDebug("Already logged in")
+ return
+
+ html = req.load('http://www.hellshare.com/login?do=loginForm-submit',
+ post={"login": "Log in",
+ "password": data['password'],
+ "username": user,
+ "perm_login": "on"},
+ decode=True)
+
+ if "<p>You input a wrong user name or wrong password</p>" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/Http.py b/pyload/plugin/account/Http.py
new file mode 100644
index 000000000..aacdbf89f
--- /dev/null
+++ b/pyload/plugin/account/Http.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class Http(Account):
+ __name = "Http"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Http dummy account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = -1 #: Unlimited
+ info_threshold = -1 #: Unlimited
diff --git a/pyload/plugin/account/HugefilesNet.py b/pyload/plugin/account/HugefilesNet.py
new file mode 100644
index 000000000..b4cd6f8c4
--- /dev/null
+++ b/pyload/plugin/account/HugefilesNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class HugefilesNet(XFSAccount):
+ __name = "HugefilesNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Hugefiles.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "hugefiles.net"
diff --git a/pyload/plugin/account/HundredEightyUploadCom.py b/pyload/plugin/account/HundredEightyUploadCom.py
new file mode 100644
index 000000000..15ee1a12a
--- /dev/null
+++ b/pyload/plugin/account/HundredEightyUploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class HundredEightyUploadCom(XFSAccount):
+ __name = "HundredEightyUploadCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """180upload.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "180upload.com"
diff --git a/pyload/plugin/account/JunkyvideoCom.py b/pyload/plugin/account/JunkyvideoCom.py
new file mode 100644
index 000000000..6c600f971
--- /dev/null
+++ b/pyload/plugin/account/JunkyvideoCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class JunkyvideoCom(XFSAccount):
+ __name = "JunkyvideoCom"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Junkyvideo.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "junkyvideo.com"
diff --git a/pyload/plugin/account/JunocloudMe.py b/pyload/plugin/account/JunocloudMe.py
new file mode 100644
index 000000000..75307c6dd
--- /dev/null
+++ b/pyload/plugin/account/JunocloudMe.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class JunocloudMe(XFSAccount):
+ __name = "JunocloudMe"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Junocloud.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "junocloud.me"
diff --git a/pyload/plugin/account/Keep2ShareCc.py b/pyload/plugin/account/Keep2ShareCc.py
new file mode 100644
index 000000000..56ac5e9ab
--- /dev/null
+++ b/pyload/plugin/account/Keep2ShareCc.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class Keep2ShareCc(Account):
+ __name = "Keep2ShareCc"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Keep2Share.cc account plugin"""
+ __license = "GPLv3"
+ __authors = [("aeronaut", "aeronaut@pianoguy.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'Premium expires:\s*<b>(.+?)<'
+ TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):\s*<b><a href="/user/statistic.html">(.+?)<'
+
+ LOGIN_FAIL_PATTERN = r'Please fix the following input errors'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = req.load("http://keep2share.cc/site/profile.html", decode=True)
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ if expiredate == "LifeTime":
+ premium = True
+ validuntil = -1
+ else:
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%Y.%m.%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ premium = validuntil > time.mktime(time.gmtime())
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ try:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ except Exception, e:
+ self.logError(e)
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("keep2share.cc", "lang", "en")
+
+ html = req.load("http://keep2share.cc/login.html",
+ post={'LoginForm[username]' : user,
+ 'LoginForm[password]' : data['password'],
+ 'LoginForm[rememberMe]': 1,
+ 'yt0' : ""},
+ decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/LetitbitNet.py b/pyload/plugin/account/LetitbitNet.py
new file mode 100644
index 000000000..0ad36f219
--- /dev/null
+++ b/pyload/plugin/account/LetitbitNet.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+# from pyload.utils import json_loads, json_dumps
+
+
+class LetitbitNet(Account):
+ __name = "LetitbitNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Letitbit.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
+ # api_key = self.getAccountData(user)['password']
+ # json_data = [api_key, ['key/info']]
+ # api_rep = req.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
+ # self.logDebug("API Key Info: " + api_rep)
+ # api_rep = json_loads(api_rep)
+ #
+ # if api_rep['status'] == 'FAIL':
+ # self.logWarning(api_rep['data'])
+ # return {'valid': False, 'premium': False}
+
+ return {"premium": True}
+
+
+ def login(self, user, data, req):
+ # API_KEY is the username and the PREMIUM_KEY is the password
+ self.logInfo(_("You must use your API KEY as username and the PREMIUM KEY as password"))
diff --git a/pyload/plugin/account/LinestorageCom.py b/pyload/plugin/account/LinestorageCom.py
new file mode 100644
index 000000000..824d3bf77
--- /dev/null
+++ b/pyload/plugin/account/LinestorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class LinestorageCom(XFSAccount):
+ __name = "LinestorageCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Linestorage.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "linestorage.com"
+ HOSTER_URL = "http://linestorage.com/"
diff --git a/pyload/plugin/account/LinksnappyCom.py b/pyload/plugin/account/LinksnappyCom.py
new file mode 100644
index 000000000..942cefbee
--- /dev/null
+++ b/pyload/plugin/account/LinksnappyCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class LinksnappyCom(Account):
+ __name = "LinksnappyCom"
+ __type = "account"
+ __version = "0.05"
+ __description = """Linksnappy.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ r = req.load('http://gen.linksnappy.com/lseAPI.php',
+ get={'act': 'USERDETAILS', 'username': user, 'password': hashlib.md5(data['password']).hexdigest()})
+
+ self.logDebug("JSON data: " + r)
+
+ j = json_loads(r)
+
+ if j['error']:
+ return {"premium": False}
+
+ validuntil = j['return']['expire']
+
+ if validuntil == 'lifetime':
+ validuntil = -1
+
+ elif validuntil == 'expired':
+ return {"premium": False}
+
+ else:
+ validuntil = float(validuntil)
+
+ if 'trafficleft' not in j['return'] or isinstance(j['return']['trafficleft'], str):
+ trafficleft = -1
+ else:
+ trafficleft = self.parseTraffic("%d MB" % j['return']['trafficleft'])
+
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ r = req.load("http://gen.linksnappy.com/lseAPI.php",
+ get={'act' : 'USERDETAILS',
+ 'username': user,
+ 'password': hashlib.md5(data['password']).hexdigest()},
+ decode=True)
+
+ if 'Invalid Account Details' in r:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MegaDebridEu.py b/pyload/plugin/account/MegaDebridEu.py
new file mode 100644
index 000000000..67af94541
--- /dev/null
+++ b/pyload/plugin/account/MegaDebridEu.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class MegaDebridEu(Account):
+ __name = "MegaDebridEu"
+ __type = "account"
+ __version = "0.20"
+
+ __description = """mega-debrid.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ # Define the base URL of MegaDebrid api
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ jsonResponse = req.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ res = json_loads(jsonResponse)
+
+ if res['response_code'] == "ok":
+ return {"premium": True, "validuntil": float(res['vip_end']), "status": True}
+ else:
+ self.logError(res)
+ return {"status": False, "premium": False}
+
+
+ def login(self, user, data, req):
+ jsonResponse = req.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ res = json_loads(jsonResponse)
+ if res['response_code'] != "ok":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MegaRapidCz.py b/pyload/plugin/account/MegaRapidCz.py
new file mode 100644
index 000000000..ea788d446
--- /dev/null
+++ b/pyload/plugin/account/MegaRapidCz.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegaRapidCz(Account):
+ __name = "MegaRapidCz"
+ __type = "account"
+ __version = "0.35"
+
+ __description = """MegaRapid.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("MikyWoW", "mikywow@seznam.cz"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = 60
+
+ LIMITDL_PATTERN = ur'<td>Max. počet paralelních stahování: </td><td>(\d+)'
+ VALID_UNTIL_PATTERN = ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>'
+ TRAFFIC_LEFT_PATTERN = r'<tr><td>Kredit</td><td>(.*?) GiB'
+
+
+ def loadAccountInfo(self, user, req):
+ htmll = req.load("http://megarapid.cz/mujucet/", decode=True)
+
+ m = re.search(self.LIMITDL_PATTERN, htmll)
+ if m:
+ data = self.getAccountData(user)
+ data['options']['limitDL'] = [int(m.group(1))]
+
+ m = re.search(self.VALID_UNTIL_PATTERN, htmll)
+ if m:
+ validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y - %H:%M"))
+ return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, htmll)
+ if m:
+ trafficleft = float(m.group(1)) * (1 << 20)
+ return {"premium": True, "trafficleft": trafficleft, "validuntil": -1}
+
+ return {"premium": False, "trafficleft": None, "validuntil": None}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://megarapid.cz/prihlaseni/", decode=True)
+
+ if "Heslo:" in html:
+ start = html.index('id="inp_hash" name="hash" value="')
+ html = html[start + 33:]
+ hashes = html[0:32]
+ html = req.load("http://megarapid.cz/prihlaseni/",
+ post={"hash": hashes,
+ "login": user,
+ "pass1": data['password'],
+ "remember": 0,
+ "sbmt": u"Přihlásit"})
diff --git a/pyload/plugin/account/MegaRapidoNet.py b/pyload/plugin/account/MegaRapidoNet.py
new file mode 100644
index 000000000..c4ee559da
--- /dev/null
+++ b/pyload/plugin/account/MegaRapidoNet.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegaRapidoNet(Account):
+ __name = "MegaRapidoNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """MegaRapido.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ VALID_UNTIL_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?[\'"]premium_index[\'"].*?>[^<]*?<[^>]*?b.*?>\s*?TEMPO\s*?PREMIUM.*?<[^>]*?/b.*?>\s*?(\d*)[^\d]*?DIAS[^\d]*?(\d*)[^\d]*?HORAS[^\d]*?(\d*)[^\d]*?MINUTOS[^\d]*?(\d*)[^\d]*?SEGUNDOS'
+ USER_ID_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']checkbox_compartilhar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']usar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']user["\'][^>]*?value\s*?=\s*?["\'](.*?)\s*?["\']'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+
+ html = req.load("http://megarapido.net/gerador", decode=True)
+
+ validuntil = re.search(self.VALID_UNTIL_PATTERN, html)
+ if validuntil:
+ # hier weitermachen!!! (mÌssen umbedingt die zeit richtig machen damit! (sollte aber möglich))
+ validuntil = time.time() + int(validuntil.group(1)) * 24 * 3600 + int(validuntil.group(2)) * 3600 + int(validuntil.group(3)) * 60 + int(validuntil.group(4))
+ trafficleft = -1
+ premium = True
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ req.load("http://megarapido.net/login")
+ req.load("http://megarapido.net/painel_user/ajax/logar.php",
+ post={'login': user, 'senha': data['password']},
+ decode=True)
+
+ html = req.load("http://megarapido.net/gerador")
+
+ if "sair" not in html.lower():
+ self.wrongPassword()
+ else:
+ m = re.search(self.USER_ID_PATTERN, html)
+ if m:
+ data['uid'] = m.group(1)
+ else:
+ self.fail("Couldn't find the user ID")
diff --git a/pyload/plugin/account/MegasharesCom.py b/pyload/plugin/account/MegasharesCom.py
new file mode 100644
index 000000000..8920bb2db
--- /dev/null
+++ b/pyload/plugin/account/MegasharesCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegasharesCom(Account):
+ __name = "MegasharesCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Megashares.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ VALID_UNTIL_PATTERN = r'<p class="premium_info_box">Period Ends: (\w{3} \d{1,2}, \d{4})</p>'
+
+
+ def loadAccountInfo(self, user, req):
+ # self.relogin(user)
+ html = req.load("http://d01.megashares.com/myms.php", decode=True)
+
+ premium = '>Premium Upgrade<' not in html
+
+ validuntil = trafficleft = -1
+ try:
+ timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug(timestr)
+ validuntil = time.mktime(time.strptime(timestr, "%b %d, %Y"))
+ except Exception, e:
+ self.logError(e)
+
+ return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load('http://d01.megashares.com/myms_login.php',
+ post={"httpref" : "",
+ "myms_login" : "Login",
+ "mymslogin_name": user,
+ "mymspassword" : data['password']},
+ decode=True)
+
+ if not '<span class="b ml">%s</span>' % user in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MovReelCom.py b/pyload/plugin/account/MovReelCom.py
new file mode 100644
index 000000000..9eabd0a6d
--- /dev/null
+++ b/pyload/plugin/account/MovReelCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class MovReelCom(XFSAccount):
+ __name = "MovReelCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Movreel.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+ login_timeout = 60
+ info_threshold = 30
+
+ HOSTER_DOMAIN = "movreel.com"
diff --git a/pyload/plugin/account/MultihostersCom.py b/pyload/plugin/account/MultihostersCom.py
new file mode 100644
index 000000000..bc48979bb
--- /dev/null
+++ b/pyload/plugin/account/MultihostersCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.account.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Multihosters.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
+
+
+ HOSTER_DOMAIN = "multihosters.com"
diff --git a/pyload/plugin/account/MultishareCz.py b/pyload/plugin/account/MultishareCz.py
new file mode 100644
index 000000000..66ab3dd47
--- /dev/null
+++ b/pyload/plugin/account/MultishareCz.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class MultishareCz(Account):
+ __name = "MultishareCz"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Multishare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
+ ACCOUNT_INFO_PATTERN = r'<input type="hidden" id="(u_ID|u_hash)" name=".+?" value="(.+?)">'
+
+
+ def loadAccountInfo(self, user, req):
+ # self.relogin(user)
+ html = req.load("http://www.multishare.cz/profil/", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ trafficleft = self.parseTraffic(m.group('S') + m.group('U')) if m else 0
+ self.premium = bool(trafficleft)
+
+ html = req.load("http://www.multishare.cz/", decode=True)
+ mms_info = dict(re.findall(self.ACCOUNT_INFO_PATTERN, html))
+
+ return dict(mms_info, **{"validuntil": -1, "trafficleft": trafficleft})
+
+
+ def login(self, user, data, req):
+ html = req.load('http://www.multishare.cz/html/prihlaseni_process.php',
+ post={"akce" : "Přihlásit",
+ "heslo": data['password'],
+ "jmeno": user},
+ decode=True)
+
+ if '<div class="akce-chyba akce">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MyfastfileCom.py b/pyload/plugin/account/MyfastfileCom.py
new file mode 100644
index 000000000..a58767b72
--- /dev/null
+++ b/pyload/plugin/account/MyfastfileCom.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Account):
+ __name = "MyfastfileCom"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Myfastfile.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ if 'days_left' in self.json_data:
+ validuntil = time.time() + self.json_data['days_left'] * 24 * 60 * 60
+ return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
+ else:
+ self.logError(_("Unable to get account information"))
+
+
+ def login(self, user, data, req):
+ # Password to use is the API-Password written in http://myfastfile.com/myaccount
+ html = req.load("http://myfastfile.com/api.php",
+ get={"user": user, "pass": data['password']})
+
+ self.logDebug("JSON data: " + html)
+
+ self.json_data = json_loads(html)
+ if self.json_data['status'] != 'ok':
+ self.logError(_('Invalid login. The password to use is the API-Password you find in your "My Account" page'))
+ self.wrongPassword()
diff --git a/pyload/plugin/account/NetloadIn.py b/pyload/plugin/account/NetloadIn.py
new file mode 100644
index 000000000..db0f0ea5a
--- /dev/null
+++ b/pyload/plugin/account/NetloadIn.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class NetloadIn(Account):
+ __name = "NetloadIn"
+ __type = "account"
+ __version = "0.24"
+
+ __description = """Netload.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, id, password, req):
+ return req.load("http://api.netload.in/user_info.php",
+ get={'auth' : "BVm96BWDSoB4WkfbEhn42HgnjIe1ilMt",
+ 'user_id' : id,
+ 'user_password': password}).strip()
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = self.api_response(user, self.getAccountData(user)['password'], req)
+
+ if html == "-1":
+ premium = True
+
+ elif html == "0":
+ validuntil = -1
+
+ else:
+ try:
+ validuntil = time.mktime(time.strptime(html, "%Y-%m-%d %H:%M"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ self.logDebug("Valid until: %s" % validuntil)
+
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ validuntil = -1
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ html = self.api_response(user, data['password'], req)
+
+ if not html or re.search(r'disallowed_agent|unknown_auth|login_failed', html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/NoPremiumPl.py b/pyload/plugin/account/NoPremiumPl.py
new file mode 100644
index 000000000..6cefed550
--- /dev/null
+++ b/pyload/plugin/account/NoPremiumPl.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import hashlib
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class NoPremiumPl(Account):
+ __name = "NoPremiumPl"
+ __version = "0.01"
+ __type = "account"
+ __description = "NoPremium.pl account plugin"
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+ _api_url = "http://crypt.nopremium.pl"
+
+ _api_query = {
+ "site": "nopremium",
+ "username": "",
+ "password": "",
+ "output": "json",
+ "loc": "1",
+ "info": "1"
+ }
+
+ _req = None
+ _usr = None
+ _pwd = None
+
+
+ def loadAccountInfo(self, name, req):
+ self._req = req
+ try:
+ result = json_loads(self.runAuthQuery())
+ except Exception:
+ #@TODO: return or let it be thrown?
+ return
+
+ premium = False
+ valid_untill = -1
+
+ if "expire" in result.keys() and result['expire']:
+ premium = True
+ valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple())
+ traffic_left = result['balance'] * 2 ** 20
+
+ return ({
+ "validuntil": valid_untill,
+ "trafficleft": traffic_left,
+ "premium": premium
+ })
+
+
+ def login(self, user, data, req):
+ self._usr = user
+ self._pwd = hashlib.sha1(hashlib.md5(data['password']).hexdigest()).hexdigest()
+ self._req = req
+
+ try:
+ response = json_loads(self.runAuthQuery())
+ except Exception:
+ self.wrongPassword()
+
+ if "errno" in response.keys():
+ self.wrongPassword()
+ data['usr'] = self._usr
+ data['pwd'] = self._pwd
+
+
+ def createAuthQuery(self):
+ query = self._api_query
+ query['username'] = self._usr
+ query['password'] = self._pwd
+
+ return query
+
+
+ def runAuthQuery(self):
+ data = self._req.load(self._api_url, post=self.createAuthQuery())
+
+ return data
diff --git a/pyload/plugin/account/NosuploadCom.py b/pyload/plugin/account/NosuploadCom.py
new file mode 100644
index 000000000..10f9007a6
--- /dev/null
+++ b/pyload/plugin/account/NosuploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class NosuploadCom(XFSAccount):
+ __name = "NosuploadCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Nosupload.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "nosupload.com"
diff --git a/pyload/plugin/account/NovafileCom.py b/pyload/plugin/account/NovafileCom.py
new file mode 100644
index 000000000..8400cc267
--- /dev/null
+++ b/pyload/plugin/account/NovafileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class NovafileCom(XFSAccount):
+ __name = "NovafileCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Novafile.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "novafile.com"
diff --git a/pyload/plugin/account/NowVideoSx.py b/pyload/plugin/account/NowVideoSx.py
new file mode 100644
index 000000000..f072e3687
--- /dev/null
+++ b/pyload/plugin/account/NowVideoSx.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class NowVideoSx(Account):
+ __name = "NowVideoSx"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """NowVideo.at account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'>Your premium membership expires on: (.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = None
+
+ html = req.load("http://www.nowvideo.sx/premium.php")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%b-%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.nowvideo.sx/login.php",
+ post={'user': user, 'pass': data['password']},
+ decode=True)
+
+ if re.search(r'>Log In<', html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/OboomCom.py b/pyload/plugin/account/OboomCom.py
new file mode 100644
index 000000000..4669ca61e
--- /dev/null
+++ b/pyload/plugin/account/OboomCom.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from beaker.crypto.pbkdf2 import PBKDF2
+
+except ImportError:
+ from beaker.crypto.pbkdf2 import pbkdf2
+ from binascii import b2a_hex
+
+ class PBKDF2(object):
+
+ def __init__(self, passphrase, salt, iterations=1000):
+ self.passphrase = passphrase
+ self.salt = salt
+ self.iterations = iterations
+
+
+ def hexread(self, octets):
+ return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class OboomCom(Account):
+ __name = "OboomCom"
+ __type = "account"
+ __version = "0.24"
+
+ __description = """Oboom.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stanley", "stanley.foerster@gmail.com")]
+
+
+ def loadAccountData(self, user, req):
+ passwd = self.getAccountData(user)['password']
+ salt = passwd[::-1]
+ pbkdf2 = PBKDF2(passwd, salt, 1000).hexread(16)
+
+ result = json_loads(req.load("https://www.oboom.com/1/login", get={"auth": user, "pass": pbkdf2}))
+
+ if not result[0] == 200:
+ self.logWarning(_("Failed to log in: %s") % result[1])
+ self.wrongPassword()
+
+ return result[1]
+
+
+ def loadAccountInfo(self, name, req):
+ accountData = self.loadAccountData(name, req)
+
+ userData = accountData['user']
+
+ if userData['premium'] == "null":
+ premium = False
+ else:
+ premium = True
+
+ if userData['premium_unix'] == "null":
+ validUntil = -1
+ else:
+ validUntil = float(userData['premium_unix'])
+
+ traffic = userData['traffic']
+
+ trafficLeft = traffic['current']
+ maxTraffic = traffic['max']
+
+ session = accountData['session']
+
+ return {'premium' : premium,
+ 'validuntil' : validUntil,
+ 'trafficleft': trafficLeft,
+ 'maxtraffic' : maxTraffic,
+ 'session' : session}
+
+
+ def login(self, user, data, req):
+ self.loadAccountData(user, req)
diff --git a/pyload/plugin/account/OneFichierCom.py b/pyload/plugin/account/OneFichierCom.py
new file mode 100644
index 000000000..531f5f735
--- /dev/null
+++ b/pyload/plugin/account/OneFichierCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class OneFichierCom(Account):
+ __name = "OneFichierCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """1fichier.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'Your Premium Status will end the (\d+/\d+/\d+)'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = None
+
+ html = req.load("https://1fichier.com/console/abo.pl")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%d/%m/%Y"))
+ except Exception, e:
+ self.logError(e)
+ else:
+ premium = True
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium or False}
+
+
+ def login(self, user, data, req):
+ req.http.c.setopt(pycurl.REFERER, "https://1fichier.com/login.pl?lg=en")
+
+ html = req.load("https://1fichier.com/login.pl?lg=en",
+ post={'mail' : user,
+ 'pass' : data['password'],
+ 'It' : "on",
+ 'purge' : "off",
+ 'valider': "Send"},
+ decode=True)
+
+ if '>Invalid email address' in html or '>Invalid password' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/OverLoadMe.py b/pyload/plugin/account/OverLoadMe.py
new file mode 100644
index 000000000..d65e4ad31
--- /dev/null
+++ b/pyload/plugin/account/OverLoadMe.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class OverLoadMe(Account):
+ __name = "OverLoadMe"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Over-Load.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ https = "https" if self.getConfig('ssl') else "http"
+ data = self.getAccountData(user)
+ html = req.load(https + "://api.over-load.me/account.php",
+ get={'user': user,
+ 'auth': data['password']}).strip()
+
+ data = json_loads(html)
+ self.logDebug(data)
+
+ # Check for premium
+ if data['membership'] == "Free":
+ return {'premium': False, 'validuntil': None, 'trafficleft': None}
+ else:
+ return {'premium': True, 'validuntil': data['expirationunix'], 'trafficleft': -1}
+
+
+ def login(self, user, data, req):
+ https = "https" if self.getConfig('ssl') else "http"
+ jsondata = req.load(https + "://api.over-load.me/account.php",
+ get={'user': user,
+ 'auth': data['password']}).strip()
+
+ data = json_loads(jsondata)
+
+ if data['err'] == 1:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/PremiumTo.py b/pyload/plugin/account/PremiumTo.py
new file mode 100644
index 000000000..586ddf635
--- /dev/null
+++ b/pyload/plugin/account/PremiumTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class PremiumTo(Account):
+ __name = "PremiumTo"
+ __type = "account"
+ __version = "0.08"
+
+ __description = """Premium.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ traffic = req.load("http://premium.to/api/straffic.php",
+ get={'username': self.username, 'password': self.password})
+
+ if "wrong username" not in traffic:
+ trafficleft = sum(map(float, traffic.split(';')))
+ return {'premium': True, 'trafficleft': trafficleft, 'validuntil': -1}
+ else:
+ return {'premium': False, 'trafficleft': None, 'validuntil': None}
+
+
+ def login(self, user, data, req):
+ self.username = user
+ self.password = data['password']
+ authcode = req.load("http://premium.to/api/getauthcode.php",
+ get={'username': user, 'password': self.password},
+ decode=True)
+
+ if "wrong username" in authcode:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/PremiumizeMe.py b/pyload/plugin/account/PremiumizeMe.py
new file mode 100644
index 000000000..06e6ffb98
--- /dev/null
+++ b/pyload/plugin/account/PremiumizeMe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+from pyload.utils import json_loads
+
+
+class PremiumizeMe(Account):
+ __name = "PremiumizeMe"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Premiumize.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+ self.logDebug(status)
+
+ # Parse account info
+ account_info = {"validuntil": float(status['result']['expires']),
+ "trafficleft": max(0, status['result']['trafficleft_bytes'])}
+
+ if status['result']['type'] == 'free':
+ account_info['premium'] = False
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+
+ # Check if user and password are valid
+ if status['status'] != 200:
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ # Use premiumize.me API v1 (see https://secure.premiumize.me/?show=api)
+ # to retrieve account info and return the parsed json answer
+ answer = req.load("https://api.premiumize.me/pm-api/v1.php",
+ get={'method' : "accountstatus",
+ 'params[login]': user,
+ 'params[pass]' : self.getAccountData(user)['password']})
+ return json_loads(answer)
diff --git a/pyload/plugin/account/PutdriveCom.py b/pyload/plugin/account/PutdriveCom.py
new file mode 100644
index 000000000..3d7279034
--- /dev/null
+++ b/pyload/plugin/account/PutdriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.account.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Putdrive.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "putdrive.com"
diff --git a/pyload/plugin/account/QuickshareCz.py b/pyload/plugin/account/QuickshareCz.py
new file mode 100644
index 000000000..2f71d9ae8
--- /dev/null
+++ b/pyload/plugin/account/QuickshareCz.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class QuickshareCz(Account):
+ __name = "QuickshareCz"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Quickshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'Stav kreditu: <strong>(.+?)</strong>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.quickshare.cz/premium", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+ premium = bool(trafficleft)
+ else:
+ trafficleft = None
+ premium = False
+
+ return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load('http://www.quickshare.cz/html/prihlaseni_process.php',
+ post={"akce": u'Přihlásit',
+ "heslo": data['password'],
+ "jmeno": user},
+ decode=True)
+
+ if u'>TakovÜ uşivatel neexistuje.<' in html or u'>Špatné heslo.<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RPNetBiz.py b/pyload/plugin/account/RPNetBiz.py
new file mode 100644
index 000000000..562436e85
--- /dev/null
+++ b/pyload/plugin/account/RPNetBiz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Account):
+ __name = "RPNetBiz"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """RPNet.biz account plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ # Get account information from rpnet.biz
+ res = self.getAccountStatus(user, req)
+ try:
+ if res['accountInfo']['isPremium']:
+ # Parse account info. Change the trafficleft later to support per host info.
+ account_info = {"validuntil": float(res['accountInfo']['premiumExpiry']),
+ "trafficleft": -1, "premium": True}
+ else:
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ except KeyError:
+ # handle wrong password exception
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ # Get account information from rpnet.biz
+ res = self.getAccountStatus(user, req)
+
+ # If we have an error in the res, we have wrong login information
+ if 'error' in res:
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ # Using the rpnet API, check if valid premium account
+ res = req.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": self.getAccountData(user)['password'],
+ "action": "showAccountInformation"})
+ self.logDebug("JSON data: %s" % res)
+
+ return json_loads(res)
diff --git a/pyload/plugin/account/RapideoPl.py b/pyload/plugin/account/RapideoPl.py
new file mode 100644
index 000000000..c58414b53
--- /dev/null
+++ b/pyload/plugin/account/RapideoPl.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import hashlib
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapideoPl(Account):
+ __name = "RapideoPl"
+ __version = "0.01"
+ __type = "account"
+ __description = "Rapideo.pl account plugin"
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+ _api_url = "http://enc.rapideo.pl"
+
+ _api_query = {
+ "site": "newrd",
+ "username": "",
+ "password": "",
+ "output": "json",
+ "loc": "1",
+ "info": "1"
+ }
+
+ _req = None
+ _usr = None
+ _pwd = None
+
+
+ def loadAccountInfo(self, name, req):
+ self._req = req
+ try:
+ result = json_loads(self.runAuthQuery())
+ except Exception:
+ #@TODO: return or let it be thrown?
+ return
+
+ premium = False
+ valid_untill = -1
+ if "expire" in result.keys() and result['expire']:
+ premium = True
+ valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple())
+
+ traffic_left = result['balance']
+
+ return ({
+ "validuntil": valid_untill,
+ "trafficleft": traffic_left,
+ "premium": premium
+ })
+
+
+ def login(self, user, data, req):
+ self._usr = user
+ self._pwd = hashlib.md5(data['password']).hexdigest()
+ self._req = req
+ try:
+ response = json_loads(self.runAuthQuery())
+ except Exception:
+ self.wrongPassword()
+
+ if "errno" in response.keys():
+ self.wrongPassword()
+ data['usr'] = self._usr
+ data['pwd'] = self._pwd
+
+
+ def createAuthQuery(self):
+ query = self._api_query
+ query['username'] = self._usr
+ query['password'] = self._pwd
+
+ return query
+
+
+ def runAuthQuery(self):
+ data = self._req.load(self._api_url, post=self.createAuthQuery())
+
+ return data
diff --git a/pyload/plugin/account/RapidfileshareNet.py b/pyload/plugin/account/RapidfileshareNet.py
new file mode 100644
index 000000000..0b0ed210c
--- /dev/null
+++ b/pyload/plugin/account/RapidfileshareNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RapidfileshareNet(XFSAccount):
+ __name = "RapidfileshareNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Rapidfileshare.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "rapidfileshare.net"
+
+ TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><label for="name">\s*(?P<S>[\d.,]+)\s*(?:(?P<U>[\w^_]+))?'
diff --git a/pyload/plugin/account/RapidgatorNet.py b/pyload/plugin/account/RapidgatorNet.py
new file mode 100644
index 000000000..729635037
--- /dev/null
+++ b/pyload/plugin/account/RapidgatorNet.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapidgatorNet(Account):
+ __name = "RapidgatorNet"
+ __type = "account"
+ __version = "0.09"
+
+ __description = """Rapidgator.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://rapidgator.net/api/user"
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+ sid = None
+
+ try:
+ sid = self.getAccountData(user).get('sid')
+ assert sid
+
+ html = req.load("%s/info" % self.API_URL, get={'sid': sid})
+
+ self.logDebug("API:USERINFO", html)
+
+ json = json_loads(html)
+
+ if json['response_status'] == 200:
+ if "reset_in" in json['response']:
+ self.scheduleRefresh(user, json['response']['reset_in'])
+
+ validuntil = json['response']['expire_date']
+ trafficleft = float(json['response']['traffic_left'])
+ premium = True
+ else:
+ self.logError(json['response_details'])
+
+ except Exception, e:
+ self.logError(e)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium,
+ 'sid' : sid}
+
+
+ def login(self, user, data, req):
+ try:
+ html = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
+
+ self.logDebug("API:LOGIN", html)
+
+ json = json_loads(html)
+
+ if json['response_status'] == 200:
+ data['sid'] = str(json['response']['session_id'])
+ return
+ else:
+ self.logError(json['response_details'])
+
+ except Exception, e:
+ self.logError(e)
+
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RapiduNet.py b/pyload/plugin/account/RapiduNet.py
new file mode 100644
index 000000000..c19e54a9b
--- /dev/null
+++ b/pyload/plugin/account/RapiduNet.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapiduNet(Account):
+ __name = "RapiduNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Rapidu.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", None),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'>Account: <b>Premium'
+
+ VALID_UNTIL_PATTERN = r'>Account: <b>\w+ \((\d+)'
+
+ TRAFFIC_LEFT_PATTERN = r'class="tipsyS"><b>(.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = req.load("https://rapidu.net/", decode=True)
+
+ if re.search(self.PREMIUM_PATTERN, html):
+ premium = True
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ validuntil = time.time() + (86400 * int(m.group(1)))
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ req.load("https://rapidu.net/ajax.php",
+ get={'a': "getChangeLang"},
+ post={'_go' : "",
+ 'lang': "en"})
+
+ json = json_loads(req.load("https://rapidu.net/ajax.php",
+ get={'a': "getUserLogin"},
+ post={'_go' : "",
+ 'login' : user,
+ 'pass' : data['password'],
+ 'remember': "1"}))
+
+ self.logDebug(json)
+
+ if not json['message'] == "success":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RarefileNet.py b/pyload/plugin/account/RarefileNet.py
new file mode 100644
index 000000000..fc736bafc
--- /dev/null
+++ b/pyload/plugin/account/RarefileNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RarefileNet(XFSAccount):
+ __name = "RarefileNet"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """RareFile.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "rarefile.net"
diff --git a/pyload/plugin/account/RealdebridCom.py b/pyload/plugin/account/RealdebridCom.py
new file mode 100644
index 000000000..0d28f2ad6
--- /dev/null
+++ b/pyload/plugin/account/RealdebridCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import xml.dom.minidom as dom
+
+from pyload.plugin.Account import Account
+
+
+class RealdebridCom(Account):
+ __name = "RealdebridCom"
+ __type = "account"
+ __version = "0.45"
+
+ __description = """Real-Debrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ if self.pin_code:
+ return {"premium": False}
+ html = req.load("https://real-debrid.com/api/account.php")
+ xml = dom.parseString(html)
+ account_info = {"validuntil": float(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
+ "trafficleft": -1}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ self.pin_code = False
+ html = req.load("https://real-debrid.com/ajax/login.php",
+ get={"user": user, "pass": data['password']},
+ decode=True)
+
+ if "Your login informations are incorrect" in html:
+ self.wrongPassword()
+
+ elif "PIN Code required" in html:
+ self.logWarning(_("PIN code required. Please login to https://real-debrid.com using the PIN or disable the double authentication in your control panel on https://real-debrid.com"))
+ self.pin_code = True
diff --git a/pyload/plugin/account/RehostTo.py b/pyload/plugin/account/RehostTo.py
new file mode 100644
index 000000000..3b02145fc
--- /dev/null
+++ b/pyload/plugin/account/RehostTo.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class RehostTo(Account):
+ __name = "RehostTo"
+ __type = "account"
+ __version = "0.16"
+
+ __description = """Rehost.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ trafficleft = None
+ validuntil = -1
+ session = ""
+
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd' : "login", 'user': user,
+ 'pass': self.getAccountData(user)['password']})
+ try:
+ session = html.split(",")[1].split("=")[1]
+
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd': "get_premium_credits", 'long_ses': session})
+
+ if html.strip() == "0,0" or "ERROR" in html:
+ self.logDebug(html)
+ else:
+ traffic, valid = html.split(",")
+
+ premium = True
+ trafficleft = self.parseTraffic(traffic + "MB")
+ validuntil = float(valid)
+
+ finally:
+ return {'premium' : premium,
+ 'trafficleft': trafficleft,
+ 'validuntil' : validuntil,
+ 'session' : session}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd': "login", 'user': user, 'pass': data['password']},
+ decode=True)
+
+ if "ERROR" in html:
+ self.logDebug(html)
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RyushareCom.py b/pyload/plugin/account/RyushareCom.py
new file mode 100644
index 000000000..f34d1d388
--- /dev/null
+++ b/pyload/plugin/account/RyushareCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RyushareCom(XFSAccount):
+ __name = "RyushareCom"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Ryushare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "ryushare.com"
diff --git a/pyload/plugin/account/SafesharingEu.py b/pyload/plugin/account/SafesharingEu.py
new file mode 100644
index 000000000..eae8140fb
--- /dev/null
+++ b/pyload/plugin/account/SafesharingEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SafesharingEu(XFSAccount):
+ __name = "SafesharingEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Safesharing.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "safesharing.eu"
diff --git a/pyload/plugin/account/SecureUploadEu.py b/pyload/plugin/account/SecureUploadEu.py
new file mode 100644
index 000000000..b349e893f
--- /dev/null
+++ b/pyload/plugin/account/SecureUploadEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SecureUploadEu(XFSAccount):
+ __name = "SecureUploadEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """SecureUpload.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "secureupload.eu"
diff --git a/pyload/plugin/account/SendmywayCom.py b/pyload/plugin/account/SendmywayCom.py
new file mode 100644
index 000000000..add0b2183
--- /dev/null
+++ b/pyload/plugin/account/SendmywayCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SendmywayCom(XFSAccount):
+ __name = "SendmywayCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Sendmyway.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "sendmyway.com"
diff --git a/pyload/plugin/account/ShareonlineBiz.py b/pyload/plugin/account/ShareonlineBiz.py
new file mode 100644
index 000000000..06b2818c9
--- /dev/null
+++ b/pyload/plugin/account/ShareonlineBiz.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class ShareonlineBiz(Account):
+ __name = "ShareonlineBiz"
+ __type = "account"
+ __version = "0.31"
+
+ __description = """Share-online.biz account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, user, req):
+ return req.load("http://api.share-online.biz/cgi-bin",
+ get={'q' : "userdetails",
+ 'aux' : "traffic",
+ 'username': user,
+ 'password': self.getAccountData(user)['password']})
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = -1
+ maxtraffic = 100 * 2 ** 30 #: 100 GB
+
+ api = {}
+ for line in self.api_response(user, req).splitlines():
+ if "=" in line:
+ key, value = line.split("=")
+ api[key] = value
+
+ self.logDebug(api)
+
+ if api['a'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", 'a', api['a'])
+
+ premium = api['group'] in ("Premium", "PrePaid")
+
+ validuntil = float(api['expire_date'])
+
+ traffic = float(api['traffic_1d'].split(";")[0])
+ maxtraffic = max(maxtraffic, traffic)
+ trafficleft = maxtraffic - traffic
+
+ return {'premium' : premium,
+ 'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'maxtraffic' : maxtraffic}
+
+
+ def login(self, user, data, req):
+ html = self.api_response(user, req)
+ err = re.search(r'\*\*(.+?)\*\*', html)
+ if err:
+ self.logError(err.group(1))
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SimplyPremiumCom.py b/pyload/plugin/account/SimplyPremiumCom.py
new file mode 100644
index 000000000..2547dad56
--- /dev/null
+++ b/pyload/plugin/account/SimplyPremiumCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class SimplyPremiumCom(Account):
+ __name = "SimplyPremiumCom"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Simply-Premium.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = -1
+ trafficleft = None
+
+ json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
+
+ self.logDebug("JSON data: %s" % json_data)
+
+ json_data = json_loads(json_data)
+
+ if 'vip' in json_data['result'] and json_data['result']['vip']:
+ premium = True
+
+ if 'timeend' in json_data['result'] and json_data['result']['timeend']:
+ validuntil = float(json_data['result']['timeend'])
+
+ if 'remain_traffic' in json_data['result'] and json_data['result']['remain_traffic']:
+ trafficleft = float(json_data['result']['remain_traffic'])
+
+ return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("simply-premium.com", "lang", "EN")
+
+ html = req.load("http://www.simply-premium.com/login.php",
+ post={'key': user} if not data['password'] else {'login_name': user, 'login_pass': data['password']},
+ decode=True)
+
+ if 'logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SimplydebridCom.py b/pyload/plugin/account/SimplydebridCom.py
new file mode 100644
index 000000000..dc855c441
--- /dev/null
+++ b/pyload/plugin/account/SimplydebridCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+
+
+class SimplydebridCom(Account):
+ __name = "SimplydebridCom"
+ __type = "account"
+ __version = "0.11"
+
+ __description = """Simply-Debrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def loadAccountInfo(self, user, req):
+ get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
+ res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ data = [x.strip() for x in res.split(";")]
+ if str(data[0]) != "1":
+ return {"premium": False}
+ else:
+ return {"trafficleft": -1, "validuntil": time.mktime(time.strptime(str(data[2]), "%d/%m/%Y"))}
+
+
+ def login(self, user, data, req):
+ self.loginname = user
+ self.password = data['password']
+ get_data = {'login': 1, 'u': self.loginname, 'p': self.password}
+
+ res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ if res != "02: loggin success":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SmoozedCom.py b/pyload/plugin/account/SmoozedCom.py
new file mode 100644
index 000000000..f24799caf
--- /dev/null
+++ b/pyload/plugin/account/SmoozedCom.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+import time
+
+try:
+ from beaker.crypto.pbkdf2 import PBKDF2
+
+except ImportError:
+ from beaker.crypto.pbkdf2 import pbkdf2
+ from binascii import b2a_hex
+
+ class PBKDF2(object):
+
+ def __init__(self, passphrase, salt, iterations=1000):
+ self.passphrase = passphrase
+ self.salt = salt
+ self.iterations = iterations
+
+
+ def hexread(self, octets):
+ return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class SmoozedCom(Account):
+ __name = "SmoozedCom"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Smoozed.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def loadAccountInfo(self, user, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+
+ self.logDebug(status)
+
+ if status['state'] != 'ok':
+ info = {'validuntil' : None,
+ 'trafficleft': None,
+ 'premium' : False}
+ else:
+ # Parse account info
+ info = {'validuntil' : float(status['data']['user']['user_premium']),
+ 'trafficleft': max(0, status['data']['traffic'][1] - status['data']['traffic'][0]),
+ 'session' : status['data']['session_key'],
+ 'hosters' : [hoster['name'] for hoster in status['data']['hoster']]}
+
+ if info['validuntil'] < time.time():
+ info['premium'] = False
+ else:
+ info['premium'] = True
+
+ return info
+
+
+ def login(self, user, data, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+
+ # Check if user and password are valid
+ if status['state'] != 'ok':
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ password = self.getAccountData(user)['password']
+ salt = hashlib.sha256(password).hexdigest()
+ encrypted = PBKDF2(password, salt, iterations=1000).hexread(32)
+
+ return json_loads(req.load("http://www2.smoozed.com/api/login",
+ get={'auth': user, 'password': encrypted}))
diff --git a/pyload/plugin/account/StahnuTo.py b/pyload/plugin/account/StahnuTo.py
new file mode 100644
index 000000000..57f9adc10
--- /dev/null
+++ b/pyload/plugin/account/StahnuTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class StahnuTo(Account):
+ __name = "StahnuTo"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """StahnuTo account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.stahnu.to/")
+
+ m = re.search(r'>VIP: (\d+.*)<', html)
+ trafficleft = self.parseTraffic(m.group(1)) if m else 0
+
+ return {"premium": trafficleft > 512, "trafficleft": trafficleft, "validuntil": -1}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.stahnu.to/login.php",
+ post={"username": user,
+ "password": data['password'],
+ "submit": "Login"},
+ decode=True)
+
+ if not '<a href="logout.php">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/StreamcloudEu.py b/pyload/plugin/account/StreamcloudEu.py
new file mode 100644
index 000000000..f3eb6cce9
--- /dev/null
+++ b/pyload/plugin/account/StreamcloudEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class StreamcloudEu(XFSAccount):
+ __name = "StreamcloudEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Streamcloud.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "streamcloud.eu"
diff --git a/pyload/plugin/account/TurbobitNet.py b/pyload/plugin/account/TurbobitNet.py
new file mode 100644
index 000000000..4b1b6b2a0
--- /dev/null
+++ b/pyload/plugin/account/TurbobitNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class TurbobitNet(Account):
+ __name = "TurbobitNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """TurbobitNet account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://turbobit.net")
+
+ m = re.search(r'<u>Turbo Access</u> to ([\d.]+)', html)
+ if m:
+ premium = True
+ validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("turbobit.net", "user_lang", "en")
+
+ html = req.load("http://turbobit.net/user/login",
+ post={"user[login]": user,
+ "user[pass]": data['password'],
+ "user[submit]": "Login"},
+ decode=True)
+
+ if not '<div class="menu-item user-name">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/TusfilesNet.py b/pyload/plugin/account/TusfilesNet.py
new file mode 100644
index 000000000..b18d49748
--- /dev/null
+++ b/pyload/plugin/account/TusfilesNet.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class TusfilesNet(XFSAccount):
+ __name = "TusfilesNet"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Tusfile.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "tusfiles.net"
+
+ VALID_UNTIL_PATTERN = r'<span class="label label-default">([^<]+)</span>'
+ TRAFFIC_LEFT_PATTERN = r'<td><img src="//www\.tusfiles\.net/i/icon/meter\.png" alt=""/></td>\n<td>&nbsp;(?P<S>[\d.,]+)'
diff --git a/pyload/plugin/account/UlozTo.py b/pyload/plugin/account/UlozTo.py
new file mode 100644
index 000000000..20f03f907
--- /dev/null
+++ b/pyload/plugin/account/UlozTo.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.Account import Account
+
+
+class UlozTo(Account):
+ __name = "UlozTo"
+ __type = "account"
+ __version = "0.10"
+
+ __description = """Uloz.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("pulpe", "")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a .*?title=".+?GB = ([\d.]+) MB"'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.ulozto.net/", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+
+ trafficleft = float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048 if m else 0
+ premium = bool(trafficleft)
+
+ return {'validuntil': -1, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ login_page = req.load('http://www.ulozto.net/?do=web-login', decode=True)
+ action = re.findall('<form action="(.+?)"', login_page)[1].replace('&amp;', '&')
+ token = re.search('_token_" value="(.+?)"', login_page).group(1)
+
+ html = req.load(urlparse.urljoin("http://www.ulozto.net/", action),
+ post={'_token_' : token,
+ 'do' : "loginForm-submit",
+ 'login' : u"Přihlásit",
+ 'password': data['password'],
+ 'username': user,
+ 'remember': "on"},
+ decode=True)
+
+ if '<div class="flash error">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UnrestrictLi.py b/pyload/plugin/account/UnrestrictLi.py
new file mode 100644
index 000000000..d57b998ca
--- /dev/null
+++ b/pyload/plugin/account/UnrestrictLi.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class UnrestrictLi(Account):
+ __name = "UnrestrictLi"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Unrestrict.li account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json')
+ self.logDebug("JSON data: " + json_data)
+ json_data = json_loads(json_data)
+
+ if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
+ return {"premium": False}
+
+ validuntil = json_data['result']['expires']
+ trafficleft = float(json_data['result']['traffic'])
+
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("unrestrict.li", "lang", "EN")
+ html = req.load("https://unrestrict.li/sign_in", decode=True)
+
+ if 'solvemedia' in html:
+ self.logError(_("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry"))
+ return
+
+ post_data = {"username": user, "password": data['password'],
+ "remember_me": "remember", "signin": "Sign in"}
+ html = req.load("https://unrestrict.li/sign_in", post=post_data, decode=True)
+
+ if 'sign_out' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadableCh.py b/pyload/plugin/account/UploadableCh.py
new file mode 100644
index 000000000..c95fe7f0b
--- /dev/null
+++ b/pyload/plugin/account/UploadableCh.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class UploadableCh(Account):
+ __name = "UploadableCh"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Uploadable.ch account plugin"""
+ __license = "GPLv3"
+ __authors = [("Sasch", "gsasch@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.uploadable.ch/login.php")
+
+ premium = '<a href="/logout.php"' in html
+ trafficleft = -1 if premium else None
+
+ return {'validuntil': None, 'trafficleft': trafficleft, 'premium': premium} #@TODO: validuntil
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.uploadable.ch/login.php",
+ post={'userName' : user,
+ 'userPassword' : data['password'],
+ 'autoLogin' : "1",
+ 'action__login': "normalLogin"},
+ decode=True)
+
+ if "Login failed" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadcCom.py b/pyload/plugin/account/UploadcCom.py
new file mode 100644
index 000000000..01102168c
--- /dev/null
+++ b/pyload/plugin/account/UploadcCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class UploadcCom(XFSAccount):
+ __name = "UploadcCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Uploadc.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "uploadc.com"
diff --git a/pyload/plugin/account/UploadedTo.py b/pyload/plugin/account/UploadedTo.py
new file mode 100644
index 000000000..1b8ae5b27
--- /dev/null
+++ b/pyload/plugin/account/UploadedTo.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class UploadedTo(Account):
+ __name = "UploadedTo"
+ __type = "account"
+ __version = "0.30"
+
+ __description = """Uploaded.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'<em>Premium</em>'
+ VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>(.+?)<'
+ TRAFFIC_LEFT_PATTERN = r'<b class="cB">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ html = req.load("http://uploaded.net/me")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html, re.M)
+ if m:
+ expiredate = m.group(1).lower().strip()
+
+ if expiredate == "unlimited":
+ validuntil = -1
+ else:
+ m = re.findall(r'(\d+) (week|day|hour)', expiredate)
+ if m:
+ validuntil = time.time()
+ for n, u in m:
+ validuntil += float(n) * 60 * 60 * {'week': 168, 'day': 24, 'hour': 1}[u]
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ traffic = m.groupdict()
+ size = traffic['S'].replace('.', '')
+ unit = traffic['U'].lower()
+
+ trafficleft = self.parseTraffic(size + unit)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ # req.cj.setCookie("uploaded.net", "lang", "en")
+
+ html = req.load("http://uploaded.net/io/login",
+ post={'id': user, 'pw': data['password'], '_': ""},
+ decode=True)
+
+ if '"err"' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadheroCom.py b/pyload/plugin/account/UploadheroCom.py
new file mode 100644
index 000000000..a0c8e2aa8
--- /dev/null
+++ b/pyload/plugin/account/UploadheroCom.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+import datetime
+import time
+
+from pyload.plugin.Account import Account
+
+
+class UploadheroCom(Account):
+ __name = "UploadheroCom"
+ __type = "account"
+ __version = "0.21"
+
+ __description = """Uploadhero.co account plugin"""
+ __license = "GPLv3"
+ __authors = [("mcmyst", "mcmyst@hotmail.fr")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium_pattern = re.compile('Il vous reste <span class="bleu">(\d+)</span> jours premium')
+
+ data = self.getAccountData(user)
+ html = req.load("http://uploadhero.co/my-account")
+
+ if premium_pattern.search(html):
+ end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(html).group(1)))
+ end_date = time.mktime(future.timetuple())
+ account_info = {"validuntil": end_date, "trafficleft": -1, "premium": True}
+ else:
+ account_info = {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://uploadhero.co/lib/connexion.php",
+ post={"pseudo_login": user, "password_login": data['password']},
+ decode=True)
+
+ if "mot de passe invalide" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadingCom.py b/pyload/plugin/account/UploadingCom.py
new file mode 100644
index 000000000..992e63615
--- /dev/null
+++ b/pyload/plugin/account/UploadingCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+from pyload.plugin.internal.SimpleHoster import set_cookies
+
+
+class UploadingCom(Account):
+ __name = "UploadingCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """Uploading.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ PREMIUM_PATTERN = r'UPGRADE TO PREMIUM'
+ VALID_UNTIL_PATTERN = r'Valid Until:(.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ html = req.load("http://uploading.com/")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%b %d, %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = None
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ set_cookies(req.cj,
+ [("uploading.com", "lang" , "1" ),
+ ("uploading.com", "language", "1" ),
+ ("uploading.com", "setlang" , "en"),
+ ("uploading.com", "_lang" , "en")])
+
+ req.load("http://uploading.com/")
+ req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time.time() * 1000),
+ post={'email': user, 'password': data['password'], 'remember': "on"})
diff --git a/pyload/plugin/account/UptoboxCom.py b/pyload/plugin/account/UptoboxCom.py
new file mode 100644
index 000000000..f7e715a33
--- /dev/null
+++ b/pyload/plugin/account/UptoboxCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class UptoboxCom(XFSAccount):
+ __name = "UptoboxCom"
+ __type = "account"
+ __version = "0.08"
+
+ __description = """DDLStorage.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "uptobox.com"
+ HOSTER_URL = "https://uptobox.com/"
+ LOGIN_URL = "https://login.uptobox.com/"
diff --git a/pyload/plugin/account/VidPlayNet.py b/pyload/plugin/account/VidPlayNet.py
new file mode 100644
index 000000000..c5d4e0b5a
--- /dev/null
+++ b/pyload/plugin/account/VidPlayNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class VidPlayNet(XFSAccount):
+ __name = "VidPlayNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """VidPlay.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "vidplay.net"
diff --git a/pyload/plugin/account/WebshareCz.py b/pyload/plugin/account/WebshareCz.py
new file mode 100644
index 000000000..c547534ba
--- /dev/null
+++ b/pyload/plugin/account/WebshareCz.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+import re
+import time
+
+from passlib.hash import md5_crypt
+
+from pyload.plugin.Account import Account
+
+
+class WebshareCz(Account):
+ __name = "WebshareCz"
+ __type = "account"
+ __version = "0.07"
+
+ __description = """Webshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("rush", "radek.senfeld@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'<vip_until>(.+)</vip_until>'
+
+ TRAFFIC_LEFT_PATTERN = r'<bytes>(.+)</bytes>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://webshare.cz/api/user_data/",
+ post={'wst': self.infos['wst']},
+ decode=True)
+
+ self.logDebug("Response: " + html)
+
+ expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
+ trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
+ premium = validuntil > time.time()
+
+ return {'validuntil': validuntil, 'trafficleft': -1, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ salt = req.load("https://webshare.cz/api/salt/",
+ post={'username_or_email': user,
+ 'wst' : ""},
+ decode=True)
+
+ if "<status>OK</status>" not in salt:
+ self.wrongPassword()
+
+ salt = re.search('<salt>(.+)</salt>', salt).group(1)
+ password = hashlib.sha1(md5_crypt.encrypt(data['password'], salt=salt)).hexdigest()
+ digest = hashlib.md5(user + ":Webshare:" + password).hexdigest()
+
+ login = req.load("https://webshare.cz/api/login/",
+ post={'digest' : digest,
+ 'keep_logged_in' : 1,
+ 'password' : password,
+ 'username_or_email': user,
+ 'wst' : ""},
+ decode=True)
+
+ if "<status>OK</status>" not in login:
+ self.wrongPassword()
+
+ self.infos['wst'] = re.search('<token>(.+)</token>', login).group(1)
diff --git a/pyload/plugin/account/XFileSharingPro.py b/pyload/plugin/account/XFileSharingPro.py
new file mode 100644
index 000000000..79b5427ef
--- /dev/null
+++ b/pyload/plugin/account/XFileSharingPro.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class XFileSharingPro(XFSAccount):
+ __name = "XFileSharingPro"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """XFileSharingPro multi-purpose account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+
+ def init(self):
+ if self.HOSTER_DOMAIN:
+ return super(XFileSharingPro, self).init()
+
+
+ def loadAccountInfo(self, user, req):
+ return super(XFileSharingPro if self.HOSTER_DOMAIN else XFSAccount, self).loadAccountInfo(user, req)
+
+
+ def login(self, user, data, req):
+ if self.HOSTER_DOMAIN:
+ try:
+ return super(XFileSharingPro, self).login(user, data, req)
+ except Exception:
+ self.HOSTER_URL = self.HOSTER_URL.replace("www.", "")
+ return super(XFileSharingPro, self).login(user, data, req)
diff --git a/pyload/plugin/account/YibaishiwuCom.py b/pyload/plugin/account/YibaishiwuCom.py
new file mode 100644
index 000000000..e12e3f3f2
--- /dev/null
+++ b/pyload/plugin/account/YibaishiwuCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class YibaishiwuCom(Account):
+ __name = "YibaishiwuCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """115.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ ACCOUNT_INFO_PATTERN = r'var USER_PERMISSION = {(.*?)}'
+
+
+ def loadAccountInfo(self, user, req):
+ # self.relogin(user)
+ html = req.load("http://115.com/", decode=True)
+
+ m = re.search(self.ACCOUNT_INFO_PATTERN, html, re.S)
+ premium = m and 'is_vip: 1' in m.group(1)
+ validuntil = trafficleft = (-1 if m else 0)
+ return dict({"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium})
+
+
+ def login(self, user, data, req):
+ html = req.load("http://passport.115.com/?ac=login",
+ post={"back": "http://www.115.com/",
+ "goto": "http://115.com/",
+ "login[account]": user,
+ "login[passwd]": data['password']},
+ decode=True)
+
+ if not 'var USER_PERMISSION = {' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/ZeveraCom.py b/pyload/plugin/account/ZeveraCom.py
new file mode 100644
index 000000000..1e5eacb4c
--- /dev/null
+++ b/pyload/plugin/account/ZeveraCom.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+
+
+class ZeveraCom(Account):
+ __name = "ZeveraCom"
+ __type = "account"
+ __version = "0.26"
+
+ __description = """Zevera.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "zevera.com"
+
+
+ def init(self):
+ if not self.HOSTER_DOMAIN:
+ self.logError(_("Missing HOSTER_DOMAIN"))
+
+ if not hasattr(self, "API_URL"):
+ self.API_URL = "http://api.%s/jDownloader.ashx" % (self.HOSTER_DOMAIN or "")
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+
+ api = self.api_response(req)
+
+ if "No trafic" not in api and api['endsubscriptiondate'] != "Expired!":
+ validuntil = time.mktime(time.strptime(api['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S"))
+ trafficleft = float(api['availabletodaytraffic']) * 2 ** 20 if api['orondaytrafficlimit'] != '0' else -1
+ premium = True
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ self.user = user
+ self.password = data['password']
+
+ if self.api_response(req) == "No trafic":
+ self.wrongPassword()
+
+
+ def api_response(self, req, just_header=False, **kwargs):
+ get_data = {'cmd' : "accountinfo",
+ 'login': self.user,
+ 'pass' : self.password}
+
+ get_data.update(kwargs)
+
+ res = req.load(self.API_URL,
+ get=get_data,
+ just_header=just_header,
+ decode=True)
+
+ self.logDebug(res)
+
+ if ':' in res:
+ if not just_header:
+ res = res.replace(',', '\n')
+ return dict((y.strip().lower(), z.strip()) for (y, z) in
+ [x.split(':', 1) for x in res.splitlines() if ':' in x])
+ else:
+ return res
diff --git a/pyload/plugin/account/__init__.py b/pyload/plugin/account/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/account/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/addon/AndroidPhoneNotify.py b/pyload/plugin/addon/AndroidPhoneNotify.py
new file mode 100644
index 000000000..8332f668d
--- /dev/null
+++ b/pyload/plugin/addon/AndroidPhoneNotify.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Addon, Expose
+
+
+class AndroidPhoneNotify(Addon):
+ __name = "AndroidPhoneNotify"
+ __type = "addon"
+ __version = "0.07"
+
+ __config = [("apikey" , "str" , "API key" , "" ),
+ ("notifycaptcha" , "bool", "Notify captcha request" , True ),
+ ("notifypackage" , "bool", "Notify package finished" , True ),
+ ("notifyprocessed", "bool", "Notify packages processed" , True ),
+ ("notifyupdate" , "bool", "Notify plugin updates" , True ),
+ ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
+ ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
+ ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
+ ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
+
+ __description = """Send push notifications to your Android Phone (using notifymyandroid.com)"""
+ __license = "GPLv3"
+ __authors = [("Steven Kosyra" , "steven.kosyra@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ event_list = ["allDownloadsProcessed", "plugin_updated"]
+
+
+ def setup(self):
+ self.last_notify = 0
+ self.notifications = 0
+
+
+ def plugin_updated(self, type_plugins):
+ if not self.getConfig('notifyupdate'):
+ return
+
+ self.notify(_("Plugins updated"), str(type_plugins))
+
+
+ def exit(self):
+ if not self.getConfig('notifyexit'):
+ return
+
+ if self.core.do_restart:
+ self.notify(_("Restarting pyLoad"))
+ else:
+ self.notify(_("Exiting pyLoad"))
+
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig('notifycaptcha'):
+ return
+
+ self.notify(_("Captcha"), _("New request waiting user input"))
+
+
+ def packageFinished(self, pypack):
+ if self.getConfig('notifypackage'):
+ self.notify(_("Package finished"), pypack.name)
+
+
+ def allDownloadsProcessed(self):
+ if not self.getConfig('notifyprocessed'):
+ return
+
+ if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
+ self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
+ else:
+ self.notify(_("All packages finished"))
+
+
+ @Expose
+ def notify(self,
+ event,
+ msg="",
+ key=self.getConfig('apikey')):
+
+ if not key:
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
+ return
+
+ elapsed_time = time.time() - self.last_notify
+
+ if elapsed_time < self.getConf("sendtimewait"):
+ return
+
+ if elapsed_time > 60:
+ self.notifications = 0
+
+ elif self.notifications >= self.getConf("sendpermin"):
+ return
+
+
+ getURL("http://www.notifymyandroid.com/publicapi/notify",
+ get={'apikey' : key,
+ 'application': "pyLoad",
+ 'event' : event,
+ 'description': msg})
+
+ self.last_notify = time.time()
+ self.notifications += 1
diff --git a/pyload/plugin/addon/AntiVirus.py b/pyload/plugin/addon/AntiVirus.py
new file mode 100644
index 000000000..e2280a0a5
--- /dev/null
+++ b/pyload/plugin/addon/AntiVirus.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+import os
+import shutil
+import subprocess
+
+try:
+ import send2trash
+except ImportError:
+ pass
+
+from pyload.plugin.Addon import Addon, Expose, threaded
+from pyload.utils import fs_encode, fs_join
+
+
+class AntiVirus(Addon):
+ __name = "AntiVirus"
+ __type = "addon"
+ __version = "0.08"
+
+ #@TODO: add trash option (use Send2Trash lib)
+ __config = [("action" , "Antivirus default;Delete;Quarantine", "Manage infected files" , "Antivirus default"),
+ ("quardir" , "folder" , "Quarantine folder" , "" ),
+ ("deltotrash", "bool" , "Move to trash (recycle bin) instead delete", True ),
+ ("scanfailed", "bool" , "Scan incompleted files (failed downloads)" , False ),
+ ("cmdfile" , "file" , "Antivirus executable" , "" ),
+ ("cmdargs" , "str" , "Scan options" , "" ),
+ ("ignore-err", "bool" , "Ignore scan errors" , False )]
+
+ __description = """Scan downloaded files with antivirus program"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+
+
+
+ @Expose
+ @threaded
+ def scan(self, pyfile, thread):
+ file = fs_encode(pyfile.plugin.lastDownload)
+ filename = os.path.basename(pyfile.plugin.lastDownload)
+ cmdfile = fs_encode(self.getConfig('cmdfile'))
+ cmdargs = fs_encode(self.getConfig('cmdargs').strip())
+
+ if not os.path.isfile(file) or not os.path.isfile(cmdfile):
+ return
+
+ thread.addActive(pyfile)
+ pyfile.setCustomStatus(_("virus scanning"))
+ pyfile.setProgress(0)
+
+ try:
+ p = subprocess.Popen([cmdfile, cmdargs, file], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ out, err = map(str.strip, p.communicate())
+
+ if out:
+ self.logInfo(filename, out)
+
+ if err:
+ self.logWarning(filename, err)
+ if not self.getConfig('ignore-err'):
+ self.logDebug("Delete/Quarantine task is aborted")
+ return
+
+ if p.returncode:
+ pyfile.error = _("infected file")
+ action = self.getConfig('action')
+ try:
+ if action == "Delete":
+ if not self.getConfig('deltotrash'):
+ os.remove(file)
+
+ else:
+ try:
+ send2trash.send2trash(file)
+
+ except Exception:
+ self.logWarning(_("Unable to move file to trash, move to quarantine instead"))
+ pyfile.setCustomStatus(_("file moving"))
+ shutil.move(file, self.getConfig('quardir'))
+
+ elif action == "Quarantine":
+ pyfile.setCustomStatus(_("file moving"))
+ shutil.move(file, self.getConfig('quardir'))
+
+ except (IOError, shutil.Error), e:
+ self.logError(filename, action + " action failed!", e)
+
+ elif not out and not err:
+ self.logDebug(filename, "No infected file found")
+
+ finally:
+ pyfile.setProgress(100)
+ thread.finishFile(pyfile)
+
+
+ def downloadFinished(self, pyfile):
+ return self.scan(pyfile)
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status == 8 and self.getConfig('scanfailed'):
+ return self.scan(pyfile)
diff --git a/pyload/plugin/addon/Checksum.py b/pyload/plugin/addon/Checksum.py
new file mode 100644
index 000000000..ada52d56e
--- /dev/null
+++ b/pyload/plugin/addon/Checksum.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import hashlib
+import os
+import re
+import zlib
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_join, fs_encode
+
+
+def computeChecksum(local_file, algorithm):
+ if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")):
+ h = getattr(hashlib, algorithm)()
+
+ with open(local_file, 'rb') as f:
+ for chunk in iter(lambda: f.read(128 * h.block_size), ''):
+ h.update(chunk)
+
+ return h.hexdigest()
+
+ elif algorithm in ("adler32", "crc32"):
+ hf = getattr(zlib, algorithm)
+ last = 0
+
+ with open(local_file, 'rb') as f:
+ for chunk in iter(lambda: f.read(8192), ''):
+ last = hf(chunk, last)
+
+ return "%x" % last
+
+ else:
+ return None
+
+
+class Checksum(Addon):
+ __name = "Checksum"
+ __type = "addon"
+ __version = "0.16"
+
+ __config = [("activated" , "bool" , "Activated" , True ),
+ ("check_checksum", "bool" , "Check checksum? (If False only size will be verified)", True ),
+ ("check_action" , "fail;retry;nothing", "What to do if check fails?" , "retry"),
+ ("max_tries" , "int" , "Number of retries" , 2 ),
+ ("retry_action" , "fail;nothing" , "What to do if all retries fail?" , "fail" ),
+ ("wait_time" , "int" , "Time to wait before each retry (seconds)" , 1 )]
+
+ __description = """Verify downloaded file size and checksum"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("stickell" , "l.stickell@yahoo.it")]
+
+
+ methods = {'sfv' : 'crc32',
+ 'crc' : 'crc32',
+ 'hash': 'md5'}
+ regexps = {'sfv' : r'^(?P<NAME>[^;].+)\s+(?P<HASH>[0-9A-Fa-f]{8})$',
+ 'md5' : r'^(?P<NAME>[0-9A-Fa-f]{32}) (?P<FILE>.+)$',
+ 'crc' : r'filename=(?P<NAME>.+)\nsize=(?P<SIZE>\d+)\ncrc32=(?P<HASH>[0-9A-Fa-f]{8})$',
+ 'default': r'^(?P<HASH>[0-9A-Fa-f]+)\s+\*?(?P<NAME>.+)$'}
+
+
+ def activate(self):
+ if not self.getConfig('check_checksum'):
+ self.logInfo(_("Checksum validation is disabled in plugin configuration"))
+
+
+ def setup(self):
+ self.algorithms = sorted(
+ getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse=True)
+
+ self.algorithms.extend(["crc32", "adler32"])
+
+ self.formats = self.algorithms + ["sfv", "crc", "hash"]
+
+
+ def downloadFinished(self, pyfile):
+ """
+ Compute checksum for the downloaded file and compare it with the hash provided by the hoster.
+ pyfile.plugin.check_data should be a dictionary which can contain:
+ a) if known, the exact filesize in bytes (e.g. "size": 123456789)
+ b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307")
+ """
+ if hasattr(pyfile.plugin, "check_data") and isinstance(pyfile.plugin.check_data, dict):
+ data = pyfile.plugin.check_data.copy()
+
+ elif hasattr(pyfile.plugin, "api_data") and isinstance(pyfile.plugin.api_data, dict):
+ data = pyfile.plugin.api_data.copy()
+
+ elif hasattr(pyfile.plugin, "info") and isinstance(pyfile.plugin.info, dict):
+ data = pyfile.plugin.info.copy()
+ data.pop('size', None) #@NOTE: Don't check file size until a similary matcher will be implemented
+
+ else:
+ return
+
+ self.logDebug(data)
+
+ if not pyfile.plugin.lastDownload:
+ self.checkFailed(pyfile, None, "No file downloaded")
+
+ local_file = fs_encode(pyfile.plugin.lastDownload)
+ # download_folder = self.config.get("general", "download_folder")
+ # local_file = fs_encode(fs_join(download_folder, pyfile.package().folder, pyfile.name))
+
+ if not os.path.isfile(local_file):
+ self.checkFailed(pyfile, None, "File does not exist")
+
+ # validate file size
+ if "size" in data:
+ api_size = int(data['size'])
+ file_size = os.path.getsize(local_file)
+
+ if api_size != file_size:
+ self.logWarning(_("File %s has incorrect size: %d B (%d expected)") % (pyfile.name, file_size, api_size))
+ self.checkFailed(pyfile, local_file, "Incorrect file size")
+
+ data.pop('size', None)
+
+ # validate checksum
+ if data and self.getConfig('check_checksum'):
+
+ if not 'md5' in data:
+ for type in ("checksum", "hashsum", "hash"):
+ if type in data:
+ data['md5'] = data[type] #@NOTE: What happens if it's not an md5 hash?
+ break
+
+ for key in self.algorithms:
+ if key in data:
+ checksum = computeChecksum(local_file, key.replace("-", "").lower())
+ if checksum:
+ if checksum == data[key].lower():
+ self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') %
+ (pyfile.name, key.upper(), checksum))
+ break
+ else:
+ self.logWarning(_("%s checksum for file %s does not match (%s != %s)") %
+ (key.upper(), pyfile.name, checksum, data[key]))
+ self.checkFailed(pyfile, local_file, "Checksums do not match")
+ else:
+ self.logWarning(_("Unsupported hashing algorithm"), key.upper())
+ else:
+ self.logWarning(_("Unable to validate checksum for file: ") + pyfile.name)
+
+
+ def checkFailed(self, pyfile, local_file, msg):
+ check_action = self.getConfig('check_action')
+ if check_action == "retry":
+ max_tries = self.getConfig('max_tries')
+ retry_action = self.getConfig('retry_action')
+ if pyfile.plugin.retries < max_tries:
+ if local_file:
+ os.remove(local_file)
+ pyfile.plugin.retry(max_tries, self.getConfig('wait_time'), msg)
+ elif retry_action == "nothing":
+ return
+ elif check_action == "nothing":
+ return
+ pyfile.plugin.fail(reason=msg)
+
+
+ def packageFinished(self, pypack):
+ download_folder = fs_join(self.config.get("general", "download_folder"), pypack.folder, "")
+
+ for link in pypack.getChildren().itervalues():
+ file_type = os.path.splitext(link['name'])[1][1:].lower()
+
+ if file_type not in self.formats:
+ continue
+
+ hash_file = fs_encode(fs_join(download_folder, link['name']))
+ if not os.path.isfile(hash_file):
+ self.logWarning(_("File not found"), link['name'])
+ continue
+
+ with open(hash_file) as f:
+ text = f.read()
+
+ for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text):
+ data = m.groupdict()
+ self.logDebug(link['name'], data)
+
+ local_file = fs_encode(fs_join(download_folder, data['NAME']))
+ algorithm = self.methods.get(file_type, file_type)
+ checksum = computeChecksum(local_file, algorithm)
+ if checksum == data['HASH']:
+ self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') %
+ (data['NAME'], algorithm, checksum))
+ else:
+ self.logWarning(_("%s checksum for file %s does not match (%s != %s)") %
+ (algorithm, data['NAME'], checksum, data['HASH']))
diff --git a/pyload/plugin/addon/ClickNLoad.py b/pyload/plugin/addon/ClickNLoad.py
new file mode 100644
index 000000000..4e1be807d
--- /dev/null
+++ b/pyload/plugin/addon/ClickNLoad.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import socket
+import time
+
+from threading import Lock
+
+from pyload.plugin.Addon import Addon, threaded
+
+
+def forward(source, destination):
+ try:
+ bufsize = 1024
+ bufdata = source.recv(bufsize)
+ while bufdata:
+ destination.sendall(bufdata)
+ bufdata = source.recv(bufsize)
+ finally:
+ destination.shutdown(socket.SHUT_WR)
+ # destination.close()
+
+
+#@TODO: IPv6 support
+class ClickNLoad(Addon):
+ __name = "ClickNLoad"
+ __type = "addon"
+ __version = "0.41"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("port" , "int" , "Port" , 9666),
+ ("extern" , "bool", "Listen on the public network interface", True)]
+
+ __description = """Click'n'Load addon plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.de" ),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def activate(self):
+ if not self.core.config.get("webui", "activated"):
+ return
+
+ ip = "" if self.getConfig('extern') else "127.0.0.1"
+ webport = self.core.config.get("webui", "port")
+ cnlport = self.getConfig('port')
+
+ self.proxy(ip, webport, cnlport)
+
+
+ @threaded
+ def proxy(self, ip, webport, cnlport):
+ time.sleep(10) #@TODO: Remove in 0.4.10 (implement addon delay on startup)
+
+ self.logInfo(_("Proxy listening on %s:%s") % (ip or "0.0.0.0", cnlport))
+
+ self._server(ip, webport, cnlport)
+
+ lock = Lock()
+ lock.acquire()
+ lock.acquire()
+
+
+ @threaded
+ def _server(self, ip, webport, cnlport):
+ try:
+ dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ dock_socket.bind((ip, cnlport))
+ dock_socket.listen(5)
+
+ while True:
+ client_socket, client_addr = dock_socket.accept()
+ self.logDebug("Connection from %s:%s" % client_addr)
+
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server_socket.connect(("127.0.0.1", webport))
+
+ self.manager.startThread(forward, client_socket, server_socket)
+ self.manager.startThread(forward, server_socket, client_socket)
+
+ except socket.timeout:
+ self.logDebug("Connection timed out, retrying...")
+ return self._server(ip, webport, cnlport)
+
+ except socket.error, e:
+ self.logError(e)
+ time.sleep(240)
+ return self._server(ip, webport, cnlport)
diff --git a/pyload/plugin/addon/DeleteFinished.py b/pyload/plugin/addon/DeleteFinished.py
new file mode 100644
index 000000000..8fe0b1497
--- /dev/null
+++ b/pyload/plugin/addon/DeleteFinished.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+from pyload.database import style
+from pyload.plugin.Addon import Addon
+
+
+class DeleteFinished(Addon):
+ __name = "DeleteFinished"
+ __type = "addon"
+ __version = "1.12"
+
+ __config = [("interval" , "int" , "Check interval in hours" , 72 ),
+ ("deloffline", "bool", "Delete package with offline links", False)]
+
+ __description = """Automatically delete all finished packages from queue"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ MIN_CHECK_INTERVAL = 1 * 60 * 60 #: 1 hour
+
+
+ ## overwritten methods ##
+
+
+ def setup(self):
+ self.interval = self.MIN_CHECK_INTERVAL
+
+
+ def periodical(self):
+ if not self.info['sleep']:
+ deloffline = self.getConfig('deloffline')
+ mode = '0,1,4' if deloffline else '0,4'
+ msg = _('delete all finished packages in queue list (%s packages with offline links)')
+ self.logInfo(msg % (_('including') if deloffline else _('excluding')))
+ self.deleteFinished(mode)
+ self.info['sleep'] = True
+ self.addEvent('packageFinished', self.wakeup)
+
+
+ # def pluginConfigChanged(self, plugin, name, value):
+ # if name == "interval" and value != self.interval:
+ # self.interval = value * 3600
+ # self.initPeriodical()
+
+
+ def deactivate(self):
+ self.manager.removeEvent('packageFinished', self.wakeup)
+
+
+ def activate(self):
+ self.info['sleep'] = True
+ # interval = self.getConfig('interval')
+ # self.pluginConfigChanged(self.getClassName(), 'interval', interval)
+ self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60 * 60)
+ self.addEvent('packageFinished', self.wakeup)
+ self.initPeriodical()
+
+
+ ## own methods ##
+
+
+ @style.queue
+ def deleteFinished(self, mode):
+ self.c.execute('DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE package=packages.id AND status NOT IN (%s))' % mode)
+ self.c.execute('DELETE FROM links WHERE NOT EXISTS(SELECT 1 FROM packages WHERE id=links.package)')
+
+
+ def wakeup(self, pypack):
+ self.manager.removeEvent('packageFinished', self.wakeup)
+ self.info['sleep'] = False
+
+
+ ## event managing ##
+
+
+ def addEvent(self, event, func):
+ """Adds an event listener for event name"""
+ if event in self.manager.events:
+ if func in self.manager.events[event]:
+ self.logDebug("Function already registered", func)
+ else:
+ self.manager.events[event].append(func)
+ else:
+ self.manager.events[event] = [func]
diff --git a/pyload/plugin/addon/DownloadScheduler.py b/pyload/plugin/addon/DownloadScheduler.py
new file mode 100644
index 000000000..62cee31c5
--- /dev/null
+++ b/pyload/plugin/addon/DownloadScheduler.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Addon import Addon
+
+
+class DownloadScheduler(Addon):
+ __name = "DownloadScheduler"
+ __type = "addon"
+ __version = "0.22"
+
+ __config = [("timetable", "str" , "List time periods as hh:mm full or number(kB/s)" , "0:00 full, 7:00 250, 10:00 0, 17:00 150"),
+ ("abort" , "bool", "Abort active downloads when start period with speed 0", False )]
+
+ __description = """Download Scheduler"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def activate(self):
+ self.updateSchedule()
+
+
+ def updateSchedule(self, schedule=None):
+ if schedule is None:
+ schedule = self.getConfig('timetable')
+
+ schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)",
+ schedule.lower().replace("full", "-1").replace("none", "0"))
+ if not schedule:
+ self.logError(_("Invalid schedule"))
+ return
+
+ t0 = time.localtime()
+ now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X")
+ schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now])
+
+ self.logDebug("Schedule", schedule)
+
+ for i, v in enumerate(schedule):
+ if v[3] == "X":
+ last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)]
+ self.logDebug("Now/Last/Next", now, last, next)
+
+ self.setDownloadSpeed(last[3])
+
+ next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400
+ self.core.scheduler.removeJob(self.cb)
+ self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False)
+
+
+ def setDownloadSpeed(self, speed):
+ if speed == 0:
+ abort = self.getConfig('abort')
+ self.logInfo(_("Stopping download server. (Running downloads will %sbe aborted.)") % '' if abort else _('not '))
+ self.core.api.pauseServer()
+ if abort:
+ self.core.api.stopAllDownloads()
+ else:
+ self.core.api.unpauseServer()
+
+ if speed > 0:
+ self.logInfo(_("Setting download speed to %d kB/s") % speed)
+ self.core.api.setConfigValue("download", "limit_speed", 1)
+ self.core.api.setConfigValue("download", "max_speed", speed)
+ else:
+ self.logInfo(_("Setting download speed to FULL"))
+ self.core.api.setConfigValue("download", "limit_speed", 0)
+ self.core.api.setConfigValue("download", "max_speed", -1)
diff --git a/pyload/plugin/addon/ExternalScripts.py b/pyload/plugin/addon/ExternalScripts.py
new file mode 100644
index 000000000..341dd6dcd
--- /dev/null
+++ b/pyload/plugin/addon/ExternalScripts.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+
+import os
+import subprocess
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode, fs_join
+
+
+class ExternalScripts(Addon):
+ __name = "ExternalScripts"
+ __type = "addon"
+ __version = "0.39"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("waitend" , "bool", "Wait script ending", False)]
+
+ __description = """Run external scripts"""
+ __license = "GPLv3"
+ __authors = [("mkaay" , "mkaay@mkaay.de"),
+ ("RaNaN" , "ranan@pyload.org"),
+ ("spoob" , "spoob@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+ event_list = ["archive_extract_failed", "archive_extracted" ,
+ "package_extract_failed", "package_extracted" ,
+ "all_archives_extracted", "all_archives_processed",
+ "allDownloadsFinished" , "allDownloadsProcessed" ,
+ "packageDeleted"]
+
+
+ def setup(self):
+ self.info['oldip'] = None
+ self.scripts = {}
+
+ folders = ["pyload_start", "pyload_restart", "pyload_stop",
+ "before_reconnect", "after_reconnect",
+ "download_preparing", "download_failed", "download_finished",
+ "archive_extract_failed", "archive_extracted",
+ "package_finished", "package_deleted", "package_extract_failed", "package_extracted",
+ "all_downloads_processed", "all_downloads_finished", #@TODO: Invert `all_downloads_processed`, `all_downloads_finished` order in 0.4.10
+ "all_archives_extracted", "all_archives_processed"]
+
+ for folder in folders:
+ self.scripts[folder] = []
+ for dir in (pypath, ''):
+ self.initPluginType(folder, os.path.join(dir, 'scripts', folder))
+
+ for script_type, names in self.scripts.iteritems():
+ if names:
+ self.logInfo(_("Installed scripts for: ") + script_type, ", ".join(map(os.path.basename, names)))
+
+ self.pyload_start()
+
+
+ def initPluginType(self, name, dir):
+ if not os.path.isdir(dir):
+ try:
+ os.makedirs(dir)
+
+ except OSError, e:
+ self.logDebug(e)
+ return
+
+ for filename in os.listdir(dir):
+ file = fs_join(dir, filename)
+
+ if not os.path.isfile(file):
+ continue
+
+ if filename[0] in ("#", "_") or filename.endswith("~") or filename.endswith(".swp"):
+ continue
+
+ if not os.access(file, os.X_OK):
+ self.logWarning(_("Script not executable:") + " %s/%s" % (name, filename))
+
+ self.scripts[name].append(file)
+
+
+ def callScript(self, script, *args):
+ try:
+ cmd_args = [fs_encode(str(x) if not isinstance(x, basestring) else x) for x in args]
+ cmd = [script] + cmd_args
+
+ self.logDebug("Executing: %s" % os.path.abspath(script), "Args: " + ' '.join(cmd_args))
+
+ p = subprocess.Popen(cmd, bufsize=-1) #@NOTE: output goes to pyload
+ if self.getConfig('waitend'):
+ p.communicate()
+
+ except Exception, e:
+ try:
+ self.logError(_("Runtime error: %s") % os.path.abspath(script), e)
+ except Exception:
+ self.logError(_("Runtime error: %s") % os.path.abspath(script), _("Unknown error"))
+
+
+ def pyload_start(self):
+ for script in self.scripts['pyload_start']:
+ self.callScript(script)
+
+
+ def exit(self):
+ for script in self.scripts['pyload_restart' if self.core.do_restart else 'pyload_stop']:
+ self.callScript(script)
+
+
+ def beforeReconnecting(self, ip):
+ for script in self.scripts['before_reconnect']:
+ self.callScript(script, ip)
+ self.info['oldip'] = ip
+
+
+ def afterReconnecting(self, ip):
+ for script in self.scripts['after_reconnect']:
+ self.callScript(script, ip, self.info['oldip']) #@TODO: Use built-in oldip in 0.4.10
+
+
+ def downloadPreparing(self, pyfile):
+ for script in self.scripts['download_preparing']:
+ self.callScript(script, pyfile.id, pyfile.name, None, pyfile.pluginname, pyfile.url)
+
+
+ def downloadFailed(self, pyfile):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['download_failed']:
+ file = fs_join(download_folder, pyfile.name)
+ self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
+
+
+ def downloadFinished(self, pyfile):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['download_finished']:
+ file = fs_join(download_folder, pyfile.name)
+ self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
+
+
+ def archive_extract_failed(self, pyfile, archive):
+ for script in self.scripts['archive_extract_failed']:
+ self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
+
+
+ def archive_extracted(self, pyfile, archive):
+ for script in self.scripts['archive_extracted']:
+ self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
+
+
+ def packageFinished(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_finished']:
+ self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
+
+
+ def packageDeleted(self, pid):
+ pack = self.core.api.getPackageInfo(pid)
+
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_deleted']:
+ self.callScript(script, pack.id, pack.name, download_folder, pack.password)
+
+
+ def package_extract_failed(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_extract_failed']:
+ self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
+
+
+ def package_extracted(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_extracted']:
+ self.callScript(script, pypack.id, pypack.name, download_folder)
+
+
+ def allDownloadsFinished(self):
+ for script in self.scripts['all_downloads_finished']:
+ self.callScript(script)
+
+
+ def allDownloadsProcessed(self):
+ for script in self.scripts['all_downloads_processed']:
+ self.callScript(script)
+
+
+ def all_archives_extracted(self):
+ for script in self.scripts['all_archives_extracted']:
+ self.callScript(script)
+
+
+ def all_archives_processed(self):
+ for script in self.scripts['all_archives_processed']:
+ self.callScript(script)
diff --git a/pyload/plugin/addon/ExtractArchive.py b/pyload/plugin/addon/ExtractArchive.py
new file mode 100644
index 000000000..71802cbfe
--- /dev/null
+++ b/pyload/plugin/addon/ExtractArchive.py
@@ -0,0 +1,562 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import sys
+import traceback
+
+# monkey patch bug in python 2.6 and lower
+# http://bugs.python.org/issue6122, http://bugs.python.org/issue1236, http://bugs.python.org/issue1731717
+if sys.version_info < (2, 7) and os.name != "nt":
+ import errno
+ import subprocess
+
+
+ def _eintr_retry_call(func, *args):
+ while True:
+ try:
+ return func(*args)
+
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
+
+
+ # unsued timeout option for older python version
+
+
+ def wait(self, timeout=0):
+ """Wait for child process to terminate. Returns returncode
+ attribute."""
+ if self.returncode is None:
+ try:
+ pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
+
+ except OSError, e:
+ if e.errno != errno.ECHILD:
+ raise
+ # This happens if SIGCLD is set to be ignored or waiting
+ # for child processes has otherwise been disabled for our
+ # process. This child is dead, we can't get the status.
+ sts = 0
+ self._handle_exitstatus(sts)
+ return self.returncode
+
+ subprocess.Popen.wait = wait
+
+try:
+ import send2trash
+except ImportError:
+ pass
+
+from copy import copy
+if os.name != "nt":
+ from grp import getgrnam
+ from pwd import getpwnam
+
+from pyload.plugin.Addon import Addon, threaded, Expose
+from pyload.plugin.Extractor import ArchiveError, CRCError, PasswordError
+from pyload.plugin.internal.SimpleHoster import replace_patterns
+from pyload.utils import fs_encode, fs_join, uniqify
+
+
+class ArchiveQueue(object):
+
+ def __init__(self, plugin, storage):
+ self.plugin = plugin
+ self.storage = storage
+
+
+ def get(self):
+ try:
+ return [int(pid) for pid in self.plugin.getStorage("ExtractArchive:%s" % self.storage, "").decode('base64').split()]
+ except Exception:
+ return []
+
+
+ def set(self, value):
+ if isinstance(value, list):
+ item = str(value)[1:-1].replace(' ', '').replace(',', ' ')
+ else:
+ item = str(value).strip()
+ return self.plugin.setStorage("ExtractArchive:%s" % self.storage, item.encode('base64')[:-1])
+
+
+ def delete(self):
+ return self.plugin.delStorage("ExtractArchive:%s" % self.storage)
+
+
+ def add(self, item):
+ queue = self.get()
+ if item not in queue:
+ return self.set(queue + [item])
+ else:
+ return True
+
+
+ def remove(self, item):
+ queue = self.get()
+ try:
+ queue.remove(item)
+
+ except ValueError:
+ pass
+
+ if queue == []:
+ return self.delete()
+
+ return self.set(queue)
+
+
+class ExtractArchive(Addon):
+ __name = "ExtractArchive"
+ __type = "addon"
+ __version = "1.42"
+
+ __config = [("activated" , "bool" , "Activated" , True),
+ ("fullpath" , "bool" , "Extract with full paths" , True),
+ ("overwrite" , "bool" , "Overwrite files" , False),
+ ("keepbroken" , "bool" , "Try to extract broken archives" , False),
+ ("repair" , "bool" , "Repair broken archives (RAR required)" , False),
+ ("test" , "bool" , "Test archive before extracting" , False),
+ ("usepasswordfile", "bool" , "Use password file" , True),
+ ("passwordfile" , "file" , "Password file" , "archive_password.txt"),
+ ("delete" , "bool" , "Delete archive after extraction" , True),
+ ("deltotrash" , "bool" , "Move to trash (recycle bin) instead delete", True),
+ ("subfolder" , "bool" , "Create subfolder for each package" , False),
+ ("destination" , "folder" , "Extract files to folder" , ""),
+ ("extensions" , "str" , "Extract archives ending with extension" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"),
+ ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db"),
+ ("recursive" , "bool" , "Extract archives in archives" , True),
+ ("waitall" , "bool" , "Run after all downloads was processed" , False),
+ ("renice" , "int" , "CPU priority" , 0)]
+
+ __description = """Extract different kind of archives"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz" , "immenz@gmx.net")]
+
+
+ event_list = ["allDownloadsProcessed", "packageDeleted"]
+
+ NAME_REPLACEMENTS = [(r'\.part\d+\.rar$', ".part.rar")]
+
+
+ def setup(self):
+ self.queue = ArchiveQueue(self, "Queue")
+ self.failed = ArchiveQueue(self, "Failed")
+
+ self.interval = 60
+ self.extracting = False
+ self.lastPackage = False
+ self.extractors = []
+ self.passwords = []
+ self.repair = False
+
+
+ def activate(self):
+ for p in ("UnRar", "SevenZip", "UnZip"):
+ try:
+ module = self.core.pluginManager.loadModule("extractor", p)
+ klass = getattr(module, p)
+ if klass.isUsable():
+ self.extractors.append(klass)
+ if klass.REPAIR:
+ self.repair = self.getConfig('repair')
+
+ except OSError, e:
+ if e.errno == 2:
+ self.logWarning(_("No %s installed") % p)
+ else:
+ self.logWarning(_("Could not activate: %s") % p, e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ except Exception, e:
+ self.logWarning(_("Could not activate: %s") % p, e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ if self.extractors:
+ self.logDebug(*["Found %s %s" % (Extractor.NAME, Extractor.VERSION) for Extractor in self.extractors])
+ self.extractQueued() #: Resume unfinished extractions
+ else:
+ self.logInfo(_("No Extract plugins activated"))
+
+
+ @threaded
+ def extractQueued(self, thread):
+ packages = self.queue.get()
+ while packages:
+ if self.lastPackage: #: called from allDownloadsProcessed
+ self.lastPackage = False
+ if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
+ self.manager.dispatchEvent("all_archives_extracted")
+ self.manager.dispatchEvent("all_archives_processed")
+ else:
+ if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
+ pass
+
+ packages = self.queue.get() #: check for packages added during extraction
+
+
+ @Expose
+ def extractPackage(self, *ids):
+ """ Extract packages with given id"""
+ for id in ids:
+ self.queue.add(id)
+ if not self.getConfig('waitall') and not self.extracting:
+ self.extractQueued()
+
+
+ def packageDeleted(self, pid):
+ self.queue.remove(pid)
+
+
+ def packageFinished(self, pypack):
+ self.queue.add(pypack.id)
+ if not self.getConfig('waitall') and not self.extracting:
+ self.extractQueued()
+
+
+ def allDownloadsProcessed(self):
+ self.lastPackage = True
+ if not self.extracting:
+ self.extractQueued()
+
+
+ @Expose
+ def extract(self, ids, thread=None): #@TODO: Use pypack, not pid to improve method usability
+ if not ids:
+ return False
+
+ self.extracting = True
+
+ processed = []
+ extracted = []
+ failed = []
+
+ toList = lambda string: string.replace(' ', '').replace(',', '|').replace(';', '|').split('|')
+
+ destination = self.getConfig('destination')
+ subfolder = self.getConfig('subfolder')
+ fullpath = self.getConfig('fullpath')
+ overwrite = self.getConfig('overwrite')
+ renice = self.getConfig('renice')
+ recursive = self.getConfig('recursive')
+ delete = self.getConfig('delete')
+ keepbroken = self.getConfig('keepbroken')
+
+ extensions = [x.lstrip('.').lower() for x in toList(self.getConfig('extensions'))]
+ excludefiles = toList(self.getConfig('excludefiles'))
+
+ if extensions:
+ self.logDebug("Use for extensions: %s" % "|.".join(extensions))
+
+ # reload from txt file
+ self.reloadPasswords()
+
+ download_folder = self.config.get("general", "download_folder")
+
+ # iterate packages -> extractors -> targets
+ for pid in ids:
+ pypack = self.core.files.getPackage(pid)
+
+ if not pypack:
+ self.queue.remove(pid)
+ continue
+
+ self.logInfo(_("Check package: %s") % pypack.name)
+
+ # determine output folder
+ out = fs_join(download_folder, pypack.folder, destination, "") #: force trailing slash
+
+ if subfolder:
+ out = fs_join(out, pypack.folder)
+
+ if not os.path.exists(out):
+ os.makedirs(out)
+
+ matched = False
+ success = True
+ files_ids = dict((pylink['name'], ((fs_join(download_folder, pypack.folder, pylink['name'])), pylink['id'], out)) for pylink
+ in sorted(pypack.getChildren().itervalues(), key=lambda k: k['name'])).values() #: remove duplicates
+
+ # check as long there are unseen files
+ while files_ids:
+ new_files_ids = []
+
+ if extensions:
+ files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids
+ if filter(lambda ext: fname.lower().endswith(ext), extensions)]
+
+ for Extractor in self.extractors:
+ targets = Extractor.getTargets(files_ids)
+ if targets:
+ self.logDebug("Targets for %s: %s" % (Extractor.__name__, targets))
+ matched = True
+
+ for fname, fid, fout in targets:
+ name = os.path.basename(fname)
+
+ if not os.path.exists(fname):
+ self.logDebug(name, "File not found")
+ continue
+
+ self.logInfo(name, _("Extract to: %s") % fout)
+ try:
+ pyfile = self.core.files.getFile(fid)
+ archive = Extractor(self,
+ fname,
+ fout,
+ fullpath,
+ overwrite,
+ excludefiles,
+ renice,
+ delete,
+ keepbroken,
+ fid)
+
+ thread.addActive(pyfile)
+ archive.init()
+
+ try:
+ new_files = self._extract(pyfile, archive, pypack.password)
+
+ finally:
+ pyfile.setProgress(100)
+ thread.finishFile(pyfile)
+
+ except Exception, e:
+ self.logError(name, e)
+ success = False
+ continue
+
+ # remove processed file and related multiparts from list
+ files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids
+ if fname not in archive.getDeleteFiles()]
+ self.logDebug("Extracted files: %s" % new_files)
+ self.setPermissions(new_files)
+
+ for filename in new_files:
+ file = fs_encode(fs_join(os.path.dirname(archive.filename), filename))
+ if not os.path.exists(file):
+ self.logDebug("New file %s does not exists" % filename)
+ continue
+
+ if recursive and os.path.isfile(file):
+ new_files_ids.append((filename, fid, os.path.dirname(filename))) #: append as new target
+
+ self.manager.dispatchEvent("archive_extracted", pyfile, archive)
+
+ files_ids = new_files_ids #: also check extracted files
+
+ if matched:
+ if success:
+ extracted.append(pid)
+ self.manager.dispatchEvent("package_extracted", pypack)
+
+ else:
+ failed.append(pid)
+ self.manager.dispatchEvent("package_extract_failed", pypack)
+
+ self.failed.add(pid)
+ else:
+ self.logInfo(_("No files found to extract"))
+
+ if not matched or not success and subfolder:
+ try:
+ os.rmdir(out)
+
+ except OSError:
+ pass
+
+ self.queue.remove(pid)
+
+ self.extracting = False
+ return True if not failed else False
+
+
+ def _extract(self, pyfile, archive, password):
+ name = os.path.basename(archive.filename)
+
+ pyfile.setStatus("processing")
+
+ encrypted = False
+ try:
+ self.logDebug("Password: %s" % (password or "None provided"))
+ passwords = uniqify([password] + self.getPasswords(False)) if self.getConfig('usepasswordfile') else [password]
+ for pw in passwords:
+ try:
+ if self.getConfig('test') or self.repair:
+ pyfile.setCustomStatus(_("archive testing"))
+ if pw:
+ self.logDebug("Testing with password: %s" % pw)
+ pyfile.setProgress(0)
+ archive.verify(pw)
+ pyfile.setProgress(100)
+ else:
+ archive.check(pw)
+
+ self.addPassword(pw)
+ break
+
+ except PasswordError:
+ if not encrypted:
+ self.logInfo(name, _("Password protected"))
+ encrypted = True
+
+ except CRCError, e:
+ self.logDebug(name, e)
+ self.logInfo(name, _("CRC Error"))
+
+ if self.repair:
+ self.logWarning(name, _("Repairing..."))
+
+ pyfile.setCustomStatus(_("archive repairing"))
+ pyfile.setProgress(0)
+ repaired = archive.repair()
+ pyfile.setProgress(100)
+
+ if not repaired and not self.getConfig('keepbroken'):
+ raise CRCError("Archive damaged")
+
+ self.addPassword(pw)
+ break
+
+ raise CRCError("Archive damaged")
+
+ except ArchiveError, e:
+ raise ArchiveError(e)
+
+ pyfile.setCustomStatus(_("extracting"))
+ pyfile.setProgress(0)
+
+ if not encrypted or not self.getConfig('usepasswordfile'):
+ self.logDebug("Extracting using password: %s" % (password or "None"))
+ archive.extract(password)
+ else:
+ for pw in filter(None, uniqify([password] + self.getPasswords(False))):
+ try:
+ self.logDebug("Extracting using password: %s" % pw)
+
+ archive.extract(pw)
+ self.addPassword(pw)
+ break
+
+ except PasswordError:
+ self.logDebug("Password was wrong")
+ else:
+ raise PasswordError
+
+ pyfile.setProgress(100)
+ pyfile.setStatus("processing")
+
+ delfiles = archive.getDeleteFiles()
+ self.logDebug("Would delete: " + ", ".join(delfiles))
+
+ if self.getConfig('delete'):
+ self.logInfo(_("Deleting %s files") % len(delfiles))
+
+ deltotrash = self.getConfig('deltotrash')
+ for f in delfiles:
+ file = fs_encode(f)
+ if not os.path.exists(file):
+ continue
+
+ if not deltotrash:
+ os.remove(file)
+
+ else:
+ try:
+ send2trash.send2trash(file)
+
+ except Exception:
+ self.logWarning(_("Unable to move %s to trash") % os.path.basename(f))
+
+ self.logInfo(name, _("Extracting finished"))
+ extracted_files = archive.files or archive.list()
+
+ return extracted_files
+
+ except PasswordError:
+ self.logError(name, _("Wrong password" if password else "No password found"))
+
+ except CRCError, e:
+ self.logError(name, _("CRC mismatch"), e)
+
+ except ArchiveError, e:
+ self.logError(name, _("Archive error"), e)
+
+ except Exception, e:
+ self.logError(name, _("Unknown error"), e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.manager.dispatchEvent("archive_extract_failed", pyfile, archive)
+
+ raise Exception(_("Extract failed"))
+
+
+ @Expose
+ def getPasswords(self, reload=True):
+ """ List of saved passwords """
+ if reload:
+ self.reloadPasswords()
+
+ return self.passwords
+
+
+ def reloadPasswords(self):
+ try:
+ passwords = []
+
+ file = fs_encode(self.getConfig('passwordfile'))
+ with open(file) as f:
+ for pw in f.read().splitlines():
+ passwords.append(pw)
+
+ except IOError, e:
+ self.logError(e)
+
+ else:
+ self.passwords = passwords
+
+
+ @Expose
+ def addPassword(self, password):
+ """ Adds a password to saved list"""
+ try:
+ self.passwords = uniqify([password] + self.passwords)
+
+ file = fs_encode(self.getConfig('passwordfile'))
+ with open(file, "wb") as f:
+ for pw in self.passwords:
+ f.write(pw + '\n')
+
+ except IOError, e:
+ self.logError(e)
+
+
+ def setPermissions(self, files):
+ for f in files:
+ if not os.path.exists(f):
+ continue
+
+ try:
+ if self.config.get("permission", "change_file"):
+ if os.path.isfile(f):
+ os.chmod(f, int(self.config.get("permission", "file"), 8))
+
+ elif os.path.isdir(f):
+ os.chmod(f, int(self.config.get("permission", "folder"), 8))
+
+ if self.config.get("permission", "change_dl") and os.name != "nt":
+ uid = getpwnam(self.config.get("permission", "user"))[2]
+ gid = getgrnam(self.config.get("permission", "group"))[2]
+ os.chown(f, uid, gid)
+
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), e)
diff --git a/pyload/plugin/addon/HotFolder.py b/pyload/plugin/addon/HotFolder.py
new file mode 100644
index 000000000..14d0a7ce3
--- /dev/null
+++ b/pyload/plugin/addon/HotFolder.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import time
+
+from shutil import move
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode, fs_join
+
+
+class HotFolder(Addon):
+ __name = "HotFolder"
+ __type = "addon"
+ __version = "0.14"
+
+ __config = [("folder" , "str" , "Folder to observe" , "container"),
+ ("watch_file", "bool", "Observe link file" , False ),
+ ("keep" , "bool", "Keep added containers", True ),
+ ("file" , "str" , "Link file" , "links.txt")]
+
+ __description = """Observe folder and file for changes and add container and links"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.de")]
+
+
+ def setup(self):
+ self.interval = 30
+
+
+ def activate(self):
+ self.initPeriodical()
+
+
+ def periodical(self):
+ folder = fs_encode(self.getConfig('folder'))
+ file = fs_encode(self.getConfig('file'))
+
+ try:
+ if not os.path.isdir(os.path.join(folder, "finished")):
+ os.makedirs(os.path.join(folder, "finished"))
+
+ if self.getConfig('watch_file'):
+ with open(file, "a+") as f:
+ f.seek(0)
+ content = f.read().strip()
+
+ if content:
+ f = open(file, "wb")
+ f.close()
+
+ name = "%s_%s.txt" % (file, time.strftime("%H-%M-%S_%d%b%Y"))
+
+ with open(fs_join(folder, "finished", name), "wb") as f:
+ f.write(content)
+
+ self.core.api.addPackage(f.name, [f.name], 1)
+
+ for f in os.listdir(folder):
+ path = os.path.join(folder, f)
+
+ if not os.path.isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
+ continue
+
+ newpath = os.path.join(folder, "finished", f if self.getConfig('keep') else "tmp_" + f)
+ move(path, newpath)
+
+ self.logInfo(_("Added %s from HotFolder") % f)
+ self.core.api.addPackage(f, [newpath], 1)
+
+ except (IOError, OSError), e:
+ self.logError(e)
diff --git a/pyload/plugin/addon/IRCInterface.py b/pyload/plugin/addon/IRCInterface.py
new file mode 100644
index 000000000..051d30aa9
--- /dev/null
+++ b/pyload/plugin/addon/IRCInterface.py
@@ -0,0 +1,431 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import socket
+import ssl
+import time
+import traceback
+
+from select import select
+from threading import Thread
+
+from pyload.api import PackageDoesNotExists, FileDoesNotExists
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Addon
+from pyload.utils import formatSize
+
+
+class IRCInterface(Thread, Addon):
+ __name = "IRCInterface"
+ __type = "addon"
+ __version = "0.13"
+
+ __config = [("host" , "str" , "IRC-Server Address" , "Enter your server here!"),
+ ("port" , "int" , "IRC-Server Port" , 6667 ),
+ ("ident" , "str" , "Clients ident" , "pyload-irc" ),
+ ("realname" , "str" , "Realname" , "pyload-irc" ),
+ ("ssl" , "bool", "Use SSL" , False ),
+ ("nick" , "str" , "Nickname the Client will take" , "pyLoad-IRC" ),
+ ("owner" , "str" , "Nickname the Client will accept commands from", "Enter your nick here!" ),
+ ("info_file", "bool", "Inform about every file finished" , False ),
+ ("info_pack", "bool", "Inform about every package finished" , True ),
+ ("captcha" , "bool", "Send captcha requests" , True )]
+
+ __description = """Connect to irc and let owner perform different tasks"""
+ __license = "GPLv3"
+ __authors = [("Jeix", "Jeix@hasnomail.com")]
+
+
+ def __init__(self, core, manager):
+ Thread.__init__(self)
+ Addon.__init__(self, core, manager)
+ self.setDaemon(True)
+
+
+ def activate(self):
+ self.abort = False
+ self.more = []
+ self.new_package = {}
+
+ self.start()
+
+
+ def packageFinished(self, pypack):
+ try:
+ if self.getConfig('info_pack'):
+ self.response(_("Package finished: %s") % pypack.name)
+ except Exception:
+ pass
+
+
+ def downloadFinished(self, pyfile):
+ try:
+ if self.getConfig('info_file'):
+ self.response(
+ _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname})
+ except Exception:
+ pass
+
+
+ def captchaTask(self, task):
+ if self.getConfig('captcha') and task.isTextual():
+ task.handler.append(self)
+ task.setWaiting(60)
+
+ html = getURL("http://www.freeimagehosting.net/upload.php",
+ post={"attached": (pycurl.FORM_FILE, task.captchaFile)}, multipart=True)
+
+ url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", html).group(1)
+ self.response(_("New Captcha Request: %s") % url)
+ self.response(_("Answer with 'c %s text on the captcha'") % task.id)
+
+
+ def run(self):
+ # connect to IRC etc.
+ self.sock = socket.socket()
+ host = self.getConfig('host')
+ self.sock.connect((host, self.getConfig('port')))
+
+ if self.getConfig('ssl'):
+ self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE) #@TODO: support certificate
+
+ nick = self.getConfig('nick')
+ self.sock.send("NICK %s\r\n" % nick)
+ self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick))
+ for t in self.getConfig('owner').split():
+ if t.strip().startswith("#"):
+ self.sock.send("JOIN %s\r\n" % t.strip())
+ self.logInfo(_("Connected to"), host)
+ self.logInfo(_("Switching to listening mode!"))
+ try:
+ self.main_loop()
+
+ except IRCError, ex:
+ self.sock.send("QUIT :byebye\r\n")
+ traceback.print_exc()
+ self.sock.close()
+
+
+ def main_loop(self):
+ readbuffer = ""
+ while True:
+ time.sleep(1)
+ fdset = select([self.sock], [], [], 0)
+ if self.sock not in fdset[0]:
+ continue
+
+ if self.abort:
+ raise IRCError("quit")
+
+ readbuffer += self.sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ line = line.rstrip()
+ first = line.split()
+
+ if first[0] == "PING":
+ self.sock.send("PONG %s\r\n" % first[1])
+
+ if first[0] == "ERROR":
+ raise IRCError(line)
+
+ msg = line.split(None, 3)
+ if len(msg) < 4:
+ continue
+
+ msg = {
+ "origin": msg[0][1:],
+ "action": msg[1],
+ "target": msg[2],
+ "text": msg[3][1:]
+ }
+
+ self.handle_events(msg)
+
+
+ def handle_events(self, msg):
+ if not msg['origin'].split("!", 1)[0] in self.getConfig('owner').split():
+ return
+
+ if msg['target'].split("!", 1)[0] != self.getConfig('nick'):
+ return
+
+ if msg['action'] != "PRIVMSG":
+ return
+
+ # HANDLE CTCP ANTI FLOOD/BOT PROTECTION
+ if msg['text'] == "\x01VERSION\x01":
+ self.logDebug("Sending CTCP VERSION")
+ self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
+ return
+ elif msg['text'] == "\x01TIME\x01":
+ self.logDebug("Sending CTCP TIME")
+ self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
+ return
+ elif msg['text'] == "\x01LAG\x01":
+ self.logDebug("Received CTCP LAG") #: don't know how to answer
+ return
+
+ trigger = "pass"
+ args = None
+
+ try:
+ temp = msg['text'].split()
+ trigger = temp[0]
+ if len(temp) > 1:
+ args = temp[1:]
+ except Exception:
+ pass
+
+ handler = getattr(self, "event_%s" % trigger, self.event_pass)
+ try:
+ res = handler(args)
+ for line in res:
+ self.response(line, msg['origin'])
+ except Exception, e:
+ self.logError(e)
+
+
+ def response(self, msg, origin=""):
+ if origin == "":
+ for t in self.getConfig('owner').split():
+ self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg))
+ else:
+ self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg))
+
+
+ #### Events
+
+
+ def event_pass(self, args):
+ return []
+
+
+ def event_status(self, args):
+ downloads = self.core.api.statusDownloads()
+ if not downloads:
+ return ["INFO: There are no active downloads currently."]
+
+ temp_progress = ""
+ lines = ["ID - Name - Status - Speed - ETA - Progress"]
+ for data in downloads:
+
+ if data.status == 5:
+ temp_progress = data.format_wait
+ else:
+ temp_progress = "%d%% (%s)" % (data.percent, data.format_size)
+
+ lines.append("#%d - %s - %s - %s - %s - %s" %
+ (
+ data.fid,
+ data.name,
+ data.statusmsg,
+ "%s/s" % formatSize(data.speed),
+ "%s" % data.format_eta,
+ temp_progress
+ ))
+ return lines
+
+
+ def event_queue(self, args):
+ ps = self.core.api.getQueueData()
+
+ if not ps:
+ return ["INFO: There are no packages in queue."]
+
+ lines = []
+ for pack in ps:
+ lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
+
+ return lines
+
+
+ def event_collector(self, args):
+ ps = self.core.api.getCollectorData()
+ if not ps:
+ return ["INFO: No packages in collector!"]
+
+ lines = []
+ for pack in ps:
+ lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
+
+ return lines
+
+
+ def event_info(self, args):
+ if not args:
+ return ["ERROR: Use info like this: info <id>"]
+
+ info = None
+ try:
+ info = self.core.api.getFileData(int(args[0]))
+
+ except FileDoesNotExists:
+ return ["ERROR: Link doesn't exists."]
+
+ return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)]
+
+
+ def event_packinfo(self, args):
+ if not args:
+ return ["ERROR: Use packinfo like this: packinfo <id>"]
+
+ lines = []
+ pack = None
+ try:
+ pack = self.core.api.getPackageData(int(args[0]))
+
+ except PackageDoesNotExists:
+ return ["ERROR: Package doesn't exists."]
+
+ id = args[0]
+
+ self.more = []
+
+ lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links)))
+ for pyfile in pack.links:
+ self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size,
+ pyfile.statusmsg, pyfile.plugin))
+
+ if len(self.more) < 6:
+ lines.extend(self.more)
+ self.more = []
+ else:
+ lines.extend(self.more[:6])
+ self.more = self.more[6:]
+ lines.append("%d more links do display." % len(self.more))
+
+ return lines
+
+
+ def event_more(self, args):
+ if not self.more:
+ return ["No more information to display."]
+
+ lines = self.more[:6]
+ self.more = self.more[6:]
+ lines.append("%d more links do display." % len(self.more))
+
+ return lines
+
+
+ def event_start(self, args):
+ self.core.api.unpauseServer()
+ return ["INFO: Starting downloads."]
+
+
+ def event_stop(self, args):
+ self.core.api.pauseServer()
+ return ["INFO: No new downloads will be started."]
+
+
+ def event_add(self, args):
+ if len(args) < 2:
+ return ['ERROR: Add links like this: "add <packagename|id> links". ',
+ "This will add the link <link> to to the package <package> / the package with id <id>!"]
+
+ pack = args[0].strip()
+ links = [x.strip() for x in args[1:]]
+
+ count_added = 0
+ count_failed = 0
+ try:
+ id = int(pack)
+ pack = self.core.api.getPackageData(id)
+ if not pack:
+ return ["ERROR: Package doesn't exists."]
+
+ # TODO add links
+
+ return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack['name'], id)]
+
+ except Exception:
+ # create new package
+ id = self.core.api.addPackage(pack, links, 1)
+ return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))]
+
+
+ def event_del(self, args):
+ if len(args) < 2:
+ return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
+
+ if args[0] == "-p":
+ ret = self.core.api.deletePackages(map(int, args[1:]))
+ return ["INFO: Deleted %d packages!" % len(args[1:])]
+
+ elif args[0] == "-l":
+ ret = self.core.api.delLinks(map(int, args[1:]))
+ return ["INFO: Deleted %d links!" % len(args[1:])]
+
+ else:
+ return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
+
+
+ def event_push(self, args):
+ if not args:
+ return ["ERROR: Push package to queue like this: push <package id>"]
+
+ id = int(args[0])
+ try:
+ info = self.core.api.getPackageInfo(id)
+ except PackageDoesNotExists:
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.core.api.pushToQueue(id)
+ return ["INFO: Pushed package #%d to queue." % id]
+
+
+ def event_pull(self, args):
+ if not args:
+ return ["ERROR: Pull package from queue like this: pull <package id>."]
+
+ id = int(args[0])
+ if not self.core.api.getPackageData(id):
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.core.api.pullFromQueue(id)
+ return ["INFO: Pulled package #%d from queue to collector." % id]
+
+
+ def event_c(self, args):
+ """ captcha answer """
+ if not args:
+ return ["ERROR: Captcha ID missing."]
+
+ task = self.core.captchaManager.getTaskByID(args[0])
+ if not task:
+ return ["ERROR: Captcha Task with ID %s does not exists." % args[0]]
+
+ task.setResult(" ".join(args[1:]))
+ return ["INFO: Result %s saved." % " ".join(args[1:])]
+
+
+ def event_help(self, args):
+ lines = ["The following commands are available:",
+ "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)",
+ "queue Shows all packages in the queue",
+ "collector Shows all packages in collector",
+ "del -p|-l <id> [...] Deletes all packages|links with the ids specified",
+ "info <id> Shows info of the link with id <id>",
+ "packinfo <id> Shows info of the package with id <id>",
+ "more Shows more info when the result was truncated",
+ "start Starts all downloads",
+ "stop Stops the download (but not abort active downloads)",
+ "push <id> Push package to queue",
+ "pull <id> Pull package from queue",
+ "status Show general download status",
+ "help Shows this help message"]
+ return lines
+
+
+class IRCError(Exception):
+
+ def __init__(self, value):
+ self.value = value
+
+
+ def __str__(self):
+ return repr(self.value)
diff --git a/pyload/plugin/addon/JustPremium.py b/pyload/plugin/addon/JustPremium.py
new file mode 100644
index 000000000..03a2b9261
--- /dev/null
+++ b/pyload/plugin/addon/JustPremium.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Addon import Addon
+
+
+class JustPremium(Addon):
+ __name = "JustPremium"
+ __type = "addon"
+ __version = "0.22"
+
+ __config = [("excluded", "str", "Exclude hosters (comma separated)", ""),
+ ("included", "str", "Include hosters (comma separated)", "")]
+
+ __description = """Remove not-premium links from added urls"""
+ __license = "GPLv3"
+ __authors = [("mazleu" , "mazleica@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("immenz" , "immenz@gmx.net" )]
+
+
+ event_list = ["linksAdded"]
+
+
+ def linksAdded(self, links, pid):
+ hosterdict = self.core.pluginManager.hosterPlugins
+ linkdict = self.core.api.checkURLs(links)
+
+ premiumplugins = set(account.type for account in self.core.api.getAccounts(False) \
+ if account.valid and account.premium)
+ multihosters = set(hoster for hoster in self.core.pluginManager.hosterPlugins \
+ if 'new_name' in hosterdict[hoster] \
+ and hosterdict[hoster]['new_name'] in premiumplugins)
+
+ excluded = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
+ self.getConfig('excluded').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
+ included = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
+ self.getConfig('included').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
+
+ hosterlist = (premiumplugins | multihosters).union(excluded).difference(included)
+
+ #: Found at least one hoster with account or multihoster
+ if not any( True for pluginname in linkdict if pluginname in hosterlist ):
+ return
+
+ for pluginname in set(linkdict.keys()) - hosterlist:
+ self.logInfo(_("Remove links of plugin: %s") % pluginname)
+ for link in linkdict[pluginname]:
+ self.logDebug("Remove link: %s" % link)
+ links.remove(link)
diff --git a/pyload/plugin/addon/MergeFiles.py b/pyload/plugin/addon/MergeFiles.py
new file mode 100644
index 000000000..4f95ef9d7
--- /dev/null
+++ b/pyload/plugin/addon/MergeFiles.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+import traceback
+
+from pyload.plugin.Addon import Addon, threaded
+from pyload.utils import fs_join
+
+
+class MergeFiles(Addon):
+ __name = "MergeFiles"
+ __type = "addon"
+ __version = "0.14"
+
+ __config = [("activated", "bool", "Activated", True)]
+
+ __description = """Merges parts splitted with hjsplit"""
+ __license = "GPLv3"
+ __authors = [("and9000", "me@has-no-mail.com")]
+
+
+ BUFFER_SIZE = 4096
+
+
+ @threaded
+ def packageFinished(self, pack):
+ files = {}
+ fid_dict = {}
+ for fid, data in pack.getChildren().iteritems():
+ if re.search("\.\d{3}$", data['name']):
+ if data['name'][:-4] not in files:
+ files[data['name'][:-4]] = []
+ files[data['name'][:-4]].append(data['name'])
+ files[data['name'][:-4]].sort()
+ fid_dict[data['name']] = fid
+
+ download_folder = self.config.get("general", "download_folder")
+
+ if self.config.get("general", "folder_per_package"):
+ download_folder = fs_join(download_folder, pack.folder)
+
+ for name, file_list in files.iteritems():
+ self.logInfo(_("Starting merging of"), name)
+
+ with open(fs_join(download_folder, name), "wb") as final_file:
+ for splitted_file in file_list:
+ self.logDebug("Merging part", splitted_file)
+
+ pyfile = self.core.files.getFile(fid_dict[splitted_file])
+
+ pyfile.setStatus("processing")
+
+ try:
+ with open(fs_join(download_folder, splitted_file), "rb") as s_file:
+ size_written = 0
+ s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file)))
+
+ while True:
+ f_buffer = s_file.read(self.BUFFER_SIZE)
+ if f_buffer:
+ final_file.write(f_buffer)
+ size_written += self.BUFFER_SIZE
+ pyfile.setProgress((size_written * 100) / s_file_size)
+ else:
+ break
+
+ self.logDebug("Finished merging part", splitted_file)
+
+ except Exception, e:
+ traceback.print_exc()
+
+ finally:
+ pyfile.setProgress(100)
+ pyfile.setStatus("finished")
+ pyfile.release()
+
+ self.logInfo(_("Finished merging of"), name)
diff --git a/pyload/plugin/addon/MultiHome.py b/pyload/plugin/addon/MultiHome.py
new file mode 100644
index 000000000..bca6493db
--- /dev/null
+++ b/pyload/plugin/addon/MultiHome.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Addon import Addon
+
+
+class MultiHome(Addon):
+ __name = "MultiHome"
+ __type = "addon"
+ __version = "0.12"
+
+ __config = [("interfaces", "str", "Interfaces", "None")]
+
+ __description = """Ip address changer"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def setup(self):
+ self.register = {}
+ self.interfaces = []
+
+ self.parseInterfaces(self.getConfig('interfaces').split(";"))
+
+ if not self.interfaces:
+ self.parseInterfaces([self.config.get("download", "interface")])
+ self.setConfig("interfaces", self.toConfig())
+
+
+ def toConfig(self):
+ return ";".join(i.adress for i in self.interfaces)
+
+
+ def parseInterfaces(self, interfaces):
+ for interface in interfaces:
+ if not interface or str(interface).lower() == "none":
+ continue
+ self.interfaces.append(Interface(interface))
+
+
+ def activate(self):
+ requestFactory = self.core.requestFactory
+ oldGetRequest = requestFactory.getRequest
+
+
+ def getRequest(pluginName, account=None):
+ iface = self.bestInterface(pluginName, account)
+ if iface:
+ iface.useFor(pluginName, account)
+ requestFactory.iface = lambda: iface.adress
+ self.logDebug("Using address", iface.adress)
+ return oldGetRequest(pluginName, account)
+
+ requestFactory.getRequest = getRequest
+
+
+ def bestInterface(self, pluginName, account):
+ best = None
+ for interface in self.interfaces:
+ if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account):
+ best = interface
+ return best
+
+
+class Interface(object):
+
+ def __init__(self, adress):
+ self.adress = adress
+ self.history = {}
+
+
+ def lastPluginAccess(self, pluginName, account):
+ if (pluginName, account) in self.history:
+ return self.history[(pluginName, account)]
+ return 0
+
+
+ def useFor(self, pluginName, account):
+ self.history[(pluginName, account)] = time.time()
+
+
+ def __repr__(self):
+ return "<Interface - %s>" % self.adress
diff --git a/pyload/plugin/addon/RestartFailed.py b/pyload/plugin/addon/RestartFailed.py
new file mode 100644
index 000000000..8a3a36fe1
--- /dev/null
+++ b/pyload/plugin/addon/RestartFailed.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Addon import Addon
+
+
+class RestartFailed(Addon):
+ __name = "RestartFailed"
+ __type = "addon"
+ __version = "1.58"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("interval" , "int" , "Check interval in minutes", 90 )]
+
+ __description = """Restart all the failed downloads in queue"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ MIN_CHECK_INTERVAL = 15 * 60 #: 15 minutes
+
+
+ # def pluginConfigChanged(self, plugin, name, value):
+ # if name == "interval":
+ # interval = value * 60
+ # if self.MIN_CHECK_INTERVAL <= interval != self.interval:
+ # self.core.scheduler.removeJob(self.cb)
+ # self.interval = interval
+ # self.initPeriodical()
+ # else:
+ # self.logDebug("Invalid interval value, kept current")
+
+
+ def periodical(self):
+ self.logDebug(_("Restart failed downloads"))
+ self.core.api.restartFailed()
+
+
+ def activate(self):
+ # self.pluginConfigChanged(self.getClassName(), "interval", self.getConfig('interval'))
+ self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60)
+ self.initPeriodical()
diff --git a/pyload/plugin/addon/SkipRev.py b/pyload/plugin/addon/SkipRev.py
new file mode 100644
index 000000000..b54e66af5
--- /dev/null
+++ b/pyload/plugin/addon/SkipRev.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+import urlparse
+
+from types import MethodType
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Addon import Addon
+from pyload.plugin.Plugin import SkipDownload
+
+
+class SkipRev(Addon):
+ __name = "SkipRev"
+ __type = "addon"
+ __version = "0.29"
+
+ __config = [("mode" , "Auto;Manual", "Choose recovery archives to skip" , "Auto"),
+ ("revtokeep", "int" , "Number of recovery archives to keep for package", 0 )]
+
+ __description = """Skip recovery archives (.rev)"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ @staticmethod
+ def _setup(self):
+ self.pyfile.plugin._setup()
+ if self.pyfile.hasStatus("skipped"):
+ raise SkipDownload(self.pyfile.statusname or self.pyfile.pluginname)
+
+
+ def _name(self, pyfile):
+ if hasattr(pyfile.pluginmodule, "getInfo"): #@NOTE: getInfo is deprecated in 0.4.10
+ return pyfile.pluginmodule.getInfo([pyfile.url]).next()[0]
+ else:
+ self.logWarning("Unable to grab file name")
+ return urlparse.urlparse(urllib.unquote(pyfile.url)).path.split('/')[-1]
+
+
+ def _pyfile(self, link):
+ return PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
+
+
+ def downloadPreparing(self, pyfile):
+ name = self._name(pyfile)
+
+ if pyfile.statusname is _("unskipped") or not name.endswith(".rev") or not ".part" in name:
+ return
+
+ revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
+
+ if revtokeep:
+ status_list = (1, 4, 8, 9, 14) if revtokeep < 0 else (1, 3, 4, 8, 9, 14)
+ pyname = re.compile(r'%s\.part\d+\.rev$' % name.rsplit('.', 2)[0].replace('.', '\.'))
+
+ queued = [True for link in self.core.api.getPackageData(pyfile.package().id).links \
+ if link.status not in status_list and pyname.match(link.name)].count(True)
+
+ if not queued or queued < revtokeep: #: keep one rev at least in auto mode
+ return
+
+ pyfile.setCustomStatus("SkipRev", "skipped")
+
+ if not hasattr(pyfile.plugin, "_setup"):
+ # Work-around: inject status checker inside the preprocessing routine of the plugin
+ pyfile.plugin._setup = pyfile.plugin.setup
+ pyfile.plugin.setup = MethodType(self._setup, pyfile.plugin)
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status != 8 or pyfile.name.rsplit('.', 1)[-1].strip() not in ("rar", "rev"):
+ return
+
+ revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
+
+ if not revtokeep:
+ return
+
+ pyname = re.compile(r'%s\.part\d+\.rev$' % pyfile.name.rsplit('.', 2)[0].replace('.', '\.'))
+
+ for link in self.core.api.getPackageData(pyfile.package().id).links:
+ if link.status is 4 and pyname.match(link.name):
+ pylink = self._pyfile(link)
+
+ if revtokeep > -1 or pyfile.name.endswith(".rev"):
+ pylink.setStatus("queued")
+ else:
+ pylink.setCustomStatus(_("unskipped"), "queued")
+
+ self.core.files.save()
+ pylink.release()
+ return
diff --git a/pyload/plugin/addon/UnSkipOnFail.py b/pyload/plugin/addon/UnSkipOnFail.py
new file mode 100644
index 000000000..048547a1b
--- /dev/null
+++ b/pyload/plugin/addon/UnSkipOnFail.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Addon import Addon
+
+
+class UnSkipOnFail(Addon):
+ __name = "UnSkipOnFail"
+ __type = "addon"
+ __version = "0.07"
+
+ __config = [("activated", "bool", "Activated", True)]
+
+ __description = """Restart skipped duplicates when download fails"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status != 8:
+ return
+
+ msg = _("Looking for skipped duplicates of: %s (pid:%s)")
+ self.logInfo(msg % (pyfile.name, pyfile.package().id))
+
+ link = self.findDuplicate(pyfile)
+ if link:
+ self.logInfo(_("Queue found duplicate: %s (pid:%s)") % (link.name, link.packageID))
+
+ #: Change status of "link" to "new_status".
+ # "link" has to be a valid FileData object,
+ # "new_status" has to be a valid status name
+ # (i.e. "queued" for this Plugin)
+ # It creates a temporary PyFile object using
+ # "link" data, changes its status, and tells
+ # the core.files-manager to save its data.
+ pylink = self._pyfile(link)
+
+ pylink.setCustomStatus(_("unskipped"), "queued")
+
+ self.core.files.save()
+ pylink.release()
+
+ else:
+ self.logInfo(_("No duplicates found"))
+
+
+ def findDuplicate(self, pyfile):
+ """ Search all packages for duplicate links to "pyfile".
+ Duplicates are links that would overwrite "pyfile".
+ To test on duplicity the package-folder and link-name
+ of twolinks are compared (link.name).
+ So this method returns a list of all links with equal
+ package-folders and filenames as "pyfile", but except
+ the data for "pyfile" iotselöf.
+ It does MOT check the link's status.
+ """
+ queue = self.core.api.getQueue() #: get packages (w/o files, as most file data is useless here)
+
+ for package in queue:
+ #: check if package-folder equals pyfile's package folder
+ if package.folder != pyfile.package().folder:
+ continue
+
+ #: now get packaged data w/ files/links
+ pdata = self.core.api.getPackageData(package.pid)
+ for link in pdata.links:
+ #: check if link is "skipped"
+ if link.status != 4:
+ continue
+
+ #: check if link name collides with pdata's name
+ #: AND at last check if it is not pyfile itself
+ if link.name == pyfile.name and link.fid != pyfile.id:
+ return link
+
+
+ def _pyfile(self, link):
+ return PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
diff --git a/pyload/plugin/addon/UpdateManager.py b/pyload/plugin/addon/UpdateManager.py
new file mode 100644
index 000000000..84d282bde
--- /dev/null
+++ b/pyload/plugin/addon/UpdateManager.py
@@ -0,0 +1,325 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+import sys
+import time
+
+from operator import itemgetter
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Expose, Addon, threaded
+from pyload.utils import fs_join
+from pyload import __status_code__ as release_status
+
+
+# Case-sensitive os.path.exists
+def exists(path):
+ if os.path.exists(path):
+ if os.name == 'nt':
+ dir, name = os.path.split(path)
+ return name in os.listdir(dir)
+ else:
+ return True
+ else:
+ return False
+
+
+class UpdateManager(Addon):
+ __name = "UpdateManager"
+ __type = "addon"
+ __version = "0.51"
+
+ __config = [("activated", "bool", "Activated", False),
+ ("checkinterval", "int", "Check interval in hours", 8),
+ ("autorestart", "bool",
+ "Auto-restart pyLoad when required", True),
+ ("checkonstart", "bool", "Check for updates on startup", True),
+ ("checkperiod", "bool",
+ "Check for updates periodically", True),
+ ("reloadplugins", "bool",
+ "Monitor plugin code changes in debug mode", True),
+ ("nodebugupdate", "bool", "Don't update plugins in debug mode", False)]
+
+ __description = """ Check for updates """
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ SERVER_URL = "http://updatemanager.pyload.org" if release_status == 5 else None
+ MIN_CHECK_INTERVAL = 3 * 60 * 60 #: 3 hours
+
+ event_list = ["allDownloadsProcessed"]
+
+
+ def activate(self):
+ if self.checkonstart:
+ self.update()
+
+ self.initPeriodical()
+
+
+ def setup(self):
+ self.interval = 10
+ self.info = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()}
+ self.mtimes = {} #: store modification time for each plugin
+
+ if self.getConfig('checkonstart'):
+ self.core.api.pauseServer()
+ self.checkonstart = True
+ else:
+ self.checkonstart = False
+
+ self.do_restart = False
+
+
+ def allDownloadsProcessed(self):
+ if self.do_restart is True:
+ self.logWarning(_("Downloads are done, restarting pyLoad to reload the updated plugins"))
+ self.core.api.restart()
+
+
+ def periodical(self):
+ if self.core.debug:
+ if self.getConfig('reloadplugins'):
+ self.autoreloadPlugins()
+
+ if self.getConfig('nodebugupdate'):
+ return
+
+ if self.getConfig('checkperiod') \
+ and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']:
+ self.update()
+
+
+ @Expose
+ def autoreloadPlugins(self):
+ """ reload and reindex all modified plugins """
+ modules = filter(
+ lambda m: m and (m.__name__.startswith("pyload.plugin.") or
+ m.__name__.startswith("userplugins.")) and
+ m.__name__.count(".") >= 2, sys.modules.itervalues()
+ )
+
+ reloads = []
+
+ for m in modules:
+ root, type, name = m.__name__.rsplit(".", 2)
+ id = (type, name)
+ if type in self.core.pluginManager.plugins:
+ f = m.__file__.replace(".pyc", ".py")
+ if not os.path.isfile(f):
+ continue
+
+ mtime = os.stat(f).st_mtime
+
+ if id not in self.mtimes:
+ self.mtimes[id] = mtime
+ elif self.mtimes[id] < mtime:
+ reloads.append(id)
+ self.mtimes[id] = mtime
+
+ return bool(self.core.pluginManager.reloadPlugins(reloads))
+
+
+ def server_response(self):
+ try:
+ return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
+
+ except Exception:
+ self.logWarning(_("Unable to retrieve server to get updates"))
+
+
+ @Expose
+ @threaded
+ def update(self):
+ """ check for updates """
+
+ self.core.api.pauseServer()
+
+ if self._update() is 2 and self.getConfig('autorestart'):
+ if not self.core.api.statusDownloads():
+ self.core.api.restart()
+ else:
+ self.do_restart = True
+ self.logWarning(_("Downloads are active, will restart once the download is done"))
+ else:
+ self.core.api.unpauseServer()
+
+
+ def _update(self):
+ data = self.server_response()
+
+ self.info['last_check'] = time.time()
+
+ if not data:
+ exitcode = 0
+
+ elif data[0] == "None":
+ self.logInfo(_("No new pyLoad version available"))
+ exitcode = self._updatePlugins(data[1:])
+
+ elif onlyplugin:
+ exitcode = 0
+
+ else:
+ self.logInfo(_("*** New pyLoad Version %s available ***") % data[0])
+ self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
+ self.info['pyload'] = True
+ self.info['version'] = data[0]
+ exitcode = 3
+
+ # Exit codes:
+ # -1 = No plugin updated, new pyLoad version available
+ # 0 = No plugin updated
+ # 1 = Plugins updated
+ # 2 = Plugins updated, but restart required
+ return exitcode
+
+
+ def _updatePlugins(self, data):
+ """ check for plugin updates """
+
+ exitcode = 0
+ updated = []
+
+ url = data[0]
+ schema = data[1].split('|')
+
+ VERSION = re.compile(r'__version.*=.*("|\')([\d.]+)')
+
+ if "BLACKLIST" in data:
+ blacklist = data[data.index('BLACKLIST') + 1:]
+ updatelist = data[2:data.index('BLACKLIST')]
+ else:
+ blacklist = []
+ updatelist = data[2:]
+
+ updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist]
+ blacklist = [dict(zip(schema, x.split('|'))) for x in blacklist]
+
+ if blacklist:
+ type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist]
+
+ # Protect UpdateManager from self-removing
+ try:
+ type_plugins.remove(("addon", "UpdateManager"))
+ except ValueError:
+ pass
+
+ for t, n in type_plugins:
+ for idx, plugin in enumerate(updatelist):
+ if n == plugin['name'] and t == plugin['type']:
+ updatelist.pop(idx)
+ break
+
+ for t, n in self.removePlugins(sorted(type_plugins)):
+ self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % {
+ 'type': t,
+ 'name': n,
+ })
+
+ for plugin in sorted(updatelist, key=itemgetter("type", "name")):
+ filename = plugin['name']
+ type = plugin['type']
+ version = plugin['version']
+
+ if filename.endswith(".pyc"):
+ name = filename[:filename.find("_")]
+ else:
+ name = filename.replace(".py", "")
+
+ plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
+
+ oldver = float(plugins[name]['version']) if name in plugins else None
+ newver = float(version)
+
+ if not oldver:
+ msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)"
+ elif newver > oldver:
+ msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)"
+ else:
+ continue
+
+ self.logInfo(_(msg) % {'type': type,
+ 'name': name,
+ 'oldver': oldver,
+ 'newver': newver})
+ try:
+ content = getURL(url % plugin)
+ m = VERSION.search(content)
+
+ if m and m.group(2) == version:
+ with open(fs_join("userplugins", type, filename), "wb") as f:
+ f.write(content)
+
+ updated.append((type, name))
+ else:
+ raise Exception, _("Version mismatch")
+
+ except Exception, e:
+ self.logError(_("Error updating plugin: %s") % filename, e)
+
+ if updated:
+ self.logInfo(_("*** Plugins updated ***"))
+
+ if self.core.pluginManager.reloadPlugins(updated):
+ exitcode = 1
+ else:
+ self.logWarning(_("pyLoad restart required to reload the updated plugins"))
+ self.info['plugins'] = True
+ exitcode = 2
+
+ self.manager.dispatchEvent("plugin_updated", updated)
+ else:
+ self.logInfo(_("No plugin updates available"))
+
+ # Exit codes:
+ # 0 = No plugin updated
+ # 1 = Plugins updated
+ # 2 = Plugins updated, but restart required
+ return exitcode
+
+
+ @Expose
+ def removePlugins(self, type_plugins):
+ """ delete plugins from disk """
+
+ if not type_plugins:
+ return
+
+ removed = set()
+
+ self.logDebug("Requested deletion of plugins: %s" % type_plugins)
+
+ for type, name in type_plugins:
+ rootplugins = os.path.join(pypath, "module", "plugins")
+
+ for dir in ("userplugins", rootplugins):
+ py_filename = fs_join(dir, type, name + ".py")
+ pyc_filename = py_filename + "c"
+
+ if type == "addon":
+ try:
+ self.manager.deactivateAddon(name)
+
+ except Exception, e:
+ self.logDebug(e)
+
+ for filename in (py_filename, pyc_filename):
+ if not exists(filename):
+ continue
+
+ try:
+ os.remove(filename)
+
+ except OSError, e:
+ self.logError(_("Error removing: %s") % filename, e)
+
+ else:
+ id = (type, name)
+ removed.add(id)
+
+ #: return a list of the plugins successfully removed
+ return list(removed)
diff --git a/pyload/plugin/addon/UserAgentSwitcher.py b/pyload/plugin/addon/UserAgentSwitcher.py
new file mode 100644
index 000000000..31a2b763b
--- /dev/null
+++ b/pyload/plugin/addon/UserAgentSwitcher.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import pycurl
+import random
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode
+
+
+class UserAgentSwitcher(Addon):
+ __name = "UserAgentSwitcher"
+ __type = "addon"
+ __version = "0.04"
+
+ __config = [("activated", "bool", "Activated" , True ),
+ ("uaf" , "file", "Random user-agents file" , "" ),
+ ("uar" , "bool", "Random user-agent" , False ),
+ ("uas" , "str" , "Custom user-agent string", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")]
+
+ __description = """Custom user-agent"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def downloadPreparing(self, pyfile):
+ uar = self.getConfig('uar')
+ uaf = fs_encode(self.getConfig('uaf'))
+
+ if uar and os.path.isfile(uaf):
+ with open(uaf) as f:
+ uas = random.choice([ua for ua in f.read().splitlines()])
+ else:
+ uas = self.getConfig('uas')
+
+ if uas:
+ self.logDebug("Use custom user-agent string: " + uas)
+ pyfile.plugin.req.http.c.setopt(pycurl.USERAGENT, uas.encode('utf-8'))
diff --git a/pyload/plugin/addon/WindowsPhoneNotify.py b/pyload/plugin/addon/WindowsPhoneNotify.py
new file mode 100644
index 000000000..b1d1c8b0f
--- /dev/null
+++ b/pyload/plugin/addon/WindowsPhoneNotify.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+import httplib
+import time
+
+from pyload.plugin.Addon import Addon, Expose
+
+
+class WindowsPhoneNotify(Addon):
+ __name = "WindowsPhoneNotify"
+ __type = "addon"
+ __version = "0.09"
+
+ __config = [("id" , "str" , "Push ID" , "" ),
+ ("url" , "str" , "Push url" , "" ),
+ ("notifycaptcha" , "bool", "Notify captcha request" , True ),
+ ("notifypackage" , "bool", "Notify package finished" , True ),
+ ("notifyprocessed", "bool", "Notify packages processed" , True ),
+ ("notifyupdate" , "bool", "Notify plugin updates" , True ),
+ ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
+ ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
+ ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
+ ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
+
+ __description = """Send push notifications to Windows Phone"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt" , "phone-support@hotmail.de"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ event_list = ["allDownloadsProcessed", "plugin_updated"]
+
+
+ def setup(self):
+ self.last_notify = 0
+ self.notifications = 0
+
+
+ def plugin_updated(self, type_plugins):
+ if not self.getConfig('notifyupdate'):
+ return
+
+ self.notify(_("Plugins updated"), str(type_plugins))
+
+
+ def exit(self):
+ if not self.getConfig('notifyexit'):
+ return
+
+ if self.core.do_restart:
+ self.notify(_("Restarting pyLoad"))
+ else:
+ self.notify(_("Exiting pyLoad"))
+
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig('notifycaptcha'):
+ return
+
+ self.notify(_("Captcha"), _("New request waiting user input"))
+
+
+ def packageFinished(self, pypack):
+ if self.getConfig('notifypackage'):
+ self.notify(_("Package finished"), pypack.name)
+
+
+ def allDownloadsProcessed(self):
+ if not self.getConfig('notifyprocessed'):
+ return
+
+ if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
+ self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
+ else:
+ self.notify(_("All packages finished"))
+
+
+ def getXmlData(self, msg):
+ return ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
+ "<wp:Toast> <wp:Text1>pyLoad</wp:Text1> <wp:Text2>%s</wp:Text2> "
+ "</wp:Toast> </wp:Notification>" % msg)
+
+
+ @Expose
+ def notify(self,
+ event,
+ msg="",
+ key=(self.getConfig('id'), self.getConfig('url'))):
+
+ id, url = key
+
+ if not id or not url:
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
+ return
+
+ elapsed_time = time.time() - self.last_notify
+
+ if elapsed_time < self.getConf("sendtimewait"):
+ return
+
+ if elapsed_time > 60:
+ self.notifications = 0
+
+ elif self.notifications >= self.getConf("sendpermin"):
+ return
+
+
+ request = self.getXmlData("%s: %s" % (event, msg) if msg else event)
+ webservice = httplib.HTTP(url)
+
+ webservice.putrequest("POST", id)
+ webservice.putheader("Host", url)
+ webservice.putheader("Content-type", "text/xml")
+ webservice.putheader("X-NotificationClass", "2")
+ webservice.putheader("X-WindowsPhone-Target", "toast")
+ webservice.putheader("Content-length", "%d" % len(request))
+ webservice.endheaders()
+ webservice.send(request)
+ webservice.close()
+
+ self.last_notify = time.time()
+ self.notifications += 1
diff --git a/pyload/plugin/addon/XMPPInterface.py b/pyload/plugin/addon/XMPPInterface.py
new file mode 100644
index 000000000..5ce5b5e8b
--- /dev/null
+++ b/pyload/plugin/addon/XMPPInterface.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+from pyxmpp import streamtls
+from pyxmpp.all import JID, Message
+from pyxmpp.interface import implements
+from pyxmpp.interfaces import *
+from pyxmpp.jabber.client import JabberClient
+
+from pyload.plugin.addon.IRCInterface import IRCInterface
+
+
+class XMPPInterface(IRCInterface, JabberClient):
+ __name = "XMPPInterface"
+ __type = "addon"
+ __version = "0.11"
+
+ __config = [("jid" , "str" , "Jabber ID" , "user@exmaple-jabber-server.org" ),
+ ("pw" , "str" , "Password" , "" ),
+ ("tls" , "bool", "Use TLS" , False ),
+ ("owners" , "str" , "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"),
+ ("info_file", "bool", "Inform about every file finished" , False ),
+ ("info_pack", "bool", "Inform about every package finished" , True ),
+ ("captcha" , "bool", "Send captcha requests" , True )]
+
+ __description = """Connect to jabber and let owner perform different tasks"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ implements(IMessageHandlersProvider)
+
+
+ def __init__(self, core, manager):
+ IRCInterface.__init__(self, core, manager)
+
+ self.jid = JID(self.getConfig('jid'))
+ password = self.getConfig('pw')
+
+ # if bare JID is provided add a resource -- it is required
+ if not self.jid.resource:
+ self.jid = JID(self.jid.node, self.jid.domain, "pyLoad")
+
+ if self.getConfig('tls'):
+ tls_settings = streamtls.TLSSettings(require=True, verify_peer=False)
+ auth = ("sasl:PLAIN", "sasl:DIGEST-MD5")
+ else:
+ tls_settings = None
+ auth = ("sasl:DIGEST-MD5", "digest")
+
+ # setup client with provided connection information
+ # and identity data
+ JabberClient.__init__(self, self.jid, password,
+ disco_name="pyLoad XMPP Client", disco_type="bot",
+ tls_settings=tls_settings, auth_methods=auth)
+
+ self.interface_providers = [
+ VersionHandler(self),
+ self,
+ ]
+
+
+ def activate(self):
+ self.new_package = {}
+
+ self.start()
+
+
+ def packageFinished(self, pypack):
+ try:
+ if self.getConfig('info_pack'):
+ self.announce(_("Package finished: %s") % pypack.name)
+ except Exception:
+ pass
+
+
+ def downloadFinished(self, pyfile):
+ try:
+ if self.getConfig('info_file'):
+ self.announce(
+ _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname})
+ except Exception:
+ pass
+
+
+ def run(self):
+ # connect to IRC etc.
+ self.connect()
+ try:
+ self.loop()
+ except Exception, ex:
+ self.logError(ex)
+
+
+ def stream_state_changed(self, state, arg):
+ """This one is called when the state of stream connecting the component
+ to a server changes. This will usually be used to let the user
+ know what is going on."""
+ self.logDebug("*** State changed: %s %r ***" % (state, arg))
+
+
+ def disconnected(self):
+ self.logDebug("Client was disconnected")
+
+
+ def stream_closed(self, stream):
+ self.logDebug("Stream was closed", stream)
+
+
+ def stream_error(self, err):
+ self.logDebug("Stream Error", err)
+
+
+ def get_message_handlers(self):
+ """Return list of (message_type, message_handler) tuples.
+
+ The handlers returned will be called when matching message is received
+ in a client session."""
+ return [("normal", self.message)]
+
+
+ def message(self, stanza):
+ """Message handler for the component."""
+ subject = stanza.get_subject()
+ body = stanza.get_body()
+ t = stanza.get_type()
+ self.logDebug("Message from %s received." % unicode(stanza.get_from()))
+ self.logDebug("Body: %s Subject: %s Type: %s" % (body, subject, t))
+
+ if t == "headline":
+ # 'headline' messages should never be replied to
+ return True
+ if subject:
+ subject = u"Re: " + subject
+
+ to_jid = stanza.get_from()
+ from_jid = stanza.get_to()
+
+ # j = JID()
+ to_name = to_jid.as_utf8()
+ from_name = from_jid.as_utf8()
+
+ names = self.getConfig('owners').split(";")
+
+ if to_name in names or to_jid.node + "@" + to_jid.domain in names:
+ messages = []
+
+ trigger = "pass"
+ args = None
+
+ try:
+ temp = body.split()
+ trigger = temp[0]
+ if len(temp) > 1:
+ args = temp[1:]
+ except Exception:
+ pass
+
+ handler = getattr(self, "event_%s" % trigger, self.event_pass)
+ try:
+ res = handler(args)
+ for line in res:
+ m = Message(
+ to_jid=to_jid,
+ from_jid=from_jid,
+ stanza_type=stanza.get_type(),
+ subject=subject,
+ body=line)
+
+ messages.append(m)
+ except Exception, e:
+ self.logError(e)
+
+ return messages
+
+ else:
+ return True
+
+
+ def response(self, msg, origin=""):
+ return self.announce(msg)
+
+
+ def announce(self, message):
+ """ send message to all owners"""
+ for user in self.getConfig('owners').split(";"):
+ self.logDebug("Send message to", user)
+
+ to_jid = JID(user)
+
+ m = Message(from_jid=self.jid,
+ to_jid=to_jid,
+ stanza_type="chat",
+ body=message)
+
+ stream = self.get_stream()
+ if not stream:
+ self.connect()
+ stream = self.get_stream()
+
+ stream.send(m)
+
+
+ def beforeReconnecting(self, ip):
+ self.disconnect()
+
+
+ def afterReconnecting(self, ip):
+ self.connect()
+
+
+class VersionHandler(object):
+ """Provides handler for a version query.
+ This class will answer version query and announce 'jabber:iq:version' namespace
+ in the client's disco#info results."""
+
+ implements(IIqHandlersProvider, IFeaturesProvider)
+
+
+ def __init__(self, client):
+ """Just remember who created this."""
+ self.client = client
+
+
+ def get_features(self):
+ """Return namespace which should the client include in its reply to a
+ disco#info query."""
+ return ["jabber:iq:version"]
+
+
+ def get_iq_get_handlers(self):
+ """Return list of tuples (element_name, namespace, handler) describing
+ handlers of <iq type='get'/> stanzas"""
+ return [("query", "jabber:iq:version", self.get_version)]
+
+
+ def get_iq_set_handlers(self):
+ """Return empty list, as this class provides no <iq type='set'/> stanza handler."""
+ return []
+
+
+ def get_version(self, iq):
+ """Handler for jabber:iq:version queries.
+
+ jabber:iq:version queries are not supported directly by PyXMPP, so the
+ XML node is accessed directly through the libxml2 API. This should be
+ used very carefully!"""
+ iq = iq.make_result_response()
+ q = iq.new_query("jabber:iq:version")
+ q.newTextChild(q.ns(), "name", "Echo component")
+ q.newTextChild(q.ns(), "version", "1.0")
+ return iq
diff --git a/pyload/plugin/addon/__init__.py b/pyload/plugin/addon/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/addon/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/captcha/AdYouLike.py b/pyload/plugin/captcha/AdYouLike.py
new file mode 100644
index 000000000..a29524bcc
--- /dev/null
+++ b/pyload/plugin/captcha/AdYouLike.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Captcha import Captcha
+from pyload.utils import json_loads
+
+
+class AdYouLike(Captcha):
+ __name = "AdYouLike"
+ __type = "captcha"
+ __version = "0.05"
+
+ __description = """AdYouLike captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ AYL_PATTERN = r'Adyoulike\.create\s*\((.+?)\)'
+ CALLBACK_PATTERN = r'(Adyoulike\.g\._jsonp_\d+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("AdYouLike html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.AYL_PATTERN, html)
+ n = re.search(self.CALLBACK_PATTERN, html)
+ if m and n:
+ self.key = (m.group(1).strip(), n.group(1).strip())
+ self.logDebug("Ayl|callback: %s | %s" % self.key)
+ return self.key #: key is the tuple(ayl, callback)
+ else:
+ self.logDebug("Ayl or callback not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("AdYouLike key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ ayl, callback = key
+
+ # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"},
+ # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}}
+ ayl = json_loads(ayl)
+
+ html = self.plugin.req.load("http://api-ayl.appspot.com/challenge",
+ get={'key': ayl['adyoulike']['key'],
+ 'env': ayl['all']['env'],
+ 'callback': callback})
+ try:
+ challenge = json_loads(re.search(callback + r'\s*\((.+?)\)', html).group(1))
+
+ except AttributeError:
+ errmsg = _("AdYouLike challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(ayl, challenge), challenge
+
+
+ def result(self, server, challenge):
+ # Adyoulike.g._jsonp_5579316662423138
+ # ({"translations":{"fr":{"instructions_visual":"Recopiez « Soonnight » ci-dessous :"}},
+ # "site_under":true,"clickable":true,"pixels":{"VIDEO_050":[],"DISPLAY":[],"VIDEO_000":[],"VIDEO_100":[],
+ # "VIDEO_025":[],"VIDEO_075":[]},"medium_type":"image/adyoulike",
+ # "iframes":{"big":"<iframe src=\"http://www.soonnight.com/campagn.html\" scrolling=\"no\"
+ # height=\"250\" width=\"300\" frameborder=\"0\"></iframe>"},"shares":{},"id":256,
+ # "token":"e6QuI4aRSnbIZJg02IsV6cp4JQ9~MjA1","formats":{"small":{"y":300,"x":0,"w":300,"h":60},
+ # "big":{"y":0,"x":0,"w":300,"h":250},"hover":{"y":440,"x":0,"w":300,"h":60}},
+ # "tid":"SqwuAdxT1EZoi4B5q0T63LN2AkiCJBg5"})
+
+ if isinstance(server, basestring):
+ server = json_loads(server)
+
+ if isinstance(challenge, basestring):
+ challenge = json_loads(challenge)
+
+ try:
+ instructions_visual = challenge['translations'][server['all']['lang']]['instructions_visual']
+ result = re.search(u'«(.+?)»', instructions_visual).group(1).strip()
+
+ except AttributeError:
+ errmsg = _("AdYouLike result not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ result = {'_ayl_captcha_engine': "adyoulike",
+ '_ayl_env': server['all']['env'],
+ '_ayl_tid': challenge['tid'],
+ '_ayl_token_challenge': challenge['token'],
+ '_ayl_response': response}
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/AdsCaptcha.py b/pyload/plugin/captcha/AdsCaptcha.py
new file mode 100644
index 000000000..a0ccdda58
--- /dev/null
+++ b/pyload/plugin/captcha/AdsCaptcha.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugin.Captcha import Captcha
+
+
+class AdsCaptcha(Captcha):
+ __name = "AdsCaptcha"
+ __type = "captcha"
+ __version = "0.08"
+
+ __description = """AdsCaptcha captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ CAPTCHAID_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?CaptchaId=(\d+)'
+ PUBLICKEY_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?PublicKey=([\w-]+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("AdsCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.PUBLICKEY_PATTERN, html)
+ n = re.search(self.CAPTCHAID_PATTERN, html)
+ if m and n:
+ self.key = (m.group(1).strip(), n.group(1).strip()) #: key is the tuple(PublicKey, CaptchaId)
+ self.logDebug("Key|id: %s | %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key or id not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("AdsCaptcha key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ PublicKey, CaptchaId = key
+
+ html = self.plugin.req.load("http://api.adscaptcha.com/Get.aspx",
+ get={'CaptchaId': CaptchaId,
+ 'PublicKey': PublicKey})
+ try:
+ challenge = re.search("challenge: '(.+?)',", html).group(1)
+ server = re.search("server: '(.+?)',", html).group(1)
+
+ except AttributeError:
+ errmsg = _("AdsCaptcha challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(server, challenge), challenge
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha("%sChallenge.aspx" % server,
+ get={'cid': challenge, 'dummy': random()},
+ cookies=True,
+ imgtype="jpg")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/ReCaptcha.py b/pyload/plugin/captcha/ReCaptcha.py
new file mode 100644
index 000000000..9c75c2212
--- /dev/null
+++ b/pyload/plugin/captcha/ReCaptcha.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from base64 import b64encode
+from random import randint
+from urlparse import urljoin, urlparse
+
+from pyload.plugin.Captcha import Captcha
+
+
+class ReCaptcha(Captcha):
+ __name = "ReCaptcha"
+ __type = "captcha"
+ __version = "0.15"
+
+ __description = """ReCaptcha captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("zapp-brannigan", "fuerst.reinje@web.de")]
+
+ KEY_V2_PATTERN = r'(?:data-sitekey=["\']|["\']sitekey["\']:\s*["\'])([\w-]+)'
+ KEY_V1_PATTERN = r'(?:recaptcha(?:/api|\.net)/(?:challenge|noscript)\?k=|Recaptcha\.create\s*\(\s*["\'])([\w-]+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("ReCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_V2_PATTERN, html) or re.search(self.KEY_V1_PATTERN, html)
+ if m:
+ self.key = m.group(1).strip()
+ self.logDebug("Key: %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key not found")
+ return None
+
+
+ def challenge(self, key=None, html=None, version=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("ReCaptcha key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ if version in (1, 2):
+ return getattr(self, "_challenge_v%s" % version)(key)
+
+ elif not html and hasattr(self.plugin, "html") and self.plugin.html:
+ version = 2 if re.search(self.KEY_V2_PATTERN, self.plugin.html) else 1
+ return self.challenge(key, self.plugin.html, version)
+
+ else:
+ errmsg = _("ReCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+
+ def _challenge_v1(self, key):
+ html = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge",
+ get={'k': key})
+ try:
+ challenge = re.search("challenge : '(.+?)',", html).group(1)
+ server = re.search("server : '(.+?)',", html).group(1)
+
+ except AttributeError:
+ errmsg = _("ReCaptcha challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(server, challenge), challenge
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha("%simage" % server,
+ get={'c': challenge},
+ cookies=True,
+ forceUser=True,
+ imgtype="jpg")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
+
+
+ def _collectApiInfo(self):
+ html = self.plugin.req.load("http://www.google.com/recaptcha/api.js")
+ a = re.search(r'po.src = \'(.*?)\';', html).group(1)
+ vers = a.split("/")[5]
+
+ self.logDebug("API version: %s" % vers)
+
+ language = a.split("__")[1].split(".")[0]
+
+ self.logDebug("API language: %s" % language)
+
+ html = self.plugin.req.load("https://apis.google.com/js/api.js")
+ b = re.search(r'"h":"(.*?)","', html).group(1)
+ jsh = b.decode('unicode-escape')
+
+ self.logDebug("API jsh-string: %s" % jsh)
+
+ return vers, language, jsh
+
+
+ def _prepareTimeAndRpc(self):
+ self.plugin.req.load("http://www.google.com/recaptcha/api2/demo")
+
+ millis = int(round(time.time() * 1000))
+
+ self.logDebug("Time: %s" % millis)
+
+ rand = randint(1, 99999999)
+ a = "0.%s" % str(rand * 2147483647)
+ rpc = int(100000000 * float(a))
+
+ self.logDebug("Rpc-token: %s" % rpc)
+
+ return millis, rpc
+
+
+ def _challenge_v2(self, key, parent=None):
+ if parent is None:
+ try:
+ parent = urljoin("http://", urlparse(self.plugin.pyfile.url).netloc)
+
+ except Exception:
+ parent = ""
+
+ botguardstring = "!A"
+ vers, language, jsh = self._collectApiInfo()
+ millis, rpc = self._prepareTimeAndRpc()
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/anchor",
+ get={'k' : key,
+ 'hl' : language,
+ 'v' : vers,
+ 'usegapi' : "1",
+ 'jsh' : "%s#id=IO_%s" % (jsh, millis),
+ 'parent' : parent,
+ 'pfname' : "",
+ 'rpctoken': rpc})
+
+ token1 = re.search(r'id="recaptcha-token" value="(.*?)">', html)
+ self.logDebug("Token #1: %s" % token1.group(1))
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/frame",
+ get={'c' : token1.group(1),
+ 'hl' : language,
+ 'v' : vers,
+ 'bg' : botguardstring,
+ 'k' : key,
+ 'usegapi': "1",
+ 'jsh' : jsh}).decode('unicode-escape')
+
+ token2 = re.search(r'"finput","(.*?)",', html)
+ self.logDebug("Token #2: %s" % token2.group(1))
+
+ token3 = re.search(r'"rresp","(.*?)",', html)
+ self.logDebug("Token #3: %s" % token3.group(1))
+
+ millis_captcha_loading = int(round(time.time() * 1000))
+ captcha_response = self.plugin.decryptCaptcha("https://www.google.com/recaptcha/api2/payload",
+ get={'c':token3.group(1), 'k':key},
+ cookies=True,
+ forceUser=True)
+ response = b64encode('{"response":"%s"}' % captcha_response)
+
+ self.logDebug("Result: %s" % response)
+
+ timeToSolve = int(round(time.time() * 1000)) - millis_captcha_loading
+ timeToSolveMore = timeToSolve + int(float("0." + str(randint(1, 99999999))) * 500)
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/userverify",
+ post={'k' : key,
+ 'c' : token3.group(1),
+ 'response': response,
+ 't' : timeToSolve,
+ 'ct' : timeToSolveMore,
+ 'bg' : botguardstring})
+
+ token4 = re.search(r'"uvresp","(.*?)",', html)
+ self.logDebug("Token #4: %s" % token4.group(1))
+
+ result = token4.group(1)
+
+ return result, None
diff --git a/pyload/plugin/captcha/SolveMedia.py b/pyload/plugin/captcha/SolveMedia.py
new file mode 100644
index 000000000..40a198b80
--- /dev/null
+++ b/pyload/plugin/captcha/SolveMedia.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Captcha import Captcha
+
+
+class SolveMedia(Captcha):
+ __name = "SolveMedia"
+ __type = "captcha"
+ __version = "0.12"
+
+ __description = """SolveMedia captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ KEY_PATTERN = r'api\.solvemedia\.com/papi/challenge\.(?:no)?script\?k=(.+?)["\']'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("SolveMedia html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_PATTERN, html)
+ if m:
+ self.key = m.group(1).strip()
+ self.logDebug("Key: %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("SolveMedia key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript",
+ get={'k': key})
+ try:
+ challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="(.+?)">',
+ html).group(1)
+ server = "http://api.solvemedia.com/papi/media"
+
+ except AttributeError:
+ errmsg = _("SolveMedia challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ result = self.result(server, challenge)
+
+ try:
+ magic = re.search(r'name="magic" value="(.+?)"', html).group(1)
+
+ except AttributeError:
+ self.logDebug("Magic code not found")
+
+ else:
+ if not self._verify(key, magic, result, challenge):
+ self.logDebug("Captcha code was invalid")
+
+ return result, challenge
+
+
+ def _verify(self, key, magic, result, challenge, ref=None): #@TODO: Clean up
+ if ref is None:
+ try:
+ ref = self.plugin.pyfile.url
+
+ except Exception:
+ ref = ""
+
+ html = self.plugin.req.load("http://api.solvemedia.com/papi/verify.noscript",
+ post={'adcopy_response' : result,
+ 'k' : key,
+ 'l' : "en",
+ 't' : "img",
+ 's' : "standard",
+ 'magic' : magic,
+ 'adcopy_challenge' : challenge,
+ 'ref' : ref})
+ try:
+ html = self.plugin.req.load(re.search(r'URL=(.+?)">', html).group(1))
+ gibberish = re.search(r'id=gibberish>(.+?)</textarea>', html).group(1)
+
+ except Exception:
+ return False
+
+ else:
+ return True
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha(server,
+ get={'c': challenge},
+ cookies=True,
+ imgtype="gif")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/__init__.py b/pyload/plugin/captcha/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/captcha/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/container/CCF.py b/pyload/plugin/container/CCF.py
new file mode 100644
index 000000000..024987174
--- /dev/null
+++ b/pyload/plugin/container/CCF.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import MultipartPostHandler
+import re
+import urllib2
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode, fs_join
+
+
+class CCF(Container):
+ __name = "CCF"
+ __type = "container"
+ __version = "0.23"
+
+ __pattern = r'.+\.ccf$'
+
+ __description = """CCF container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Willnix", "Willnix@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def decrypt(self, pyfile):
+ fs_filename = fs_encode(pyfile.url.strip())
+ opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
+
+ dlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php',
+ {'src' : "ccf",
+ 'filename': "test.ccf",
+ 'upload' : open(fs_filename, "rb")}).read()
+
+ download_folder = self.config.get("general", "download_folder")
+ dlc_file = fs_join(download_folder, "tmp_%s.dlc" % pyfile.name)
+
+ try:
+ dlc = re.search(r'<dlc>(.+)</dlc>', dlc_content, re.S).group(1).decode('base64')
+
+ except AttributeError:
+ self.fail(_("Container is corrupted"))
+
+ with open(dlc_file, "w") as tempdlc:
+ tempdlc.write(dlc)
+
+ self.urls = [dlc_file]
diff --git a/pyload/plugin/container/DLC.py b/pyload/plugin/container/DLC.py
new file mode 100644
index 000000000..b2bfea30e
--- /dev/null
+++ b/pyload/plugin/container/DLC.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+import xml.dom.minidom
+
+from Crypto.Cipher import AES
+
+from pyload.plugin.Container import Container
+from pyload.utils import decode, fs_encode
+
+
+class DLC(Container):
+ __name = "DLC"
+ __type = "container"
+ __version = "0.24"
+
+ __pattern = r'.+\.dlc$'
+
+ __description = """DLC container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Schnusch", "Schnusch@users.noreply.github.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ KEY = "cb99b5cbc24db398"
+ IV = "9bc24cb995cb8db3"
+ API_URL = "http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType=pylo&data=%s"
+
+
+ def decrypt(self, pyfile):
+ fs_filename = fs_encode(pyfile.url.strip())
+ with open(fs_filename) as dlc:
+ data = dlc.read().strip()
+
+ data += '=' * (-len(data) % 4)
+
+ dlc_key = data[-88:]
+ dlc_data = data[:-88].decode('base64')
+ dlc_content = self.load(self.API_URL % dlc_key)
+
+ try:
+ rc = re.search(r'<rc>(.+)</rc>', dlc_content, re.S).group(1).decode('base64')
+
+ except AttributeError:
+ self.fail(_("Container is corrupted"))
+
+ key = iv = AES.new(self.KEY, AES.MODE_CBC, self.IV).decrypt(rc)
+
+ self.data = AES.new(key, AES.MODE_CBC, iv).decrypt(dlc_data).decode('base64')
+ self.packages = [(name or pyfile.name, links, name or pyfile.name) \
+ for name, links in self.getPackages()]
+
+
+ def getPackages(self):
+ root = xml.dom.minidom.parseString(self.data).documentElement
+ content = root.getElementsByTagName("content")[0]
+ return self.parsePackages(content)
+
+
+ def parsePackages(self, startNode):
+ return [(decode(node.getAttribute("name")).decode('base64'), self.parseLinks(node)) \
+ for node in startNode.getElementsByTagName("package")]
+
+
+ def parseLinks(self, startNode):
+ return [node.getElementsByTagName("url")[0].firstChild.data.decode('base64') \
+ for node in startNode.getElementsByTagName("file")]
diff --git a/pyload/plugin/container/RSDF.py b/pyload/plugin/container/RSDF.py
new file mode 100644
index 000000000..6f56ec06a
--- /dev/null
+++ b/pyload/plugin/container/RSDF.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode
+
+
+class RSDF(Container):
+ __name = "RSDF"
+ __type = "container"
+ __version = "0.29"
+
+ __pattern = r'.+\.rsdf$'
+
+ __description = """RSDF container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ KEY = "8C35192D964DC3182C6F84F3252239EB4A320D2500000000"
+ IV = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+
+
+ def decrypt(self, pyfile):
+ KEY = binascii.unhexlify(self.KEY)
+ IV = binascii.unhexlify(self.IV)
+
+ iv = AES.new(KEY, AES.MODE_ECB).encrypt(IV)
+ cipher = AES.new(KEY, AES.MODE_CFB, iv)
+
+ try:
+ fs_filename = fs_encode(pyfile.url.strip())
+ with open(fs_filename, 'r') as rsdf:
+ data = rsdf.read()
+
+ except IOError, e:
+ self.fail(e)
+
+ if re.search(r"<title>404 - Not Found</title>", data):
+ pyfile.setStatus("offline")
+
+ else:
+ try:
+ raw_links = binascii.unhexlify(''.join(data.split())).splitlines()
+
+ except TypeError:
+ self.fail(_("Container is corrupted"))
+
+ for link in raw_links:
+ if not link:
+ continue
+ link = cipher.decrypt(link.decode('base64')).replace('CCF: ', '')
+ self.urls.append(link)
diff --git a/pyload/plugin/container/TXT.py b/pyload/plugin/container/TXT.py
new file mode 100644
index 000000000..4ef29c0b7
--- /dev/null
+++ b/pyload/plugin/container/TXT.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode
+
+
+class TXT(Container):
+ __name = "TXT"
+ __type = "container"
+ __version = "0.15"
+
+ __pattern = r'.+\.(txt|text)$'
+ __config = [("flush" , "bool" , "Flush list after adding", False ),
+ ("encoding", "string", "File encoding" , "utf-8")]
+
+ __description = """Read link lists in plain text formats"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("jeix", "jeix@hasnomail.com")]
+
+
+ def decrypt(self, pyfile):
+ try:
+ encoding = codecs.lookup(self.getConfig('encoding')).name
+
+ except Exception:
+ encoding = "utf-8"
+
+ fs_filename = fs_encode(pyfile.url.strip())
+ txt = codecs.open(fs_filename, 'r', encoding)
+ curPack = "Parsed links from %s" % pyfile.name
+ packages = {curPack:[],}
+
+ for link in txt.readlines():
+ link = link.strip()
+
+ if not link:
+ continue
+
+ if link.startswith(";"):
+ continue
+
+ if link.startswith("[") and link.endswith("]"):
+ # new package
+ curPack = link[1:-1]
+ packages[curPack] = []
+ continue
+
+ packages[curPack].append(link)
+
+ txt.close()
+
+ # empty packages fix
+ for key, value in packages.iteritems():
+ if not value:
+ packages.pop(key, None)
+
+ if self.getConfig('flush'):
+ try:
+ txt = open(fs_filename, 'wb')
+ txt.close()
+
+ except IOError:
+ self.logWarning(_("Failed to flush list"))
+
+ for name, links in packages.iteritems():
+ self.packages.append((name, links, name))
diff --git a/pyload/plugin/container/__init__.py b/pyload/plugin/container/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/container/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/crypter/BitshareCom.py b/pyload/plugin/crypter/BitshareCom.py
new file mode 100644
index 000000000..2a4d17da1
--- /dev/null
+++ b/pyload/plugin/crypter/BitshareCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class BitshareCom(SimpleCrypter):
+ __name = "BitshareCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Bitshare.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href="(http://bitshare\.com/files/.+)">.+</a></td>'
+ NAME_PATTERN = r'View public folder "(?P<N>.+)"</h1>'
diff --git a/pyload/plugin/crypter/C1NeonCom.py b/pyload/plugin/crypter/C1NeonCom.py
new file mode 100644
index 000000000..e0e1bda17
--- /dev/null
+++ b/pyload/plugin/crypter/C1NeonCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class C1NeonCom(DeadCrypter):
+ __name = "C1NeonCom"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?c1neon\.com/.+'
+ __config = []
+
+ __description = """C1neon.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/crypter/ChipDe.py b/pyload/plugin/crypter/ChipDe.py
new file mode 100644
index 000000000..164531ee5
--- /dev/null
+++ b/pyload/plugin/crypter/ChipDe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class ChipDe(Crypter):
+ __name = "ChipDe"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?chip\.de/video/.+\.html'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Chip.de decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("4Christopher", "4Christopher@gmx.de")]
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ try:
+ f = re.search(r'"(http://video\.chip\.de/.+)"', self.html)
+ except Exception:
+ self.fail(_("Failed to find the URL"))
+ else:
+ self.urls = [f.group(1)]
+ self.logDebug("The file URL is %s" % self.urls[0])
diff --git a/pyload/plugin/crypter/CloudzillaTo.py b/pyload/plugin/crypter/CloudzillaTo.py
new file mode 100644
index 000000000..7042f7750
--- /dev/null
+++ b/pyload/plugin/crypter/CloudzillaTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class CloudzillaTo(SimpleHoster):
+ __name = "CloudzillaTo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?cloudzilla\.to/share/folder/(?P<ID>[\w^_]+)'
+
+ __description = """Cloudzilla.to folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'<span class="name" title="(?P<N>.+?)"'
+ OFFLINE_PATTERN = r'>File not found...<'
+
+ LINK_PATTERN = r'<a href="(.+?)" class="item_href">'
+
+ PASSWORD_PATTERN = r'<div id="pwd_protected">'
+
+
+ def checkErrors(self):
+ m = re.search(self.PASSWORD_PATTERN, self.html)
+ if m:
+ self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
+
+ if re.search(self.PASSWORD_PATTERN, self.html):
+ self.retry(reason="Wrong password")
diff --git a/pyload/plugin/crypter/CrockoCom.py b/pyload/plugin/crypter/CrockoCom.py
new file mode 100644
index 000000000..71dadd8d5
--- /dev/null
+++ b/pyload/plugin/crypter/CrockoCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class CrockoCom(SimpleCrypter):
+ __name = "CrockoCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?crocko\.com/f/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Crocko.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<td class="last"><a href="(.+?)">download</a>'
diff --git a/pyload/plugin/crypter/CryptItCom.py b/pyload/plugin/crypter/CryptItCom.py
new file mode 100644
index 000000000..615626f4f
--- /dev/null
+++ b/pyload/plugin/crypter/CryptItCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class CryptItCom(DeadCrypter):
+ __name = "CryptItCom"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/\w+'
+ __config = []
+
+ __description = """Crypt-it.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de")]
diff --git a/pyload/plugin/crypter/CzshareCom.py b/pyload/plugin/crypter/CzshareCom.py
new file mode 100644
index 000000000..0c106bddc
--- /dev/null
+++ b/pyload/plugin/crypter/CzshareCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class CzshareCom(Crypter):
+ __name = "CzshareCom"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_PATTERN = r'<tr class="subdirectory">\s*<td>\s*<table>(.*?)</table>'
+ LINK_PATTERN = r'<td class="col2"><a href="(.+?)">info</a></td>'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ m = re.search(self.FOLDER_PATTERN, html, re.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/pyload/plugin/crypter/DailymotionComFolder.py b/pyload/plugin/crypter/DailymotionComFolder.py
new file mode 100644
index 000000000..06834f9a5
--- /dev/null
+++ b/pyload/plugin/crypter/DailymotionComFolder.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class DailymotionComFolder(Crypter):
+ __name = "DailymotionComFolder"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Dailymotion.com channel & playlist decrypter"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, ref, req=None):
+ url = urlparse.urljoin("https://api.dailymotion.com/", ref)
+ html = self.load(url, get=req)
+ return json_loads(html)
+
+
+ def getPlaylistInfo(self, id):
+ ref = "playlist/" + id
+ req = {"fields": "name,owner.screenname"}
+ playlist = self.api_response(ref, req)
+
+ if "error" in playlist:
+ return
+
+ name = playlist['name']
+ owner = playlist['owner.screenname']
+ return name, owner
+
+
+ def _getPlaylists(self, user_id, page=1):
+ ref = "user/%s/playlists" % user_id
+ req = {"fields": "id", "page": page, "limit": 100}
+ user = self.api_response(ref, req)
+
+ if "error" in user:
+ return
+
+ for playlist in user['list']:
+ yield playlist['id']
+
+ if user['has_more']:
+ for item in self._getPlaylists(user_id, page + 1):
+ yield item
+
+
+ def getPlaylists(self, user_id):
+ return [(id,) + self.getPlaylistInfo(id) for id in self._getPlaylists(user_id)]
+
+
+ def _getVideos(self, id, page=1):
+ ref = "playlist/%s/videos" % id
+ req = {"fields": "url", "page": page, "limit": 100}
+ playlist = self.api_response(ref, req)
+
+ if "error" in playlist:
+ return
+
+ for video in playlist['list']:
+ yield video['url']
+
+ if playlist['has_more']:
+ for item in self._getVideos(id, page + 1):
+ yield item
+
+
+ def getVideos(self, playlist_id):
+ return list(self._getVideos(playlist_id))[::-1]
+
+
+ def decrypt(self, pyfile):
+ m = re.match(self.__pattern, pyfile.url)
+ m_id = m.group('ID')
+ m_type = m.group('TYPE')
+
+ if m_type == "playlist":
+ self.logDebug("Url recognized as Playlist")
+ p_info = self.getPlaylistInfo(m_id)
+ playlists = [(m_id,) + p_info] if p_info else None
+ else:
+ self.logDebug("Url recognized as Channel")
+ playlists = self.getPlaylists(m_id)
+ self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), m_id))
+
+ if not playlists:
+ self.fail(_("No playlist available"))
+
+ for p_id, p_name, p_owner in playlists:
+ p_videos = self.getVideos(p_id)
+ p_folder = fs_join(self.config.get("general", "download_folder"), p_owner, p_name)
+ self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
+ self.packages.append((p_name, p_videos, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
diff --git a/pyload/plugin/crypter/DataHu.py b/pyload/plugin/crypter/DataHu.py
new file mode 100644
index 000000000..ce480dacb
--- /dev/null
+++ b/pyload/plugin/crypter/DataHu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DataHu(SimpleCrypter):
+ __name = "DataHu"
+ __type = "crypter"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?data\.hu/dir/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Data.hu folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("crash", ""),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href=\'(http://data\.hu/get/.+)\' target=\'_blank\'>\1</a>'
+ NAME_PATTERN = ur'<title>(?P<N>.+) Let\xf6lt\xe9se</title>'
+
+
+ def prepare(self):
+ super(DataHu, self).prepare()
+
+ if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: #: Password protected
+ password = self.getPassword()
+ if not password:
+ self.fail(_("Password required"))
+
+ self.logDebug("The folder is password protected', 'Using password: " + password)
+
+ self.html = self.load(self.pyfile.url, post={'mappa_pass': password}, decode=True)
+
+ if u'Hib\xe1s jelsz\xf3' in self.html: #: Wrong password
+ self.fail(_("Wrong password"))
diff --git a/pyload/plugin/crypter/DdlstorageCom.py b/pyload/plugin/crypter/DdlstorageCom.py
new file mode 100644
index 000000000..e3e1f1956
--- /dev/null
+++ b/pyload/plugin/crypter/DdlstorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class DdlstorageCom(DeadCrypter):
+ __name = "DdlstorageCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+'
+ __config = []
+
+ __description = """DDLStorage.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/DepositfilesCom.py b/pyload/plugin/crypter/DepositfilesCom.py
new file mode 100644
index 000000000..d86f67426
--- /dev/null
+++ b/pyload/plugin/crypter/DepositfilesCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DepositfilesCom(SimpleCrypter):
+ __name = "DepositfilesCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?depositfiles\.com/folders/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Depositfiles.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<div class="progressName".*?>\s*<a href="(.+?)" title=".+?" target="_blank">'
diff --git a/pyload/plugin/crypter/Dereferer.py b/pyload/plugin/crypter/Dereferer.py
new file mode 100644
index 000000000..b4b52f278
--- /dev/null
+++ b/pyload/plugin/crypter/Dereferer.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class Dereferer(SimpleDereferer):
+ __name = "Dereferer"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'https?://([^/]+)/.*?(?P<LINK>(ht|f)tps?(://|%3A%2F%2F).+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Crypter for dereferers"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/DevhostSt.py b/pyload/plugin/crypter/DevhostSt.py
new file mode 100644
index 000000000..7ede22106
--- /dev/null
+++ b/pyload/plugin/crypter/DevhostSt.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/users/shine/?fld_id=37263#files
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DevhostSt(SimpleCrypter):
+ __name = "DevhostSt"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """d-h.st folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'(?:/> |;">)<a href="(.+?)"(?!>Back to \w+<)'
+ OFFLINE_PATTERN = r'"/cHP">test\.png<'
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("File info (BEFORE): %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("File info (AFTER): %s" % self.info)
+
+ try:
+ if self.info['pattern']['ID'] == "0":
+ raise
+
+ p = r'href="(.+?)">Back to \w+<'
+ m = re.search(p, self.html)
+ html = self.load(urlparse.urljoin("http://d-h.st", m.group(1)),
+ cookies=False)
+
+ p = '\?fld_id=%s.*?">(.+?)<' % self.info['pattern']['ID']
+ m = re.search(p, html)
+ self.pyfile.name = m.group(1)
+
+ except Exception, e:
+ self.logDebug(e)
+ self.pyfile.name = self.info['pattern']['USER']
+
+ try:
+ folder = self.info['folder'] = self.pyfile.name
+
+ except Exception:
+ pass
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File folder: %s" % self.pyfile.name)
diff --git a/pyload/plugin/crypter/DlProtectCom.py b/pyload/plugin/crypter/DlProtectCom.py
new file mode 100644
index 000000000..eae6d1d83
--- /dev/null
+++ b/pyload/plugin/crypter/DlProtectCom.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from base64 import urlsafe_b64encode
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DlProtectCom(SimpleCrypter):
+ __name = "DlProtectCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?dl-protect\.com/((en|fr)/)?\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Dl-protect.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ COOKIES = [("dl-protect.com", "l", "en")]
+
+ OFFLINE_PATTERN = r'Unfortunately, the link you are looking for is not found'
+
+
+ def getLinks(self):
+ # Direct link with redirect
+ if not re.match(r"https?://(?:www\.)?dl-protect\.com/.+", self.req.http.lastEffectiveURL):
+ return [self.req.http.lastEffectiveURL]
+
+ post_req = {'key' : re.search(r'name="key" value="(.+?)"', self.html).group(1),
+ 'submitform': ""}
+
+ if "Please click on continue to see the content" in self.html:
+ post_req['submitform'] = "Continue"
+ self.wait(2)
+
+ else:
+ mstime = int(round(time.time() * 1000))
+ b64time = "_" + urlsafe_b64encode(str(mstime)).replace("=", "%3D")
+
+ post_req.update({'i' : b64time,
+ 'submitform': "Decrypt+link"})
+
+ if "Password :" in self.html:
+ post_req['pwd'] = self.getPassword()
+
+ if "Security Code" in self.html:
+ captcha_id = re.search(r'/captcha\.php\?uid=(.+?)"', self.html).group(1)
+ captcha_url = "http://www.dl-protect.com/captcha.php?uid=" + captcha_id
+ captcha_code = self.decryptCaptcha(captcha_url, imgtype="gif")
+
+ post_req['secure'] = captcha_code
+
+ self.html = self.load(self.pyfile.url, post=post_req)
+
+ for errmsg in ("The password is incorrect", "The security code is incorrect"):
+ if errmsg in self.html:
+ self.fail(_(errmsg[1:]))
+
+ return re.findall(r'<a href="([^/].+?)" target="_blank">', self.html)
diff --git a/pyload/plugin/crypter/DontKnowMe.py b/pyload/plugin/crypter/DontKnowMe.py
new file mode 100644
index 000000000..e4edd8129
--- /dev/null
+++ b/pyload/plugin/crypter/DontKnowMe.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class DontKnowMe(SimpleDereferer):
+ __name = "DontKnowMe"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?dontknow\.me/at/\?(?P<LINK>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """DontKnow.me decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("selaux", "")]
diff --git a/pyload/plugin/crypter/DuckCryptInfo.py b/pyload/plugin/crypter/DuckCryptInfo.py
new file mode 100644
index 000000000..3463d44f9
--- /dev/null
+++ b/pyload/plugin/crypter/DuckCryptInfo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugin.Crypter import Crypter
+
+
+class DuckCryptInfo(Crypter):
+ __name = "DuckCryptInfo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?duckcrypt\.info/(folder|wait|link)/(\w+)/?(\w*)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """DuckCrypt.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
+
+
+ TIMER_PATTERN = r'<span id="timer">(.*)</span>'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ m = re.match(self.__pattern, url)
+ if m is None:
+ self.fail(_("Weird error in link"))
+ if str(m.group(1)) == "link":
+ self.handleLink(url)
+ else:
+ self.handleFolder(m)
+
+
+ def handleFolder(self, m):
+ html = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
+ m = re.match(self.__pattern, html)
+ self.logDebug("Redirectet to " + str(m.group(0)))
+ html = self.load(str(m.group(0)))
+ soup = BeautifulSoup(html)
+ cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
+ self.logDebug("Redirectet to " + str(cryptlinks))
+ if not cryptlinks:
+ self.error(_("No link found"))
+ for clink in cryptlinks:
+ if clink.find("a"):
+ self.handleLink(clink.find("a")['href'])
+
+
+ def handleLink(self, url):
+ html = self.load(url)
+ soup = BeautifulSoup(html)
+ self.urls = [soup.find("iframe")['src']]
+ if not self.urls:
+ self.logInfo(_("No link found"))
diff --git a/pyload/plugin/crypter/DuploadOrg.py b/pyload/plugin/crypter/DuploadOrg.py
new file mode 100644
index 000000000..445318ccc
--- /dev/null
+++ b/pyload/plugin/crypter/DuploadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class DuploadOrg(DeadCrypter):
+ __name = "DuploadOrg"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?dupload\.org/folder/\d+'
+ __config = []
+
+ __description = """Dupload.org folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/EasybytezCom.py b/pyload/plugin/crypter/EasybytezCom.py
new file mode 100644
index 000000000..06a958729
--- /dev/null
+++ b/pyload/plugin/crypter/EasybytezCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class EasybytezCom(XFSCrypter):
+ __name = "EasybytezCom"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?easybytez\.com/users/\d+/\d+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Easybytez.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LOGIN_ACCOUNT = True
diff --git a/pyload/plugin/crypter/EmbeduploadCom.py b/pyload/plugin/crypter/EmbeduploadCom.py
new file mode 100644
index 000000000..942627ad9
--- /dev/null
+++ b/pyload/plugin/crypter/EmbeduploadCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+from pyload.network.HTTPRequest import BadHeader
+
+
+class EmbeduploadCom(Crypter):
+ __name = "EmbeduploadCom"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?embedupload\.com/\?d=.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True ),
+ ("preferedHoster" , "str" , "Prefered hoster list (bar-separated)", "embedupload"),
+ ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
+
+ __description = """EmbedUpload.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<div id="(.+?)".*?>\s*<a href="(.+?)" target="_blank" (?:class="DownloadNow"|style="color:red")>'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ tmp_links = []
+
+ m = re.findall(self.LINK_PATTERN, self.html)
+ if m:
+ prefered_set = set(self.getConfig('preferedHoster').split('|'))
+ prefered_set = map(lambda s: s.lower().split('.')[0], prefered_set)
+
+ self.logDebug("PF: %s" % prefered_set)
+
+ tmp_links.extend(x[1] for x in m if x[0] in prefered_set)
+ self.urls = self.getLocation(tmp_links)
+
+ if not self.urls:
+ ignored_set = set(self.getConfig('ignoredHoster').split('|'))
+ ignored_set = map(lambda s: s.lower().split('.')[0], ignored_set)
+
+ self.logDebug("IG: %s" % ignored_set)
+
+ tmp_links.extend(x[1] for x in m if x[0] not in ignored_set)
+ self.urls = self.getLocation(tmp_links)
+
+
+ def getLocation(self, tmp_links):
+ new_links = []
+ for link in tmp_links:
+ try:
+ header = self.load(link, just_header=True)
+ if 'location' in header:
+ new_links.append(header['location'])
+ except BadHeader:
+ pass
+ return new_links
diff --git a/pyload/plugin/crypter/FilebeerInfo.py b/pyload/plugin/crypter/FilebeerInfo.py
new file mode 100644
index 000000000..5ec059375
--- /dev/null
+++ b/pyload/plugin/crypter/FilebeerInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FilebeerInfo(DeadCrypter):
+ __name = "FilebeerInfo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?filebeer\.info/\d*~f\w+'
+ __config = []
+
+ __description = """Filebeer.info folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/FilecloudIo.py b/pyload/plugin/crypter/FilecloudIo.py
new file mode 100644
index 000000000..91e7dfc3a
--- /dev/null
+++ b/pyload/plugin/crypter/FilecloudIo.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilecloudIo(SimpleCrypter):
+ __name = "FilecloudIo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filecloud.io folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'href="(http://filecloud\.io/\w+)" title'
+ NAME_PATTERN = r'>(?P<N>.+?) - filecloud\.io<'
diff --git a/pyload/plugin/crypter/FilecryptCc.py b/pyload/plugin/crypter/FilecryptCc.py
new file mode 100644
index 000000000..db939357a
--- /dev/null
+++ b/pyload/plugin/crypter/FilecryptCc.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filecrypt.cc/Container/64E039F859.html
+
+import binascii
+import re
+import urlparse
+
+from Crypto.Cipher import AES
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class FilecryptCc(Crypter):
+ __name = "FilecryptCc"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?filecrypt\.cc/Container/\w+'
+
+ __description = """Filecrypt.cc decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ # URL_REPLACEMENTS = [(r'.html$', ""), (r'$', ".html")] #@TODO: Extend SimpleCrypter
+
+ DLC_LINK_PATTERN = r'<button class="dlcdownload" type="button" title="Download \*.dlc" onclick="DownloadDLC\(\'(.+)\'\);"><i></i><span>dlc<'
+ WEBLINK_PATTERN = r"openLink.?'([\w_-]*)',"
+
+ CAPTCHA_PATTERN = r'<img id="nc" src="(.+?)"'
+ CIRCLE_CAPTCHA_PATTERN = r'<input type="image" src="(.+?)"'
+
+ MIRROR_PAGE_PATTERN = r'"[\w]*" href="(https?://(?:www\.)?filecrypt.cc/Container/\w+\.html\?mirror=\d+)">'
+
+
+ def setup(self):
+ self.links = []
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ self.base_url = self.pyfile.url.split("Container")[0]
+
+ if "content notfound" in self.html: #@NOTE: "content notfound" is NOT a typo
+ self.offline()
+
+ self.handlePasswordProtection()
+ self.handleCaptcha()
+ self.handleMirrorPages()
+
+ for handle in (self.handleCNL, self.handleWeblinks, self.handleDlcContainer):
+ handle()
+ if self.links:
+ self.packages = [(pyfile.package().name, self.links, pyfile.package().name)]
+ return
+
+
+ def handleMirrorPages(self):
+ if "mirror=" not in self.siteWithLinks:
+ return
+
+ mirror = re.findall(self.MIRROR_PAGE_PATTERN, self.siteWithLinks)
+
+ self.logInfo(_("Found %d mirrors") % len(mirror))
+
+ for i in mirror[1:]:
+ self.siteWithLinks = self.siteWithLinks + self.load(i).decode("utf-8", "replace")
+
+
+ def handlePasswordProtection(self):
+ if '<input type="text" name="password"' not in self.html:
+ return
+
+ self.logInfo(_("Folder is password protected"))
+
+ password = self.getPassword()
+
+ if not password:
+ self.fail(_("Please enter the password in package section and try again"))
+
+ self.html = self.load(self.pyfile.url, post={"password": password})
+
+
+ def handleCaptcha(self):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ m2 = re.search(self.CIRCLE_CAPTCHA_PATTERN, self.html)
+
+ if m: #: normal captcha
+ self.logDebug("Captcha-URL: %s" % m.group(1))
+
+ captcha_code = self.decryptCaptcha(urlparse.urljoin(self.base_url, m.group(1)),
+ forceUser=True,
+ imgtype="gif")
+
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'recaptcha_response_field': captcha_code},
+ decode=True)
+ elif m2: #: circle captcha
+ self.logDebug("Captcha-URL: %s" % m2.group(1))
+
+ captcha_code = self.decryptCaptcha('%s%s?c=abc' %(self.base_url, m2.group(1)),
+ result_type='positional')
+
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'button.x': captcha_code[0], 'button.y': captcha_code[1]},
+ decode=True)
+
+ else:
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+
+ if captcha_key:
+ response, challenge = recaptcha.challenge(captcha_key)
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'g-recaptcha-response': response},
+ decode=True)
+ else:
+ self.logInfo(_("No captcha found"))
+ self.siteWithLinks = self.html
+
+ if "recaptcha_image" in self.siteWithLinks or "data-sitekey" in self.siteWithLinks:
+ self.invalidCaptcha()
+ self.retry()
+
+
+ def handleDlcContainer(self):
+ dlc = re.findall(self.DLC_LINK_PATTERN, self.siteWithLinks)
+
+ if not dlc:
+ return
+
+ for i in dlc:
+ self.links.append("%s/DLC/%s.dlc" % (self.base_url, i))
+
+
+ def handleWeblinks(self):
+ try:
+ weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks)
+
+ for link in weblinks:
+ res = self.load("%s/Link/%s.html" % (self.base_url, link))
+ link2 = re.search('<iframe noresize src="(.*)"></iframe>', res)
+ res2 = self.load(link2.group(1), just_header=True)
+ self.links.append(res2['location'])
+
+ except Exception, e:
+ self.logDebug("Error decrypting weblinks: %s" % e)
+
+
+ def handleCNL(self):
+ try:
+ vjk = re.findall('<input type="hidden" name="jk" value="function f\(\){ return \'(.*)\';}">', self.siteWithLinks)
+ vcrypted = re.findall('<input type="hidden" name="crypted" value="(.*)">', self.siteWithLinks)
+
+ for i in xrange(len(vcrypted)):
+ self.links.extend(self._getLinks(vcrypted[i], vjk[i]))
+
+ except Exception, e:
+ self.logDebug("Error decrypting CNL: %s" % e)
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ key = binascii.unhexlify(str(jk))
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ return links
diff --git a/pyload/plugin/crypter/FilefactoryCom.py b/pyload/plugin/crypter/FilefactoryCom.py
new file mode 100644
index 000000000..0b6290273
--- /dev/null
+++ b/pyload/plugin/crypter/FilefactoryCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilefactoryCom(SimpleCrypter):
+ __name = "FilefactoryCom"
+ __type = "crypter"
+ __version = "0.32"
+
+ __pattern = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filefactory.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
+
+ LINK_PATTERN = r'<td>\s*<a href="(.+?)"'
+ NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+?)<'
+ PAGES_PATTERN = r'data-paginator-totalPages="(\d+)'
+
+
+ def loadPage(self, page_n):
+ return self.load(self.pyfile.url, get={'page': page_n, 'show': 100})
diff --git a/pyload/plugin/crypter/FilerNet.py b/pyload/plugin/crypter/FilerNet.py
new file mode 100644
index 000000000..6ff4c279c
--- /dev/null
+++ b/pyload/plugin/crypter/FilerNet.py
@@ -0,0 +1,25 @@
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilerNet(SimpleCrypter):
+ __name = "FilerNet"
+ __type = "crypter"
+ __version = "0.42"
+
+ __pattern = r'https?://filer\.net/folder/\w{16}'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filer.net decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
+
+ NAME_PATTERN = r'<h3>(?P<N>.+?) - <small'
+ OFFLINE_PATTERN = r'Nicht gefunden'
diff --git a/pyload/plugin/crypter/FileserveCom.py b/pyload/plugin/crypter/FileserveCom.py
new file mode 100644
index 000000000..bf593b545
--- /dev/null
+++ b/pyload/plugin/crypter/FileserveCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class FileserveCom(Crypter):
+ __name = "FileserveCom"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?fileserve\.com/list/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """FileServe.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fionnc", "fionnc@gmail.com")]
+
+
+ FOLDER_PATTERN = r'<table class="file_list">(.*?)</table>'
+ LINK_PATTERN = r'<a href="(.+?)" class="sheet_icon wbold">'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ new_links = []
+
+ folder = re.search(self.FOLDER_PATTERN, html, re.S)
+ if folder is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1)))
+
+ if new_links:
+ self.urls = [map(lambda s: "http://fileserve.com%s" % s, new_links)]
diff --git a/pyload/plugin/crypter/FilesonicCom.py b/pyload/plugin/crypter/FilesonicCom.py
new file mode 100644
index 000000000..5114d03b8
--- /dev/null
+++ b/pyload/plugin/crypter/FilesonicCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FilesonicCom(DeadCrypter):
+ __name = "FilesonicCom"
+ __type = "crypter"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?filesonic\.com/folder/\w+'
+ __config = []
+
+ __description = """Filesonic.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/FilestubeCom.py b/pyload/plugin/crypter/FilestubeCom.py
new file mode 100644
index 000000000..fadeddedc
--- /dev/null
+++ b/pyload/plugin/crypter/FilestubeCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilestubeCom(SimpleCrypter):
+ __name = "FilestubeCom"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filestube.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
+ NAME_PATTERN = r'<h1\s*> (?P<N>.+) download\s*</h1>'
diff --git a/pyload/plugin/crypter/FiletramCom.py b/pyload/plugin/crypter/FiletramCom.py
new file mode 100644
index 000000000..4d6e898b4
--- /dev/null
+++ b/pyload/plugin/crypter/FiletramCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FiletramCom(SimpleCrypter):
+ __name = "FiletramCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filetram\.com/[^/]+/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filetram.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'\s+(http://.+)'
+ NAME_PATTERN = r'<title>(?P<N>.+?) - Free Download'
diff --git a/pyload/plugin/crypter/FiredriveCom.py b/pyload/plugin/crypter/FiredriveCom.py
new file mode 100644
index 000000000..be3a9ebdb
--- /dev/null
+++ b/pyload/plugin/crypter/FiredriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FiredriveCom(DeadCrypter):
+ __name = "FiredriveCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
+ __config = []
+
+ __description = """Firedrive.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/crypter/FourChanOrg.py b/pyload/plugin/crypter/FourChanOrg.py
new file mode 100644
index 000000000..6bafc6eb4
--- /dev/null
+++ b/pyload/plugin/crypter/FourChanOrg.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599)
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class FourChanOrg(Crypter):
+ __name = "FourChanOrg"
+ __type = "crypter"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """4chan.org folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ def decrypt(self, pyfile):
+ pagehtml = self.load(pyfile.url)
+ images = set(re.findall(r'(images\.4chan\.org/[^/]*/src/[^"<]+)', pagehtml))
+ self.urls = ["http://" + image for image in images]
diff --git a/pyload/plugin/crypter/FreakhareCom.py b/pyload/plugin/crypter/FreakhareCom.py
new file mode 100644
index 000000000..a0c19ac04
--- /dev/null
+++ b/pyload/plugin/crypter/FreakhareCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreakhareCom(SimpleCrypter):
+ __name = "FreakhareCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?freakshare\.com/folder/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Freakhare.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href="(http://freakshare\.com/files/.+?)" target="_blank">'
+ NAME_PATTERN = r'Folder:</b> (?P<N>.+)'
+ PAGES_PATTERN = r'Pages: +(\d+)'
+
+
+ def loadPage(self, page_n):
+ if not hasattr(self, 'f_id') and not hasattr(self, 'f_md5'):
+ m = re.search(r'http://freakshare.com/\?x=folder&f_id=(\d+)&f_md5=(\w+)', self.html)
+ if m:
+ self.f_id = m.group(1)
+ self.f_md5 = m.group(2)
+ return self.load('http://freakshare.com/', get={'x': 'folder',
+ 'f_id': self.f_id,
+ 'f_md5': self.f_md5,
+ 'entrys': '20',
+ 'page': page_n - 1,
+ 'order': ''}, decode=True)
diff --git a/pyload/plugin/crypter/FreetexthostCom.py b/pyload/plugin/crypter/FreetexthostCom.py
new file mode 100644
index 000000000..1f82c8ffa
--- /dev/null
+++ b/pyload/plugin/crypter/FreetexthostCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreetexthostCom(SimpleCrypter):
+ __name = "FreetexthostCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?freetexthost\.com/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Freetexthost.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getLinks(self):
+ m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.S)
+ if m is None:
+ self.error(_("Unable to extract links"))
+ links = m.group(1)
+ return links.strip().split("<br />\r\n")
diff --git a/pyload/plugin/crypter/FshareVn.py b/pyload/plugin/crypter/FshareVn.py
new file mode 100644
index 000000000..09adfee3e
--- /dev/null
+++ b/pyload/plugin/crypter/FshareVn.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FshareVn(SimpleCrypter):
+ __name = "FshareVn"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?fshare\.vn/folder/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Fshare.vn folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<li class="w_80pc"><a href="(.+?)" target="_blank">'
diff --git a/pyload/plugin/crypter/Go4UpCom.py b/pyload/plugin/crypter/Go4UpCom.py
new file mode 100644
index 000000000..eef9efddc
--- /dev/null
+++ b/pyload/plugin/crypter/Go4UpCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class Go4UpCom(SimpleCrypter):
+ __name = "Go4UpCom"
+ __type = "crypter"
+ __version = "0.12"
+
+ __pattern = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Go4Up.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("rlindner81", "rlindner81@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'(http://go4up\.com/rd/.+?)<'
+
+ NAME_PATTERN = r'<title>Download (.+?)<'
+
+ OFFLINE_PATTERN = r'>\s*(404 Page Not Found|File not Found|Mirror does not exist)'
+
+
+ def getLinks(self):
+ links = []
+
+ m = re.search(r'(/download/gethosts/.+?)"', self.html)
+ if m:
+ self.html = self.load(urlparse.urljoin("http://go4up.com/", m.group(1)))
+ pages = [self.load(url) for url in re.findall(self.LINK_PATTERN, self.html)]
+ else:
+ pages = [self.html]
+
+ for html in pages:
+ try:
+ links.append(re.search(r'<b><a href="(.+?)"', html).group(1))
+ except Exception:
+ continue
+
+ return links
diff --git a/pyload/plugin/crypter/GooGl.py b/pyload/plugin/crypter/GooGl.py
new file mode 100644
index 000000000..552a9ea01
--- /dev/null
+++ b/pyload/plugin/crypter/GooGl.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import json_loads
+
+
+class GooGl(Crypter):
+ __name = "GooGl"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?goo\.gl/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Goo.gl decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ API_URL = "https://www.googleapis.com/urlshortener/v1/url"
+
+
+ def decrypt(self, pyfile):
+ rep = self.load(self.API_URL, get={'shortUrl': pyfile.url})
+ self.logDebug("JSON data: " + rep)
+ rep = json_loads(rep)
+
+ if 'longUrl' in rep:
+ self.urls = [rep['longUrl']]
+ else:
+ self.fail(_("Unable to expand shortened link"))
diff --git a/pyload/plugin/crypter/HoerbuchIn.py b/pyload/plugin/crypter/HoerbuchIn.py
new file mode 100644
index 000000000..500dad8cc
--- /dev/null
+++ b/pyload/plugin/crypter/HoerbuchIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
+
+from pyload.plugin.Crypter import Crypter
+
+
+class HoerbuchIn(Crypter):
+ __name = "HoerbuchIn"
+ __type = "crypter"
+ __version = "0.60"
+
+ __pattern = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out\.php\?.+|protection/folder_\d+\.html)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Hoerbuch.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de")]
+
+
+ article = re.compile("http://(?:www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/")
+ protection = re.compile("http://(?:www\.)?hoerbuch\.in/protection/folder_\d+.html")
+
+
+ def decrypt(self, pyfile):
+ self.pyfile = pyfile
+
+ if self.article.match(pyfile.url):
+ html = self.load(pyfile.url)
+ soup = BeautifulSoup(html, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
+
+ abookname = soup.find("a", attrs={"rel": "bookmark"}).text
+ for a in soup.findAll("a", attrs={"href": self.protection}):
+ package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1])
+ links = self.decryptFolder(a['href'])
+
+ self.packages.append((package, links, package))
+ else:
+ self.urls = self.decryptFolder(pyfile.url)
+
+
+ def decryptFolder(self, url):
+ m = self.protection.search(url)
+ if m is None:
+ self.fail(_("Bad URL"))
+ url = m.group(0)
+
+ self.pyfile.url = url
+ html = self.load(url, post={"viewed": "adpg"})
+
+ links = []
+ pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
+ for hoster, lid in pattern.findall(html):
+ self.req.lastURL = url
+ self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid))
+ links.append(self.req.lastEffectiveURL)
+
+ return links
diff --git a/pyload/plugin/crypter/HotfileCom.py b/pyload/plugin/crypter/HotfileCom.py
new file mode 100644
index 000000000..8fc10cf88
--- /dev/null
+++ b/pyload/plugin/crypter/HotfileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class HotfileCom(DeadCrypter):
+ __name = "HotfileCom"
+ __type = "crypter"
+ __version = "0.30"
+
+ __pattern = r'https?://(?:www\.)?hotfile\.com/list/\w+/\w+'
+ __config = []
+
+ __description = """Hotfile.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
diff --git a/pyload/plugin/crypter/ILoadTo.py b/pyload/plugin/crypter/ILoadTo.py
new file mode 100644
index 000000000..e00a88743
--- /dev/null
+++ b/pyload/plugin/crypter/ILoadTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class ILoadTo(DeadCrypter):
+ __name = "ILoadTo"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?iload\.to/go/\d+-[\w.-]+/'
+ __config = []
+
+ __description = """Iload.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/ImgurComAlbum.py b/pyload/plugin/crypter/ImgurComAlbum.py
new file mode 100644
index 000000000..33a5553f9
--- /dev/null
+++ b/pyload/plugin/crypter/ImgurComAlbum.py
@@ -0,0 +1,28 @@
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import uniqify
+
+
+class ImgurComAlbum(SimpleCrypter):
+ __name = "ImgurComAlbum"
+ __type = "crypter"
+ __version = "0.51"
+
+ __pattern = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Imgur.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com")]
+
+
+ NAME_PATTERN = r'(?P<N>.+?) - Imgur'
+ LINK_PATTERN = r'i\.imgur\.com/\w{7}s?\.(?:jpeg|jpg|png|gif|apng)'
+
+
+ def getLinks(self):
+ f = lambda url: "http://" + re.sub(r'(\w{7})s\.', r'\1.', url)
+ return uniqify(map(f, re.findall(self.LINK_PATTERN, self.html)))
diff --git a/pyload/plugin/crypter/LetitbitNet.py b/pyload/plugin/crypter/LetitbitNet.py
new file mode 100644
index 000000000..51f54014f
--- /dev/null
+++ b/pyload/plugin/crypter/LetitbitNet.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class LetitbitNet(Crypter):
+ __name = "LetitbitNet"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?letitbit\.net/folder/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Letitbit.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("DHMH", "webmaster@pcProfil.de"),
+ ("z00nx", "z00nx0@gmail.com")]
+
+
+ FOLDER_PATTERN = r'<table>(.*)</table>'
+ LINK_PATTERN = r'<a href="(.+?)" target="_blank">'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ folder = re.search(self.FOLDER_PATTERN, html, re.S)
+ if folder is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
diff --git a/pyload/plugin/crypter/LinkCryptWs.py b/pyload/plugin/crypter/LinkCryptWs.py
new file mode 100644
index 000000000..c997cbf9f
--- /dev/null
+++ b/pyload/plugin/crypter/LinkCryptWs.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+import pycurl
+
+from Crypto.Cipher import AES
+
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import html_unescape
+
+
+class LinkCryptWs(Crypter):
+ __name = "LinkCryptWs"
+ __type = "crypter"
+ __version = "0.08"
+
+ __pattern = r'http://(?:www\.)?linkcrypt\.ws/(dir|container)/(?P<ID>\w+)'
+
+ __description = """LinkCrypt.ws decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("kagenoshin", "kagenoshin[AT]gmx[DOT]ch"),
+ ("glukgluk", ""),
+ ("Gummibaer", "")]
+
+
+ CRYPTED_KEY = "crypted"
+ JK_KEY = "jk"
+
+
+ def setup(self):
+ self.captcha = False
+ self.links = []
+ self.sources = ['cnl', 'web', 'dlc', 'rsdf', 'ccf']
+
+
+ def prepare(self):
+ # Init
+ self.fileid = re.match(self.__pattern, self.pyfile.url).group('ID')
+
+ self.req.cj.setCookie("linkcrypt.ws", "language", "en")
+
+ # Request package
+ self.req.http.c.setopt(pycurl.USERAGENT, "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko") #: better chance to not get those key-captchas
+ self.html = self.load(self.pyfile.url)
+
+
+ def decrypt(self, pyfile):
+ if not self.js:
+ self.fail(_("Missing JS Engine"))
+
+ self.prepare()
+
+ if not self.isOnline():
+ self.offline()
+
+ if self.isKeyCaptchaProtected():
+ self.retry(8, 15, _("Can't handle Key-Captcha"))
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleCaptchaErrors()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ # get unrar password
+ self.getunrarpw()
+
+ # Get package name and folder
+ package_name, folder_name = self.getPackageInfo()
+
+ # get the container definitions from script section
+ self.get_container_html()
+
+ # Extract package links
+ for type in self.sources:
+ links = self.handleLinkSource(type)
+
+ if links:
+ self.links.extend(links)
+ break
+
+ if self.links:
+ self.packages = [(package_name, self.links, folder_name)]
+
+
+ def isOnline(self):
+ if "<title>Linkcrypt.ws // Error 404</title>" in self.html:
+ self.logDebug("folder doesen't exist anymore")
+ return False
+ else:
+ return True
+
+
+ def isPasswordProtected(self):
+ if "Authorizing" in self.html:
+ self.logDebug("Links are password protected")
+ return True
+ else:
+ return False
+
+
+ def isCaptchaProtected(self):
+ if 'id="captcha">' in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ else:
+ return False
+
+
+ def isKeyCaptchaProtected(self):
+ if re.search(r'>If the folder does not open after klick on <', self.html, re.I):
+ return True
+ else:
+ return False
+
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+
+ if password:
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ self.html = self.load(self.pyfile.url, post={"password": password, 'x': "0", 'y': "0"})
+ else:
+ self.fail(_("Folder is password protected"))
+
+
+ def unlockCaptchaProtection(self):
+ captcha_url = re.search(r'<form.*?id\s*?=\s*?"captcha"[^>]*?>.*?<\s*?input.*?src="(.+?)"', self.html, re.I | re.S).group(1)
+ captcha_code = self.decryptCaptcha(captcha_url, forceUser=True, imgtype="gif", result_type='positional')
+
+ self.html = self.load(self.pyfile.url, post={"x": captcha_code[0], "y": captcha_code[1]})
+
+
+ def getPackageInfo(self):
+ name = self.pyfile.package().name
+ folder = self.pyfile.package().folder
+
+ self.logDebug("Defaulting to pyfile name [%s] and folder [%s] for package" % (name, folder))
+
+ return name, folder
+
+
+ def getunrarpw(self):
+ sitein = self.html
+ indexi = sitein.find("|source|") + 8
+ indexe = sitein.find("|",indexi)
+
+ unrarpw = sitein[indexi:indexe]
+
+ if not (unrarpw == "Password" or "Dateipasswort") :
+ self.logDebug("File password set to: [%s]"% unrarpw)
+ self.pyfile.package().password = unrarpw
+
+
+ def handleErrors(self):
+ if self.isPasswordProtected():
+ self.fail(_("Incorrect password"))
+
+
+ def handleCaptchaErrors(self):
+ if self.captcha:
+ if "Your choice was wrong!" in self.html:
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+
+ def handleLinkSource(self, type):
+ if type == 'cnl':
+ return self.handleCNL2()
+
+ elif type == 'web':
+ return self.handleWebLinks()
+
+ elif type in ('rsdf', 'ccf', 'dlc'):
+ return self.handleContainer(type)
+
+ else:
+ self.fail(_("Unknown source type: %s") % type) #@TODO: Replace with self.error in 0.4.10
+
+
+ def handleWebLinks(self):
+ self.logDebug("Search for Web links ")
+
+ package_links = []
+ pattern = r'<form action="http://linkcrypt.ws/out.html"[^>]*?>.*?<input[^>]*?value="(.+?)"[^>]*?name="file"'
+ ids = re.findall(pattern, self.html, re.I | re.S)
+
+ self.logDebug("Decrypting %d Web links" % len(ids))
+
+ for idx, weblink_id in enumerate(ids):
+ try:
+ self.logDebug("Decrypting Web link %d, %s" % (idx + 1, weblink_id))
+
+ res = self.load("http://linkcrypt.ws/out.html", post = {'file':weblink_id})
+
+ indexs = res.find("window.location =") + 19
+ indexe = res.find('"', indexs)
+
+ link2 = res[indexs:indexe]
+
+ self.logDebug(link2)
+
+ link2 = html_unescape(link2)
+ package_links.append(link2)
+
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (weblink_id, detail))
+
+ return package_links
+
+
+ def get_container_html(self):
+ self.container_html = []
+
+ script = re.search(r'<div.*?id="ad_cont".*?<script.*?javascrip[^>]*?>(.*?)</script', self.html, re.I | re.S)
+
+ if script:
+ container_html_text = script.group(1)
+ container_html_text.strip()
+ self.container_html = container_html_text.splitlines()
+
+
+ def handle_javascript(self, line):
+ return self.js.eval(line.replace('{}))',"{}).replace('document.open();document.write','').replace(';document.close();',''))"))
+
+
+ def handleContainer(self, type):
+ package_links = []
+ type = type.lower()
+
+ self.logDebug('Search for %s Container links' % type.upper())
+
+ if not type.isalnum(): #: check to prevent broken re-pattern (cnl2,rsdf,ccf,dlc,web are all alpha-numeric)
+ self.fail(_("Unknown container type: %s") % type) #@TODO: Replace with self.error in 0.4.10
+
+ for line in self.container_html:
+ if type in line:
+ jseval = self.handle_javascript(line)
+ clink = re.search(r'href=["\'](["\']+)', jseval, re.I)
+
+ if not clink:
+ continue
+
+ self.logDebug("clink avaible")
+
+ package_name, folder_name = self.getPackageInfo()
+ self.logDebug("Added package with name %s.%s and container link %s" %( package_name, type, clink.group(1)))
+ self.core.api.uploadContainer( "%s.%s" %(package_name, type), self.load(clink.group(1)))
+ return "Found it"
+
+ return package_links
+
+
+ def handleCNL2(self):
+ self.logDebug("Search for CNL links")
+
+ package_links = []
+ cnl_line = None
+
+ for line in self.container_html:
+ if "cnl" in line:
+ cnl_line = line
+ break
+
+ if cnl_line:
+ self.logDebug("cnl_line gefunden")
+
+ try:
+ cnl_section = self.handle_javascript(cnl_line)
+ (vcrypted, vjk) = self._getCipherParams(cnl_section)
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except Exception:
+ self.logError(_("Unable to decrypt CNL links (JS Error) try to get over links"))
+ return self.handleWebLinks()
+
+ return package_links
+
+
+ def _getCipherParams(self, cnl_section):
+ # Get jk
+ jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.JK_KEY
+ vjk = re.findall(jk_re, cnl_section)
+
+ # Get crypted
+ crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, cnl_section)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ key = binascii.unhexlify(jreturn)
+
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+
+ return links
diff --git a/pyload/plugin/crypter/LinkSaveIn.py b/pyload/plugin/crypter/LinkSaveIn.py
new file mode 100644
index 000000000..8c6f0b001
--- /dev/null
+++ b/pyload/plugin/crypter/LinkSaveIn.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleDereferer import SimpleDereferer
+
+
+class LinkSaveIn(SimpleDereferer):
+ __name = "LinkSaveIn"
+ __type = "crypter"
+ __version = "2.03"
+
+ __pattern = r'https?://(?:www\.)?linksave\.in/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """LinkSave.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ COOKIES = [("linksave.in", "Linksave_Language", "english")]
+
+ OFFLINE_PATTERN = r'>(Error )?404 -'
diff --git a/pyload/plugin/crypter/LinkdecrypterCom.py b/pyload/plugin/crypter/LinkdecrypterCom.py
new file mode 100644
index 000000000..907e3accd
--- /dev/null
+++ b/pyload/plugin/crypter/LinkdecrypterCom.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class LinkdecrypterCom(Crypter):
+ __name = "LinkdecrypterCom"
+ __type = "crypter"
+ __version = "0.29"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Linkdecrypter.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("flowlee", "")]
+
+
+ TEXTAREA_PATTERN = r'<textarea name="links" wrap="off" readonly="1" class="caja_des">(.+)</textarea>'
+ PASSWORD_PATTERN = r'<input type="text" name="password"'
+ CAPTCHA_PATTERN = r'<img class="captcha" src="(.+?)"(.*?)>'
+ REDIR_PATTERN = r'<i>(Click <a href="./">here</a> if your browser does not redirect you).</i>'
+
+
+ def setup(self):
+ self.password = self.getPassword()
+ self.req.setOption("timeout", 300)
+
+
+ def decrypt(self, pyfile):
+ retries = 5
+
+ post_dict = {"link_cache": "on", "pro_links": pyfile.url, "modo_links": "text"}
+ self.html = self.load('http://linkdecrypter.com/', post=post_dict, decode=True)
+
+ while retries:
+ m = re.search(self.TEXTAREA_PATTERN, self.html, re.S)
+ if m:
+ self.urls = [x for x in m.group(1).splitlines() if '[LINK-ERROR]' not in x]
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_url = 'http://linkdecrypter.com/' + m.group(1)
+ result_type = "positional" if "getPos" in m.group(2) else "textual"
+
+ m = re.search(r"<p><i><b>([^<]+)</b></i></p>", self.html)
+ msg = m.group(1) if m else ""
+ self.logInfo(_("Captcha protected link"), result_type, msg)
+
+ captcha = self.decryptCaptcha(captcha_url, result_type=result_type)
+ if result_type == "positional":
+ captcha = "%d|%d" % captcha
+ self.html = self.load('http://linkdecrypter.com/', post={"captcha": captcha}, decode=True)
+ retries -= 1
+
+ elif self.PASSWORD_PATTERN in self.html:
+ if self.password:
+ self.logInfo(_("Password protected link"))
+ self.html = self.load('http://linkdecrypter.com/', post={'password': self.password}, decode=True)
+ else:
+ self.fail(_("Missing password"))
+
+ else:
+ retries -= 1
+ self.html = self.load('http://linkdecrypter.com/', decode=True)
diff --git a/pyload/plugin/crypter/LixIn.py b/pyload/plugin/crypter/LixIn.py
new file mode 100644
index 000000000..d15761014
--- /dev/null
+++ b/pyload/plugin/crypter/LixIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class LixIn(Crypter):
+ __name = "LixIn"
+ __type = "crypter"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?lix\.in/(?P<ID>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Lix.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
+
+
+ CAPTCHA_PATTERN = r'<img src="(captcha_img\.php\?.*?)"'
+ SUBMIT_PATTERN = r'value=\'continue.*?\''
+ LINK_PATTERN = r'name="ifram" src="(.*?)"'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ m = re.match(self.__pattern, url)
+ if m is None:
+ self.error(_("Unable to identify file ID"))
+
+ id = m.group('ID')
+ self.logDebug("File id is %s" % id)
+
+ self.html = self.load(url, decode=True)
+
+ m = re.search(self.SUBMIT_PATTERN, self.html)
+ if m is None:
+ self.error(_("Link doesn't seem valid"))
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ for _i in xrange(5):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ self.logDebug("Trying captcha")
+ captcharesult = self.decryptCaptcha("http://lix.in/" + m.group(1))
+ self.html = self.load(url, decode=True,
+ post={"capt": captcharesult, "submit": "submit", "tiny": id})
+ else:
+ self.logDebug("No captcha/captcha solved")
+ else:
+ self.html = self.load(url, decode=True, post={"submit": "submit", "tiny": id})
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Unable to find destination url"))
+ else:
+ self.urls = [m.group(1)]
+ self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/pyload/plugin/crypter/LofCc.py b/pyload/plugin/crypter/LofCc.py
new file mode 100644
index 000000000..680027b43
--- /dev/null
+++ b/pyload/plugin/crypter/LofCc.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class LofCc(DeadCrypter):
+ __name = "LofCc"
+ __type = "crypter"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?lof\.cc/(.+)'
+ __config = []
+
+ __description = """Lof.cc decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/crypter/MBLinkInfo.py b/pyload/plugin/crypter/MBLinkInfo.py
new file mode 100644
index 000000000..7b39f9b5b
--- /dev/null
+++ b/pyload/plugin/crypter/MBLinkInfo.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MBLinkInfo(DeadCrypter):
+ __name = "MBLinkInfo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
+ __config = []
+
+ __description = """MBLink.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Gummibaer", "Gummibaer@wiki-bierkiste.de"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/MediafireCom.py b/pyload/plugin/crypter/MediafireCom.py
new file mode 100644
index 000000000..aae727a90
--- /dev/null
+++ b/pyload/plugin/crypter/MediafireCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.hoster.MediafireCom import checkHTMLHeader
+from pyload.utils import json_loads
+
+
+class MediafireCom(Crypter):
+ __name = "MediafireCom"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Mediafire.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_KEY_PATTERN = r'var afI= \'(\w+)'
+ LINK_PATTERN = r'<meta property="og:url" content="http://www\.mediafire\.com/\?(\w+)"/>'
+
+
+ def decrypt(self, pyfile):
+ url, result = checkHTMLHeader(pyfile.url)
+ self.logDebug("Location (%d): %s" % (result, url))
+
+ if result == 0:
+ # load and parse html
+ html = self.load(pyfile.url)
+ m = re.search(self.LINK_PATTERN, html)
+ if m:
+ # file page
+ self.urls.append("http://www.mediafire.com/file/%s" % m.group(1))
+ else:
+ # folder page
+ m = re.search(self.FOLDER_KEY_PATTERN, html)
+ if m:
+ folder_key = m.group(1)
+ self.logDebug("FOLDER KEY: %s" % folder_key)
+
+ json_resp = json_loads(self.load("http://www.mediafire.com/api/folder/get_info.php",
+ get={'folder_key' : folder_key,
+ 'response_format': "json",
+ 'version' : 1}))
+ # self.logInfo(json_resp)
+ if json_resp['response']['result'] == "Success":
+ for link in json_resp['response']['folder_info']['files']:
+ self.urls.append("http://www.mediafire.com/file/%s" % link['quickkey'])
+ else:
+ self.fail(json_resp['response']['message'])
+ elif result == 1:
+ self.offline()
+ else:
+ self.urls.append(url)
diff --git a/pyload/plugin/crypter/MegaCoNz.py b/pyload/plugin/crypter/MegaCoNz.py
new file mode 100644
index 000000000..c66b3f112
--- /dev/null
+++ b/pyload/plugin/crypter/MegaCoNz.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class MegaCoNz(Crypter):
+ __name = "MegaCoNz"
+ __type = "crypter"
+ __version = "0.04"
+
+ __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#F!(?P<ID>[\w^_]+)!(?P<KEY>[\w,\\-]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Mega.co.nz folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def setup(self):
+ self.req.setOption("timeout", 300)
+
+
+ def decrypt(self, pyfile):
+ url = "https://mega.co.nz/#F!%s!%s" % re.match(self.__pattern, pyfile.url).groups()
+ self.html = self.load("http://rapidgen.org/linkfinder", post={'linklisturl': url})
+ self.urls = re.findall(r'(https://mega.co.nz/#N!.+?)<', self.html)
diff --git a/pyload/plugin/crypter/MegaRapidCz.py b/pyload/plugin/crypter/MegaRapidCz.py
new file mode 100644
index 000000000..fe490464a
--- /dev/null
+++ b/pyload/plugin/crypter/MegaRapidCz.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class MegaRapidCz(SimpleCrypter):
+ __name = "MegaRapidCz"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/slozka/\d+/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Share-Rapid.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<td class="soubor".*?><a href="(.+?)">'
diff --git a/pyload/plugin/crypter/MegauploadCom.py b/pyload/plugin/crypter/MegauploadCom.py
new file mode 100644
index 000000000..f300408e8
--- /dev/null
+++ b/pyload/plugin/crypter/MegauploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MegauploadCom(DeadCrypter):
+ __name = "MegauploadCom"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+'
+ __config = []
+
+ __description = """Megaupload.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/Movie2KTo.py b/pyload/plugin/crypter/Movie2KTo.py
new file mode 100644
index 000000000..e807855af
--- /dev/null
+++ b/pyload/plugin/crypter/Movie2KTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class Movie2KTo(DeadCrypter):
+ __name = "Movie2KTo"
+ __type = "crypter"
+ __version = "0.51"
+
+ __pattern = r'http://(?:www\.)?movie2k\.to/(.+)\.html'
+ __config = []
+
+ __description = """Movie2k.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("4Christopher", "4Christopher@gmx.de")]
diff --git a/pyload/plugin/crypter/MultiUpOrg.py b/pyload/plugin/crypter/MultiUpOrg.py
new file mode 100644
index 000000000..54d9a979d
--- /dev/null
+++ b/pyload/plugin/crypter/MultiUpOrg.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class MultiUpOrg(SimpleCrypter):
+ __name = "MultiUpOrg"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """MultiUp.org decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<title>.*(?:Project|Projet|ownload|élécharger) (?P<N>.+?) (\(|- )'
+
+
+ def getLinks(self):
+ m_type = re.match(self.__pattern, self.pyfile.url).group('TYPE')
+
+ if m_type == "project":
+ pattern = r'\n(http://www\.multiup\.org/(?:en|fr)/download/.*)'
+ else:
+ pattern = r'style="width:97%;text-align:left".*\n.*href="(.*)"'
+ if m_type == "download":
+ dl_pattern = r'href="(.*)">.*\n.*<h5>DOWNLOAD</h5>'
+ miror_page = urlparse.urljoin("http://www.multiup.org", re.search(dl_pattern, self.html).group(1))
+ self.html = self.load(miror_page)
+
+ return re.findall(pattern, self.html)
diff --git a/pyload/plugin/crypter/MultiloadCz.py b/pyload/plugin/crypter/MultiloadCz.py
new file mode 100644
index 000000000..2e2d69037
--- /dev/null
+++ b/pyload/plugin/crypter/MultiloadCz.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class MultiloadCz(Crypter):
+ __name = "MultiloadCz"
+ __type = "crypter"
+ __version = "0.40"
+
+ __pattern = r'http://(?:[^/]*\.)?multiload\.cz/(stahnout|slozka)/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True),
+ ("usedHoster" , "str" , "Prefered hoster list (bar-separated)", "" ),
+ ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
+
+ __description = """Multiload.cz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_PATTERN = r'<form action="" method="get"><textarea.*?>([^>]*)</textarea></form>'
+ LINK_PATTERN = r'<p class="manager-server"><strong>([^<]+)</strong></p><p class="manager-linky"><a href="(.+?)">'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ if re.match(self.__pattern, pyfile.url).group(1) == "slozka":
+ m = re.search(self.FOLDER_PATTERN, self.html)
+ if m:
+ self.urls.extend(m.group(1).split())
+ else:
+ m = re.findall(self.LINK_PATTERN, self.html)
+ if m:
+ prefered_set = set(self.getConfig('usedHoster').split('|'))
+ self.urls.extend(x[1] for x in m if x[0] in prefered_set)
+
+ if not self.urls:
+ ignored_set = set(self.getConfig('ignoredHoster').split('|'))
+ self.urls.extend(x[1] for x in m if x[0] not in ignored_set)
diff --git a/pyload/plugin/crypter/MultiuploadCom.py b/pyload/plugin/crypter/MultiuploadCom.py
new file mode 100644
index 000000000..439e0e69b
--- /dev/null
+++ b/pyload/plugin/crypter/MultiuploadCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MultiuploadCom(DeadCrypter):
+ __name = "MultiuploadCom"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?multiupload\.(com|nl)/\w+'
+
+ __description = """MultiUpload.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/NCryptIn.py b/pyload/plugin/crypter/NCryptIn.py
new file mode 100644
index 000000000..bc9702f21
--- /dev/null
+++ b/pyload/plugin/crypter/NCryptIn.py
@@ -0,0 +1,310 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class NCryptIn(Crypter):
+ __name = "NCryptIn"
+ __type = "crypter"
+ __version = "1.34"
+
+ __pattern = r'http://(?:www\.)?ncrypt\.in/(?P<TYPE>folder|link|frame)-([^/\?]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """NCrypt.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ JK_KEY = "jk"
+ CRYPTED_KEY = "crypted"
+
+ NAME_PATTERN = r'<meta name="description" content="(?P<N>.+?)"'
+
+
+ def setup(self):
+ self.package = None
+ self.cleanedHtml = None
+ self.links_source_order = ["cnl2", "rsdf", "ccf", "dlc", "web"]
+ self.protection_type = None
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.package = pyfile.package()
+ package_links = []
+ package_name = self.package.name
+ folder_name = self.package.folder
+
+ # Deal with single links
+ if self.isSingleLink():
+ package_links.extend(self.handleSingleLink())
+
+ # Deal with folders
+ else:
+
+ # Request folder home
+ self.html = self.requestFolderHome()
+ self.cleanedHtml = self.removeHtmlCrap(self.html)
+ if not self.isOnline():
+ self.offline()
+
+ # Check for folder protection
+ if self.isProtected():
+ self.html = self.unlockProtection()
+ self.cleanedHtml = self.removeHtmlCrap(self.html)
+ self.handleErrors()
+
+ # Prepare package name and folder
+ (package_name, folder_name) = self.getPackageInfo()
+
+ # Extract package links
+ for link_source_type in self.links_source_order:
+ package_links.extend(self.handleLinkSource(link_source_type))
+ if package_links: #: use only first source which provides links
+ break
+ package_links = set(package_links)
+
+ # Pack and return links
+ if package_links:
+ self.packages = [(package_name, package_links, folder_name)]
+
+
+ def isSingleLink(self):
+ link_type = re.match(self.__pattern, self.pyfile.url).group('TYPE')
+ return link_type in ("link", "frame")
+
+
+ def requestFolderHome(self):
+ return self.load(self.pyfile.url, decode=True)
+
+
+ def removeHtmlCrap(self, content):
+ patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")',
+ r'display:none;">(.*?)</(div|span)>',
+ r'<div\s+class="jdownloader"(.*?)</div>',
+ r'<table class="global">(.*?)</table>',
+ r'<iframe\s+style="display:none(.*?)</iframe>')
+ for pattern in patterns:
+ rexpr = re.compile(pattern, re.S)
+ content = re.sub(rexpr, "", content)
+ return content
+
+
+ def isOnline(self):
+ if "Your folder does not exist" in self.cleanedHtml:
+ self.logDebug("File not m")
+ return False
+ return True
+
+
+ def isProtected(self):
+ form = re.search(r'<form.*?name.*?protected.*?>(.*?)</form>', self.cleanedHtml, re.S)
+ if form:
+ content = form.group(1)
+ for keyword in ("password", "captcha"):
+ if keyword in content:
+ self.protection_type = keyword
+ self.logDebug("Links are %s protected" % self.protection_type)
+ return True
+ return False
+
+
+ def getPackageInfo(self):
+ m = re.search(self.NAME_PATTERN, self.html)
+ if m:
+ name = folder = m.group('N').strip()
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+ else:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not m, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+ return name, folder
+
+
+ def unlockProtection(self):
+ postData = {}
+
+ form = re.search(r'<form name="protected"(.*?)</form>', self.cleanedHtml, re.S).group(1)
+
+ # Submit package password
+ if "password" in form:
+ password = self.getPassword()
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ postData['password'] = password
+
+ # Resolve anicaptcha
+ if "anicaptcha" in form:
+ self.logDebug("Captcha protected")
+ captchaUri = re.search(r'src="(/temp/anicaptcha/.+?)"', form).group(1)
+ captcha = self.decryptCaptcha("http://ncrypt.in" + captchaUri)
+ self.logDebug("Captcha resolved [%s]" % captcha)
+ postData['captcha'] = captcha
+
+ # Resolve recaptcha
+ if "recaptcha" in form:
+ self.logDebug("ReCaptcha protected")
+ captcha_key = re.search(r'\?k=(.*?)"', form).group(1)
+ self.logDebug("Resolving ReCaptcha with key [%s]" % captcha_key)
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge(captcha_key)
+ postData['recaptcha_challenge_field'] = challenge
+ postData['recaptcha_response_field'] = response
+
+ # Resolve circlecaptcha
+ if "circlecaptcha" in form:
+ self.logDebug("CircleCaptcha protected")
+ captcha_img_url = "http://ncrypt.in/classes/captcha/circlecaptcha.php"
+ coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+ postData['circle.x'] = coords[0]
+ postData['circle.y'] = coords[1]
+
+ # Unlock protection
+ postData['submit_protected'] = 'Continue to folder'
+ return self.load(self.pyfile.url, post=postData, decode=True)
+
+
+ def handleErrors(self):
+ if self.protection_type == "password":
+ if "This password is invalid!" in self.cleanedHtml:
+ self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
+ self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry"))
+
+ if self.protection_type == "captcha":
+ if "The securitycheck was wrong!" in self.cleanedHtml:
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+
+ def handleLinkSource(self, link_source_type):
+ # Check for JS engine
+ require_js_engine = link_source_type in ("cnl2", "rsdf", "ccf", "dlc")
+ if require_js_engine and not self.js:
+ self.logDebug("No JS engine available, skip %s links" % link_source_type)
+ return []
+
+ # Select suitable handler
+ if link_source_type == 'single':
+ return self.handleSingleLink()
+ if link_source_type == 'cnl2':
+ return self.handleCNL2()
+ elif link_source_type in ("rsdf", "ccf", "dlc"):
+ return self.handleContainer(link_source_type)
+ elif link_source_type == "web":
+ return self.handleWebLinks()
+ else:
+ self.error(_('Unknown source type "%s"') % link_source_type)
+
+
+ def handleSingleLink(self):
+ self.logDebug("Handling Single link")
+ package_links = []
+
+ # Decrypt single link
+ decrypted_link = self.decryptLink(self.pyfile.url)
+ if decrypted_link:
+ package_links.append(decrypted_link)
+
+ return package_links
+
+
+ def handleCNL2(self):
+ self.logDebug("Handling CNL2 links")
+ package_links = []
+
+ if 'cnl2_output' in self.cleanedHtml:
+ try:
+ (vcrypted, vjk) = self._getCipherParams()
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except Exception:
+ self.fail(_("Unable to decrypt CNL2 links"))
+
+ return package_links
+
+
+ def handleContainers(self):
+ self.logDebug("Handling Container links")
+ package_links = []
+
+ pattern = r'/container/(rsdf|dlc|ccf)/(\w+)'
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Container links" % len(containersLinks))
+ for containerLink in containersLinks:
+ link = "http://ncrypt.in/container/%s/%s.%s" % (containerLink[0], containerLink[1], containerLink[0])
+ package_links.append(link)
+
+ return package_links
+
+
+ def handleWebLinks(self):
+ self.logDebug("Handling Web links")
+ pattern = r'(http://ncrypt\.in/link-.*?=)'
+ links = re.findall(pattern, self.html)
+
+ package_links = []
+ self.logDebug("Decrypting %d Web links" % len(links))
+ for i, link in enumerate(links):
+ self.logDebug("Decrypting Web link %d, %s" % (i + 1, link))
+ decrypted_link = self.decrypt(link)
+ if decrypted_link:
+ package_links.append(decrypted_link)
+
+ return package_links
+
+
+ def decryptLink(self, link):
+ try:
+ url = link.replace("link-", "frame-")
+ link = self.load(url, just_header=True)['location']
+ return link
+ except Exception, detail:
+ self.logDebug("Error decrypting link %s, %s" % (link, detail))
+
+
+ def _getCipherParams(self):
+ pattern = r'<input.*?name="%s".*?value="(.*?)"'
+
+ # Get jk
+ jk_re = pattern % NCryptIn.JK_KEY
+ vjk = re.findall(jk_re, self.html)
+
+ # Get crypted
+ crypted_re = pattern % NCryptIn.CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, self.html)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/NetfolderIn.py b/pyload/plugin/crypter/NetfolderIn.py
new file mode 100644
index 000000000..f036795d5
--- /dev/null
+++ b/pyload/plugin/crypter/NetfolderIn.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class NetfolderIn(SimpleCrypter):
+ __name = "NetfolderIn"
+ __type = "crypter"
+ __version = "0.72"
+
+ __pattern = r'http://(?:www\.)?netfolder\.in/(folder\.php\?folder_id=)?(?P<ID>\w+)(?(1)|/\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """NetFolder.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ NAME_PATTERN = r'<div class="Text">Inhalt des Ordners <span.*>(?P<N>.+)</span></div>'
+
+
+ def prepare(self):
+ super(NetfolderIn, self).prepare()
+
+ # Check for password protection
+ if self.isPasswordProtected():
+ self.html = self.submitPassword()
+ if not self.html:
+ self.fail(_("Incorrect password, please set right password on Add package form and retry"))
+
+
+ def isPasswordProtected(self):
+ if '<input type="password" name="password"' in self.html:
+ self.logDebug("Links are password protected")
+ return True
+ return False
+
+
+ def submitPassword(self):
+ # Gather data
+ try:
+ m = re.match(self.__pattern, self.pyfile.url)
+ id = m.group('ID')
+ except AttributeError:
+ self.logDebug("Unable to get package id from url [%s]" % self.pyfile.url)
+ return
+ url = "http://netfolder.in/folder.php?folder_id=" + id
+ password = self.getPassword()
+
+ # Submit package password
+ post = {'password': password, 'save': 'Absenden'}
+ self.logDebug("Submitting password [%s] for protected links with id [%s]" % (password, id))
+ html = self.load(url, {}, post)
+
+ # Check for invalid password
+ if '<div class="InPage_Error">' in html:
+ self.logDebug("Incorrect password, please set right password on Edit package form and retry")
+ return None
+
+ return html
+
+
+ def getLinks(self):
+ links = re.search(r'name="list" value="(.*?)"', self.html).group(1).split(",")
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/NosvideoCom.py b/pyload/plugin/crypter/NosvideoCom.py
new file mode 100644
index 000000000..2afc697f3
--- /dev/null
+++ b/pyload/plugin/crypter/NosvideoCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class NosvideoCom(SimpleCrypter):
+ __name = "NosvideoCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Nosvideo.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload\.com/\?d=\w+)"'
+ NAME_PATTERN = r'<[tT]itle>Watch (?P<N>.+?)<'
diff --git a/pyload/plugin/crypter/OneKhDe.py b/pyload/plugin/crypter/OneKhDe.py
new file mode 100644
index 000000000..9d21710de
--- /dev/null
+++ b/pyload/plugin/crypter/OneKhDe.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import html_unescape
+
+from pyload.plugin.Crypter import Crypter
+
+
+class OneKhDe(Crypter):
+ __name = "OneKhDe"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?1kh\.de/f/'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """1kh.de decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
+
+
+ def __init__(self, parent):
+ Crypter.__init__(self, parent)
+ self.parent = parent
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ return True
+
+
+ def proceed(self, url, location):
+ url = self.parent.url
+ self.html = self.load(url)
+ link_ids = re.findall(r"<a id=\"DownloadLink_(\d*)\" href=\"http://1kh.de/", self.html)
+ for id in link_ids:
+ new_link = html_unescape(re.search("width=\"100%\" src=\"(.*)\"></iframe>", self.load("http://1kh.de/l/" + id)).group(1))
+ self.urls.append(new_link)
diff --git a/pyload/plugin/crypter/OronCom.py b/pyload/plugin/crypter/OronCom.py
new file mode 100644
index 000000000..316e6d525
--- /dev/null
+++ b/pyload/plugin/crypter/OronCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class OronCom(DeadCrypter):
+ __name = "OronCom"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?oron\.com/folder/\w+'
+ __config = []
+
+ __description = """Oron.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("DHMH", "webmaster@pcProfil.de")]
diff --git a/pyload/plugin/crypter/PastebinCom.py b/pyload/plugin/crypter/PastebinCom.py
new file mode 100644
index 000000000..034c859a1
--- /dev/null
+++ b/pyload/plugin/crypter/PastebinCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class PastebinCom(SimpleCrypter):
+ __name = "PastebinCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?pastebin\.com/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Pastebin.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
+ NAME_PATTERN = r'<div class="paste_box_line1" title="(?P<N>.+?)">'
diff --git a/pyload/plugin/crypter/QuickshareCz.py b/pyload/plugin/crypter/QuickshareCz.py
new file mode 100644
index 000000000..201fc1f6d
--- /dev/null
+++ b/pyload/plugin/crypter/QuickshareCz.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class QuickshareCz(Crypter):
+ __name = "QuickshareCz"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?quickshare\.cz/slozka-\d+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Quickshare.cz folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_PATTERN = r'<textarea.*?>(.*?)</textarea>'
+ LINK_PATTERN = r'(http://www\.quickshare\.cz/\S+)'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ m = re.search(self.FOLDER_PATTERN, html, re.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/pyload/plugin/crypter/RSLayerCom.py b/pyload/plugin/crypter/RSLayerCom.py
new file mode 100644
index 000000000..756c356f7
--- /dev/null
+++ b/pyload/plugin/crypter/RSLayerCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class RSLayerCom(DeadCrypter):
+ __name = "RSLayerCom"
+ __type = "crypter"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?rs-layer\.com/directory-'
+ __config = []
+
+ __description = """RS-Layer.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/RelinkUs.py b/pyload/plugin/crypter/RelinkUs.py
new file mode 100644
index 000000000..2b9a85401
--- /dev/null
+++ b/pyload/plugin/crypter/RelinkUs.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import binascii
+import re
+import os
+
+from Crypto.Cipher import AES
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class RelinkUs(Crypter):
+ __name = "RelinkUs"
+ __type = "crypter"
+ __version = "3.12"
+
+ __pattern = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<ID>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Relink.us decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("AndroKev", "neureither.kevin@gmail.com")]
+
+
+ PREFERRED_LINK_SOURCES = ["cnl2", "dlc", "web"]
+
+ OFFLINE_TOKEN = r'<title>Tattooside'
+
+ PASSWORD_TOKEN = r'container_password.php'
+ PASSWORD_ERROR_ROKEN = r'You have entered an incorrect password'
+ PASSWORD_SUBMIT_URL = r'http://www.relink.us/container_password.php'
+
+ CAPTCHA_TOKEN = r'container_captcha.php'
+ CAPTCHA_ERROR_ROKEN = r'You have solved the captcha wrong'
+ CAPTCHA_IMG_URL = r'http://www.relink.us/core/captcha/circlecaptcha.php'
+ CAPTCHA_SUBMIT_URL = r'http://www.relink.us/container_captcha.php'
+
+ FILE_TITLE_REGEX = r'<th>Title</th><td>(.*)</td></tr>'
+ FILE_NOTITLE = r'No title'
+
+ CNL2_FORM_REGEX = r'<form id="cnl_form-(.*?)</form>'
+ CNL2_FORMINPUT_REGEX = r'<input.*?name="%s".*?value="(.*?)"'
+ CNL2_JK_KEY = "jk"
+ CNL2_CRYPTED_KEY = "crypted"
+
+ DLC_LINK_REGEX = r'<a href=".*?" class="dlc_button" target="_blank">'
+ DLC_DOWNLOAD_URL = r'http://www.relink.us/download.php'
+
+ WEB_FORWARD_REGEX = r'getFile\(\'(.+)\'\)'
+ WEB_FORWARD_URL = r'http://www.relink.us/frame.php'
+ WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(.+)"></iframe>'
+
+
+ def setup(self):
+ self.fileid = None
+ self.package = None
+ self.captcha = False
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.initPackage(pyfile)
+
+ # Request package
+ self.requestPackage()
+
+ # Check for online
+ if not self.isOnline():
+ self.offline()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleErrors()
+
+ # Get package name and folder
+ (package_name, folder_name) = self.getPackageInfo()
+
+ # Extract package links
+ package_links = []
+ for sources in self.PREFERRED_LINK_SOURCES:
+ package_links.extend(self.handleLinkSource(sources))
+ if package_links: #: use only first source which provides links
+ break
+ package_links = set(package_links)
+
+ # Pack
+ if package_links:
+ self.packages = [(package_name, package_links, folder_name)]
+
+
+ def initPackage(self, pyfile):
+ self.fileid = re.match(self.__pattern, pyfile.url).group('ID')
+ self.package = pyfile.package()
+
+
+ def requestPackage(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+
+ def isOnline(self):
+ if self.OFFLINE_TOKEN in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+
+ def isPasswordProtected(self):
+ if self.PASSWORD_TOKEN in self.html:
+ self.logDebug("Links are password protected")
+ return True
+
+
+ def isCaptchaProtected(self):
+ if self.CAPTCHA_TOKEN in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ return False
+
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+
+ self.logDebug("Submitting password [%s] for protected links" % password)
+
+ if password:
+ passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
+ passwd_data = {'id': self.fileid, 'password': password, 'pw': 'submit'}
+ self.html = self.load(passwd_url, post=passwd_data, decode=True)
+
+
+ def unlockCaptchaProtection(self):
+ self.logDebug("Request user positional captcha resolving")
+ captcha_img_url = self.CAPTCHA_IMG_URL + "?id=%s" % self.fileid
+ coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+ captcha_post_url = self.CAPTCHA_SUBMIT_URL + "?id=%s" % self.fileid
+ captcha_post_data = {'button.x': coords[0], 'button.y': coords[1], 'captcha': 'submit'}
+ self.html = self.load(captcha_post_url, post=captcha_post_data, decode=True)
+
+
+ def getPackageInfo(self):
+ name = folder = None
+
+ # Try to get info from web
+ m = re.search(self.FILE_TITLE_REGEX, self.html)
+ if m:
+ title = m.group(1).strip()
+ if not self.FILE_NOTITLE in title:
+ name = folder = title
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+
+ # Fallback to defaults
+ if not name or not folder:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+
+ # Return package info
+ return name, folder
+
+
+ def handleErrors(self):
+ if self.PASSWORD_ERROR_ROKEN in self.html:
+ msg = "Incorrect password, please set right password on 'Edit package' form and retry"
+ self.logDebug(msg)
+ self.fail(_(msg))
+
+ if self.captcha:
+ if self.CAPTCHA_ERROR_ROKEN in self.html:
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+
+ def handleLinkSource(self, source):
+ if source == 'cnl2':
+ return self.handleCNL2Links()
+ elif source == 'dlc':
+ return self.handleDLCLinks()
+ elif source == 'web':
+ return self.handleWEBLinks()
+ else:
+ self.error(_('Unknown source type "%s"') % source)
+
+
+ def handleCNL2Links(self):
+ self.logDebug("Search for CNL2 links")
+ package_links = []
+ m = re.search(self.CNL2_FORM_REGEX, self.html, re.S)
+ if m:
+ cnl2_form = m.group(1)
+ try:
+ (vcrypted, vjk) = self._getCipherParams(cnl2_form)
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except Exception:
+ self.logDebug("Unable to decrypt CNL2 links")
+ return package_links
+
+
+ def handleDLCLinks(self):
+ self.logDebug("Search for DLC links")
+ package_links = []
+ m = re.search(self.DLC_LINK_REGEX, self.html)
+ if m:
+ container_url = self.DLC_DOWNLOAD_URL + "?id=%s&dlc=1" % self.fileid
+ self.logDebug("Downloading DLC container link [%s]" % container_url)
+ try:
+ dlc = self.load(container_url)
+ dlc_filename = self.fileid + ".dlc"
+ dlc_filepath = fs_join(self.config.get("general", "download_folder"), dlc_filename)
+ with open(dlc_filepath, "wb") as f:
+ f.write(dlc)
+ package_links.append(dlc_filepath)
+
+ except Exception:
+ self.fail(_("Unable to download DLC container"))
+
+ return package_links
+
+
+ def handleWEBLinks(self):
+ self.logDebug("Search for WEB links")
+
+ package_links = []
+ params = re.findall(self.WEB_FORWARD_REGEX, self.html)
+
+ self.logDebug("Decrypting %d Web links" % len(params))
+
+ for index, param in enumerate(params):
+ try:
+ url = self.WEB_FORWARD_URL + "?%s" % param
+
+ self.logDebug("Decrypting Web link %d, %s" % (index + 1, url))
+
+ res = self.load(url, decode=True)
+ link = re.search(self.WEB_LINK_REGEX, res).group(1)
+
+ package_links.append(link)
+
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (index, detail))
+
+ self.setWait(4)
+ self.wait()
+
+ return package_links
+
+
+ def _getCipherParams(self, cnl2_form):
+ # Get jk
+ jk_re = self.CNL2_FORMINPUT_REGEX % self.CNL2_JK_KEY
+ vjk = re.findall(jk_re, cnl2_form, re.I)
+
+ # Get crypted
+ crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, cnl2_form, re.I)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/SafelinkingNet.py b/pyload/plugin/crypter/SafelinkingNet.py
new file mode 100644
index 000000000..a949d17b1
--- /dev/null
+++ b/pyload/plugin/crypter/SafelinkingNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+
+
+class SafelinkingNet(Crypter):
+ __name = "SafelinkingNet"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Safelinking.net decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("quareevo", "quareevo@arcor.de")]
+
+
+ SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w.-]+)';"
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ if re.match(self.__pattern, url).group(1) == "d":
+
+ header = self.load(url, just_header=True)
+ if 'location' in header:
+ self.urls = [header['location']]
+ else:
+ self.error(_("Couldn't find forwarded Link"))
+
+ else:
+ postData = {"post-protect": "1"}
+
+ self.html = self.load(url)
+
+ if "link-password" in self.html:
+ postData['link-password'] = self.getPassword()
+
+ if "altcaptcha" in self.html:
+ for _i in xrange(5):
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captchaKey = m.group(1)
+ captcha = SolveMedia(self)
+ captchaProvider = "Solvemedia"
+ else:
+ self.fail(_("Error parsing captcha"))
+
+ response, challenge = captcha.challenge(captchaKey)
+ postData['adcopy_challenge'] = challenge
+ postData['adcopy_response'] = response
+
+ self.html = self.load(url, post=postData)
+ if "The password you entered was incorrect" in self.html:
+ self.fail(_("Incorrect Password"))
+ if not "The CAPTCHA code you entered was wrong" in self.html:
+ break
+
+ pyfile.package().password = ""
+ soup = BeautifulSoup(self.html)
+ scripts = soup.findAll("script")
+ for s in scripts:
+ if "d_links" in s.text:
+ break
+ m = re.search('d_links":(\[.*?\])', s.text)
+ if m:
+ linkDict = json_loads(m.group(1))
+ for link in linkDict:
+ if not "http://" in link['full']:
+ self.urls.append("https://safelinking.net/d/" + link['full'])
+ else:
+ self.urls.append(link['full'])
diff --git a/pyload/plugin/crypter/SecuredIn.py b/pyload/plugin/crypter/SecuredIn.py
new file mode 100644
index 000000000..23bcea8ab
--- /dev/null
+++ b/pyload/plugin/crypter/SecuredIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class SecuredIn(DeadCrypter):
+ __name = "SecuredIn"
+ __type = "crypter"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?secured\.in/download-[\d]+-\w{8}\.html'
+ __config = []
+
+ __description = """Secured.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/crypter/SexuriaCom.py b/pyload/plugin/crypter/SexuriaCom.py
new file mode 100644
index 000000000..7a104ce38
--- /dev/null
+++ b/pyload/plugin/crypter/SexuriaCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class SexuriaCom(Crypter):
+ __name = "SexuriaCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sexuria\.com/(v1/)?(Pornos_Kostenlos_.+?_(\d+)\.html|dl_links_\d+_\d+\.html|id=\d+\&part=\d+\&link=\d+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Sexuria.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("NETHead", "NETHead.AT.gmx.DOT.net")]
+
+
+ PATTERN_SUPPORTED_MAIN = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?Pornos_Kostenlos_.+?_(\d+)\.html', re.I)
+ PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', re.I)
+ PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', re.I)
+ PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', re.I)
+ PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', re.I | re.S)
+ PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', re.I)
+ PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', re.I)
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.pyfile = pyfile
+ self.package = pyfile.package()
+
+ # Get package links
+ package_name, self.links, folder_name, package_pwd = self.decryptLinks(self.pyfile.url)
+ self.packages = [(package_name, self.links, folder_name)]
+
+
+ def decryptLinks(self, url):
+ linklist = []
+ name = self.package.name
+ folder = self.package.folder
+ password = None
+
+ if re.match(self.PATTERN_SUPPORTED_MAIN, url):
+ # Processing main page
+ html = self.load(url)
+ links = re.findall(self.PATTERN_DL_LINK_PAGE, html)
+ for link in links:
+ linklist.append("http://sexuria.com/v1/" + link)
+
+ elif re.match(self.PATTERN_SUPPORTED_REDIRECT, url):
+ # Processing direct redirect link (out.php), redirecting to main page
+ id = re.search(self.PATTERN_SUPPORTED_REDIRECT, url).group('ID')
+ if id:
+ linklist.append("http://sexuria.com/v1/Pornos_Kostenlos_liebe_%s.html" % id)
+
+ elif re.match(self.PATTERN_SUPPORTED_CRYPT, url):
+ # Extract info from main file
+ id = re.search(self.PATTERN_SUPPORTED_CRYPT, url).group('ID')
+ html = self.load("http://sexuria.com/v1/Pornos_Kostenlos_info_%s.html" % id, decode=True)
+
+ title = re.search(self.PATTERN_TITLE, html).group('TITLE').strip()
+ if title:
+ name = folder = title
+ self.logDebug("Package info found, name [%s] and folder [%s]" % (name, folder))
+
+ pwd = re.search(self.PATTERN_PASSWORD, html).group('PWD')
+ if pwd:
+ password = pwd.strip()
+ self.logDebug("Password info [%s] found" % password)
+
+ # Process link (dl_link)
+ html = self.load(url)
+ links = re.findall(self.PATTERN_REDIRECT_LINKS, html)
+ if len(links) == 0:
+ self.LogError("Broken for link %s" % link)
+ else:
+ for link in links:
+ link = link.replace("http://sexuria.com/", "http://www.sexuria.com/")
+ finallink = self.load(link, just_header=True)['location']
+ if not finallink or "sexuria.com/" in finallink:
+ self.LogError("Broken for link %s" % link)
+ else:
+ linklist.append(finallink)
+
+ # Debug log
+ self.logDebug("%d supported links" % len(linklist))
+ for i, link in enumerate(linklist):
+ self.logDebug("Supported link %d, %s" % (i + 1, link))
+
+ return name, linklist, folder, password
diff --git a/pyload/plugin/crypter/ShareLinksBiz.py b/pyload/plugin/crypter/ShareLinksBiz.py
new file mode 100644
index 000000000..8add5214d
--- /dev/null
+++ b/pyload/plugin/crypter/ShareLinksBiz.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+from Crypto.Cipher import AES
+from pyload.plugin.Crypter import Crypter
+
+
+class ShareLinksBiz(Crypter):
+ __name = "ShareLinksBiz"
+ __type = "crypter"
+ __version = "1.14"
+
+ __pattern = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Share-Links.biz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ def setup(self):
+ self.baseUrl = None
+ self.fileId = None
+ self.package = None
+ self.captcha = False
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.initFile(pyfile)
+
+ # Request package
+ url = self.baseUrl + '/' + self.fileId
+ self.html = self.load(url, decode=True)
+
+ # Unblock server (load all images)
+ self.unblockServer()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleErrors()
+
+ # Extract package links
+ package_links = []
+ package_links.extend(self.handleWebLinks())
+ package_links.extend(self.handleContainers())
+ package_links.extend(self.handleCNL2())
+ package_links = set(package_links)
+
+ # Get package info
+ package_name, package_folder = self.getPackageInfo()
+
+ # Pack
+ self.packages = [(package_name, package_links, package_folder)]
+
+
+ def initFile(self, pyfile):
+ url = pyfile.url
+ if 's2l.biz' in url:
+ url = self.load(url, just_header=True)['location']
+ self.baseUrl = "http://www.%s.biz" % re.match(self.__pattern, url).group(1)
+ self.fileId = re.match(self.__pattern, url).group('ID')
+ self.package = pyfile.package()
+
+
+ def isOnline(self):
+ if "No usable content was found" in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+
+ def isPasswordProtected(self):
+ if re.search(r'''<form.*?id="passwordForm".*?>''', self.html):
+ self.logDebug("Links are protected")
+ return True
+ return False
+
+
+ def isCaptchaProtected(self):
+ if '<map id="captchamap"' in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ return False
+
+
+ def unblockServer(self):
+ imgs = re.findall(r"(/template/images/.*?\.gif)", self.html)
+ for img in imgs:
+ self.load(self.baseUrl + img)
+
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ post = {"password": password, 'login': 'Submit form'}
+ url = self.baseUrl + '/' + self.fileId
+ self.html = self.load(url, post=post, decode=True)
+
+
+ def unlockCaptchaProtection(self):
+ # Get captcha map
+ captchaMap = self._getCaptchaMap()
+ self.logDebug("Captcha map with [%d] positions" % len(captchaMap.keys()))
+
+ # Request user for captcha coords
+ m = re.search(r'<img src="/captcha.gif\?d=(.*?)&amp;PHPSESSID=(.*?)&amp;legend=1"', self.html)
+ captchaUrl = self.baseUrl + '/captcha.gif?d=%s&PHPSESSID=%s' % (m.group(1), m.group(2))
+ self.logDebug("Waiting user for correct position")
+ coords = self.decryptCaptcha(captchaUrl, forceUser=True, imgtype="gif", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+
+ # Resolve captcha
+ href = self._resolveCoords(coords, captchaMap)
+ if href is None:
+ self.invalidCaptcha()
+ self.retry(wait_time=5)
+ url = self.baseUrl + href
+ self.html = self.load(url, decode=True)
+
+
+ def _getCaptchaMap(self):
+ mapp = {}
+ for m in re.finditer(r'<area shape="rect" coords="(.*?)" href="(.*?)"', self.html):
+ rect = eval('(' + m.group(1) + ')')
+ href = m.group(2)
+ mapp[rect] = href
+ return mapp
+
+
+ def _resolveCoords(self, coords, captchaMap):
+ x, y = coords
+ for rect, href in captchaMap.iteritems():
+ x1, y1, x2, y2 = rect
+ if (x >= x1 and x <= x2) and (y >= y1 and y <= y2):
+ return href
+
+
+ def handleErrors(self):
+ if "The inserted password was wrong" in self.html:
+ self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
+ self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry"))
+
+ if self.captcha:
+ if "Your choice was wrong" in self.html:
+ self.invalidCaptcha()
+ self.retry(wait_time=5)
+ else:
+ self.correctCaptcha()
+
+
+ def getPackageInfo(self):
+ name = folder = None
+
+ # Extract from web package header
+ title_re = r'<h2><img.*?/>(.*)</h2>'
+ m = re.search(title_re, self.html, re.S)
+ if m:
+ title = m.group(1).strip()
+ if 'unnamed' not in title:
+ name = folder = title
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+
+ # Fallback to defaults
+ if not name or not folder:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+
+ # Return package info
+ return name, folder
+
+
+ def handleWebLinks(self):
+ package_links = []
+ self.logDebug("Handling Web links")
+
+ #@TODO: Gather paginated web links
+ pattern = r'javascript:_get\(\'(.*?)\', \d+, \'\'\)'
+ ids = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Web links" % len(ids))
+ for i, ID in enumerate(ids):
+ try:
+ self.logDebug("Decrypting Web link %d, [%s]" % (i + 1, ID))
+
+ dwLink = self.baseUrl + "/get/lnk/" + ID
+ res = self.load(dwLink)
+
+ code = re.search(r'frm/(\d+)', res).group(1)
+ fwLink = self.baseUrl + "/get/frm/" + code
+ res = self.load(fwLink)
+
+ jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', res, re.S).group(1)
+ jscode = self.js.eval("f = %s" % jscode)
+ jslauncher = "window=''; parent={frames:{Main:{location:{href:''}}},location:''}; %s; parent.frames.Main.location.href"
+
+ dlLink = self.js.eval(jslauncher % jscode)
+
+ self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
+
+ package_links.append(dlLink)
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link [%s], %s" % (ID, detail))
+ return package_links
+
+
+ def handleContainers(self):
+ package_links = []
+ self.logDebug("Handling Container links")
+
+ pattern = r'javascript:_get\(\'(.*?)\', 0, \'(rsdf|ccf|dlc)\'\)'
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Container links" % len(containersLinks))
+ for containerLink in containersLinks:
+ link = "%s/get/%s/%s" % (self.baseUrl, containerLink[1], containerLink[0])
+ package_links.append(link)
+ return package_links
+
+
+ def handleCNL2(self):
+ package_links = []
+ self.logDebug("Handling CNL2 links")
+
+ if '/lib/cnl2/ClicknLoad.swf' in self.html:
+ try:
+ (crypted, jk) = self._getCipherParams()
+ package_links.extend(self._getLinks(crypted, jk))
+ except Exception:
+ self.fail(_("Unable to decrypt CNL2 links"))
+ return package_links
+
+
+ def _getCipherParams(self):
+ # Request CNL2
+ code = re.search(r'ClicknLoad.swf\?code=(.*?)"', self.html).group(1)
+ url = "%s/get/cnl2/%s" % (self.baseUrl, code)
+ res = self.load(url)
+ params = res.split(";;")
+
+ # Get jk
+ strlist = list(params[1].decode('base64'))
+ jk = ''.join(strlist[::-1])
+
+ # Get crypted
+ strlist = list(params[2].decode('base64'))
+ crypted = ''.join(strlist[::-1])
+
+ # Log and return
+ return crypted, jk
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/SharingmatrixCom.py b/pyload/plugin/crypter/SharingmatrixCom.py
new file mode 100644
index 000000000..4224bbd2d
--- /dev/null
+++ b/pyload/plugin/crypter/SharingmatrixCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class SharingmatrixCom(DeadCrypter):
+ __name = "SharingmatrixCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+'
+ __config = []
+
+ __description = """Sharingmatrix.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/SpeedLoadOrg.py b/pyload/plugin/crypter/SpeedLoadOrg.py
new file mode 100644
index 000000000..32677e9ef
--- /dev/null
+++ b/pyload/plugin/crypter/SpeedLoadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class SpeedLoadOrg(DeadCrypter):
+ __name = "SpeedLoadOrg"
+ __type = "crypter"
+ __version = "0.30"
+
+ __pattern = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
+ __config = []
+
+ __description = """Speedload decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/StealthTo.py b/pyload/plugin/crypter/StealthTo.py
new file mode 100644
index 000000000..b974a169c
--- /dev/null
+++ b/pyload/plugin/crypter/StealthTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class StealthTo(DeadCrypter):
+ __name = "StealthTo"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?stealth\.to/folder/.+'
+ __config = []
+
+ __description = """Stealth.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
diff --git a/pyload/plugin/crypter/TnyCz.py b/pyload/plugin/crypter/TnyCz.py
new file mode 100644
index 000000000..8de611a8e
--- /dev/null
+++ b/pyload/plugin/crypter/TnyCz.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+import re
+
+
+class TnyCz(SimpleCrypter):
+ __name = "TnyCz"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?tny\.cz/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Tny.cz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<title>(?P<N>.+) - .+</title>'
+
+
+ def getLinks(self):
+ m = re.search(r'<a id=\'save_paste\' href="(.+save\.php\?hash=.+)">', self.html)
+ return re.findall(".+", self.load(m.group(1), decode=True)) if m else None
diff --git a/pyload/plugin/crypter/TrailerzoneInfo.py b/pyload/plugin/crypter/TrailerzoneInfo.py
new file mode 100644
index 000000000..cad464d47
--- /dev/null
+++ b/pyload/plugin/crypter/TrailerzoneInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class TrailerzoneInfo(DeadCrypter):
+ __name = "TrailerzoneInfo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?trailerzone\.info/.+'
+ __config = []
+
+ __description = """TrailerZone.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/crypter/TurbobitNet.py b/pyload/plugin/crypter/TurbobitNet.py
new file mode 100644
index 000000000..d00380d71
--- /dev/null
+++ b/pyload/plugin/crypter/TurbobitNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import json_loads
+
+
+class TurbobitNet(SimpleCrypter):
+ __name = "TurbobitNet"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Turbobit.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'src=\'/js/lib/grid/icon/folder.png\'> <span>(?P<N>.+?)</span>'
+
+
+ def _getLinks(self, id, page=1):
+ gridFile = self.load("http://turbobit.net/downloadfolder/gridFile",
+ get={"rootId": id, "rows": 200, "page": page}, decode=True)
+ grid = json_loads(gridFile)
+
+ if grid['rows']:
+ for i in grid['rows']:
+ yield i['id']
+ for id in self._getLinks(id, page + 1):
+ yield id
+ else:
+ return
+
+
+ def getLinks(self):
+ id = re.match(self.__pattern, self.pyfile.url).group('ID')
+ fixurl = lambda id: "http://turbobit.net/%s.html" % id
+ return map(fixurl, self._getLinks(id))
diff --git a/pyload/plugin/crypter/TusfilesNet.py b/pyload/plugin/crypter/TusfilesNet.py
new file mode 100644
index 000000000..23d305003
--- /dev/null
+++ b/pyload/plugin/crypter/TusfilesNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import math
+import re
+import urlparse
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class TusfilesNet(XFSCrypter):
+ __name = "TusfilesNet"
+ __type = "crypter"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Tusfiles.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ PAGES_PATTERN = r'>\((\d+) \w+\)<'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.tusfiles.net/go/\g<ID>/')]
+
+
+ def loadPage(self, page_n):
+ return self.load(urlparse.urljoin(self.pyfile.url, str(page_n)), decode=True)
+
+
+ def handlePages(self, pyfile):
+ pages = re.search(self.PAGES_PATTERN, self.html)
+ if pages:
+ pages = int(math.ceil(int(pages.group('pages')) / 25.0))
+ else:
+ return
+
+ for p in xrange(2, pages + 1):
+ self.html = self.loadPage(p)
+ self.links += self.getLinks()
diff --git a/pyload/plugin/crypter/UlozTo.py b/pyload/plugin/crypter/UlozTo.py
new file mode 100644
index 000000000..f2f6745c2
--- /dev/null
+++ b/pyload/plugin/crypter/UlozTo.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class UlozTo(Crypter):
+ __name = "UlozTo"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(m|soubory)/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Uloz.to folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_PATTERN = r'<ul class="profile_files">(.*?)</ul>'
+ LINK_PATTERN = r'<br /><a href="/(.+?)">.+?</a>'
+ NEXT_PAGE_PATTERN = r'<a class="next " href="/(.+?)">&nbsp;</a>'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ new_links = []
+ for i in xrange(1, 100):
+ self.logInfo(_("Fetching links from page %i") % i)
+ m = re.search(self.FOLDER_PATTERN, html, re.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ new_links.extend(re.findall(self.LINK_PATTERN, m.group(1)))
+ m = re.search(self.NEXT_PAGE_PATTERN, html)
+ if m:
+ html = self.load("http://ulozto.net/" + m.group(1))
+ else:
+ break
+ else:
+ self.logInfo(_("Limit of 99 pages reached, aborting"))
+
+ if new_links:
+ self.urls = [map(lambda s: "http://ulozto.net/%s" % s, new_links)]
diff --git a/pyload/plugin/crypter/UploadableCh.py b/pyload/plugin/crypter/UploadableCh.py
new file mode 100644
index 000000000..45e57deb2
--- /dev/null
+++ b/pyload/plugin/crypter/UploadableCh.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadableCh(SimpleCrypter):
+ __name = "UploadableCh"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?uploadable\.ch/list/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Uploadable.ch folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
+ NAME_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<N>.+?)</div>'
+ OFFLINE_PATTERN = r'We are sorry... The URL you entered cannot be found on the server.'
+ TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
diff --git a/pyload/plugin/crypter/UploadedTo.py b/pyload/plugin/crypter/UploadedTo.py
new file mode 100644
index 000000000..895579a26
--- /dev/null
+++ b/pyload/plugin/crypter/UploadedTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadedTo(SimpleCrypter):
+ __name = "UploadedTo"
+ __type = "crypter"
+ __version = "0.42"
+
+ __pattern = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<ID>\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """UploadedTo decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ PLAIN_PATTERN = r'<small class="date"><a href="([\w/]+)" onclick='
+ NAME_PATTERN = r'<title>(?P<N>.+?)<'
+
+
+ def getLinks(self):
+ m = re.search(self.PLAIN_PATTERN, self.html)
+ if m is None:
+ self.error(_("PLAIN_PATTERN not found"))
+
+ plain_link = urlparse.urljoin("http://uploaded.net/", m.group(1))
+ return self.load(plain_link).split('\n')[:-1]
diff --git a/pyload/plugin/crypter/WiiReloadedOrg.py b/pyload/plugin/crypter/WiiReloadedOrg.py
new file mode 100644
index 000000000..1fed5e4d2
--- /dev/null
+++ b/pyload/plugin/crypter/WiiReloadedOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class WiiReloadedOrg(DeadCrypter):
+ __name = "WiiReloadedOrg"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+'
+ __config = []
+
+ __description = """Wii-Reloaded.org decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/WuploadCom.py b/pyload/plugin/crypter/WuploadCom.py
new file mode 100644
index 000000000..29e2c97c5
--- /dev/null
+++ b/pyload/plugin/crypter/WuploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class WuploadCom(DeadCrypter):
+ __name = "WuploadCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?wupload\.com/folder/\w+'
+ __config = []
+
+ __description = """Wupload.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/XFileSharingPro.py b/pyload/plugin/crypter/XFileSharingPro.py
new file mode 100644
index 000000000..b78d83262
--- /dev/null
+++ b/pyload/plugin/crypter/XFileSharingPro.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class XFileSharingPro(XFSCrypter):
+ __name = "XFileSharingPro"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """XFileSharingPro dummy folder decrypter plugin for hook"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def _log(self, type, args):
+ msg = " | ".join(str(a).strip() for a in args if a)
+ logger = getattr(self.log, type)
+ logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
+
+
+ def init(self):
+ super(XFileSharingPro, self).init()
+
+ self.__pattern = self.core.pluginManager.crypterPlugins[self.getClassName()]['pattern']
+
+ self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower()
+ self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
+
+ account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
+
+ if account and account.canUse():
+ self.account = account
+
+ elif self.account:
+ self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
+
+ else:
+ return
+
+ self.user, data = self.account.selectAccount()
+ self.req = self.account.getAccountRequest(self.user)
+ self.premium = self.account.isPremium(self.user)
diff --git a/pyload/plugin/crypter/XupPl.py b/pyload/plugin/crypter/XupPl.py
new file mode 100644
index 000000000..6dd9c3df8
--- /dev/null
+++ b/pyload/plugin/crypter/XupPl.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class XupPl(Crypter):
+ __name = "XupPl"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:[^/]*\.)?xup\.pl/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Xup.pl decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
+
+
+ def decrypt(self, pyfile):
+ header = self.load(pyfile.url, just_header=True)
+ if 'location' in header:
+ self.urls = [header['location']]
+ else:
+ self.fail(_("Unable to find link"))
diff --git a/pyload/plugin/crypter/YoutubeComFolder.py b/pyload/plugin/crypter/YoutubeComFolder.py
new file mode 100644
index 000000000..6873c9148
--- /dev/null
+++ b/pyload/plugin/crypter/YoutubeComFolder.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class YoutubeComFolder(Crypter):
+ __name = "YoutubeComFolder"
+ __type = "crypter"
+ __version = "1.01"
+
+ __pattern = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True ),
+ ("likes" , "bool", "Grab user (channel) liked videos" , False),
+ ("favorites" , "bool", "Grab user (channel) favorite videos", False),
+ ("uploads" , "bool", "Grab channel unplaylisted videos" , True )]
+
+ __description = """Youtube.com channel & playlist decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_KEY = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
+
+
+ def api_response(self, ref, req):
+ req.update({"key": self.API_KEY})
+ url = urlparse.urljoin("https://www.googleapis.com/youtube/v3/", ref)
+ html = self.load(url, get=req)
+ return json_loads(html)
+
+
+ def getChannel(self, user):
+ channels = self.api_response("channels", {"part": "id,snippet,contentDetails", "forUsername": user, "maxResults": "50"})
+ if channels['items']:
+ channel = channels['items'][0]
+ return {"id": channel['id'],
+ "title": channel['snippet']['title'],
+ "relatedPlaylists": channel['contentDetails']['relatedPlaylists'],
+ "user": user} #: One lone channel for user?
+
+
+ def getPlaylist(self, p_id):
+ playlists = self.api_response("playlists", {"part": "snippet", "id": p_id})
+ if playlists['items']:
+ playlist = playlists['items'][0]
+ return {"id": p_id,
+ "title": playlist['snippet']['title'],
+ "channelId": playlist['snippet']['channelId'],
+ "channelTitle": playlist['snippet']['channelTitle']}
+
+
+ def _getPlaylists(self, id, token=None):
+ req = {"part": "id", "maxResults": "50", "channelId": id}
+ if token:
+ req.update({"pageToken": token})
+
+ playlists = self.api_response("playlists", req)
+
+ for playlist in playlists['items']:
+ yield playlist['id']
+
+ if "nextPageToken" in playlists:
+ for item in self._getPlaylists(id, playlists['nextPageToken']):
+ yield item
+
+
+ def getPlaylists(self, ch_id):
+ return map(self.getPlaylist, self._getPlaylists(ch_id))
+
+
+ def _getVideosId(self, id, token=None):
+ req = {"part": "contentDetails", "maxResults": "50", "playlistId": id}
+ if token:
+ req.update({"pageToken": token})
+
+ playlist = self.api_response("playlistItems", req)
+
+ for item in playlist['items']:
+ yield item['contentDetails']['videoId']
+
+ if "nextPageToken" in playlist:
+ for item in self._getVideosId(id, playlist['nextPageToken']):
+ yield item
+
+
+ def getVideosId(self, p_id):
+ return list(self._getVideosId(p_id))
+
+
+ def decrypt(self, pyfile):
+ m = re.match(self.__pattern, pyfile.url)
+ m_id = m.group('ID')
+ m_type = m.group('TYPE')
+
+ if m_type == "user":
+ self.logDebug("Url recognized as Channel")
+ user = m_id
+ channel = self.getChannel(user)
+
+ if channel:
+ playlists = self.getPlaylists(channel['id'])
+ self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), channel['title']))
+
+ relatedplaylist = {p_name: self.getPlaylist(p_id) for p_name, p_id in channel['relatedPlaylists'].iteritems()}
+ self.logDebug("Channel's related playlists found = %s" % relatedplaylist.keys())
+
+ relatedplaylist['uploads']['title'] = "Unplaylisted videos"
+ relatedplaylist['uploads']['checkDups'] = True #: checkDups flag
+
+ for p_name, p_data in relatedplaylist.iteritems():
+ if self.getConfig(p_name):
+ p_data['title'] += " of " + user
+ playlists.append(p_data)
+ else:
+ playlists = []
+ else:
+ self.logDebug("Url recognized as Playlist")
+ playlists = [self.getPlaylist(m_id)]
+
+ if not playlists:
+ self.fail(_("No playlist available"))
+
+ addedvideos = []
+ urlize = lambda x: "https://www.youtube.com/watch?v=" + x
+ for p in playlists:
+ p_name = p['title']
+ p_videos = self.getVideosId(p['id'])
+ p_folder = fs_join(self.config.get("general", "download_folder"), p['channelTitle'], p_name)
+ self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
+
+ if not p_videos:
+ continue
+ elif "checkDups" in p:
+ p_urls = [urlize(v_id) for v_id in p_videos if v_id not in addedvideos]
+ self.logDebug("%s video\s available on playlist \"%s\" after duplicates cleanup" % (len(p_urls), p_name))
+ else:
+ p_urls = map(urlize, p_videos)
+
+ self.packages.append((p_name, p_urls, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
+
+ addedvideos.extend(p_videos)
diff --git a/pyload/plugin/crypter/__init__.py b/pyload/plugin/crypter/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/crypter/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/extractor/SevenZip.py b/pyload/plugin/extractor/SevenZip.py
new file mode 100644
index 000000000..cf5f7bfb8
--- /dev/null
+++ b/pyload/plugin/extractor/SevenZip.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+
+from pyload.plugin.extractor.UnRar import ArchiveError, CRCError, PasswordError, UnRar, renice
+from pyload.utils import fs_encode, fs_join
+
+
+class SevenZip(UnRar):
+ __name = "SevenZip"
+ __type = "extractor"
+ __version = "0.11"
+
+ __description = """7-Zip extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Michael Nowak" , ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ CMD = "7z"
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = ""
+
+ EXTENSIONS = [".7z", ".xz", ".zip", ".gz", ".gzip", ".tgz", ".bz2", ".bzip2",
+ ".tbz2", ".tbz", ".tar", ".wim", ".swm", ".lzma", ".rar", ".cab",
+ ".arj", ".z", ".taz", ".cpio", ".rpm", ".deb", ".lzh", ".lha",
+ ".chm", ".chw", ".hxs", ".iso", ".msi", ".doc", ".xls", ".ppt",
+ ".dmg", ".xar", ".hfs", ".exe", ".ntfs", ".fat", ".vhd", ".mbr",
+ ".squashfs", ".cramfs", ".scap"]
+
+
+ #@NOTE: there are some more uncovered 7z formats
+ re_filelist = re.compile(r'([\d\:]+)\s+([\d\:]+)\s+([\w\.]+)\s+(\d+)\s+(\d+)\s+(.+)')
+ re_wrongpwd = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I)
+ re_wrongcrc = re.compile(r'CRC Failed|Can not open file', re.I)
+ re_version = re.compile(r'7-Zip\s(?:\[64\]\s)?(\d+\.\d+)', re.I)
+
+
+ @classmethod
+ def isUsable(cls):
+ if os.name == "nt":
+ cls.CMD = os.path.join(pypath, "7z.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ else:
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+
+ m = cls.re_version.search(out)
+ cls.VERSION = m.group(1) if m else '(version unknown)'
+
+ return True
+
+
+ def verify(self, password):
+ # 7z can't distinguish crc and pw error in test
+ p = self.call_cmd("l", "-slt", fs_encode(self.filename))
+ out, err = p.communicate()
+
+ if self.re_wrongpwd.search(out):
+ raise PasswordError
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+
+ def check(self, password):
+ p = self.call_cmd("l", "-slt", fs_encode(self.filename))
+ out, err = p.communicate()
+
+ # check if output or error macthes the 'wrong password'-Regexp
+ if self.re_wrongpwd.search(out):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(out):
+ raise CRCError(_("Header protected"))
+
+
+ def repair(self):
+ return False
+
+
+ def extract(self, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_cmd(command, '-o' + self.out, fs_encode(self.filename), password=password)
+
+ renice(p.pid, self.renice)
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if err:
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ elif self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ else: #: raise error if anything is on stderr
+ raise ArchiveError(err)
+
+ if p.returncode > 1:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ self.files = self.list(password)
+
+
+ def list(self, password=None):
+ command = "l" if self.fullpath else "l"
+
+ p = self.call_cmd(command, fs_encode(self.filename), password=password)
+ out, err = p.communicate()
+
+ if "Can not open" in err:
+ raise ArchiveError(_("Cannot open file"))
+
+ if p.returncode > 1:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ result = set()
+ for groups in self.re_filelist.findall(out):
+ f = groups[-1].strip()
+ result.add(fs_join(self.out, f))
+
+ return list(result)
+
+
+ def call_cmd(self, command, *xargs, **kwargs):
+ args = []
+
+ # overwrite flag
+ if self.overwrite:
+ args.append("-y")
+
+ # set a password
+ if "password" in kwargs and kwargs['password']:
+ args.append("-p%s" % kwargs['password'])
+ else:
+ args.append("-p-")
+
+ #@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
+ call = [self.CMD, command] + args + list(xargs)
+
+ self.manager.logDebug(" ".join(call))
+
+ p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return p
diff --git a/pyload/plugin/extractor/UnRar.py b/pyload/plugin/extractor/UnRar.py
new file mode 100644
index 000000000..83821cdc1
--- /dev/null
+++ b/pyload/plugin/extractor/UnRar.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+
+from glob import glob
+from string import digits
+
+from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError
+from pyload.utils import fs_decode, fs_encode, fs_join
+
+
+def renice(pid, value):
+ if value and os.name != "nt":
+ try:
+ subprocess.Popen(["renice", str(value), str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
+
+ except Exception:
+ pass
+
+
+class UnRar(Extractor):
+ __name = "UnRar"
+ __type = "extractor"
+ __version = "1.20"
+
+ __description = """Rar extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz", "immenz@gmx.net")]
+
+
+ CMD = "unrar"
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = ""
+ EXTENSIONS = [".rar"]
+
+
+ re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?', re.I)
+
+ re_filefixed = re.compile(r'Building (.+)')
+ re_filelist = re.compile(r'^(.)(\s*[\w\.\-]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.M | re.I)
+
+ re_wrongpwd = re.compile(r'password', re.I)
+ re_wrongcrc = 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
+ def isUsable(cls):
+ if os.name == "nt":
+ try:
+ cls.CMD = os.path.join(pypath, "RAR.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ cls.NAME = "RAR"
+ cls.REPAIR = True
+
+ except OSError:
+ cls.CMD = os.path.join(pypath, "UnRAR.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ else:
+ try:
+ p = subprocess.Popen(["rar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ cls.NAME = "RAR"
+ cls.REPAIR = True
+
+ except OSError: #: fallback to unrar
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+
+ m = cls.re_version.search(out)
+ cls.VERSION = m.group(1) if m else '(version unknown)'
+
+ return True
+
+
+ @classmethod
+ def isMultipart(cls, filename):
+ return cls.re_multipart.search(filename) is not None
+
+
+ def verify(self, password):
+ p = self.call_cmd("t", "-v", fs_encode(self.filename), password=password)
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+
+ def check(self, password):
+ p = self.call_cmd("l", "-v", fs_encode(self.filename), password=password)
+ out, err = p.communicate()
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ # output only used to check if passworded files are present
+ for attr in self.re_filelist.findall(out):
+ if attr[0].startswith("*"):
+ raise PasswordError
+
+
+ def repair(self):
+ p = self.call_cmd("rc", fs_encode(self.filename))
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+ if err or p.returncode:
+ return False
+ return True
+
+
+ def _progress(self, process):
+ s = ""
+ while True:
+ c = process.stdout.read(1)
+ # quit loop on eof
+ if not c:
+ break
+ # reading a percentage sign -> set progress and restart
+ if c == '%':
+ self.notifyProgress(int(s))
+ s = ""
+ # not reading a digit -> therefore restart
+ elif c not in digits:
+ s = ""
+ # add digit to progressstring
+ else:
+ s += c
+
+
+ def extract(self, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_cmd(command, fs_encode(self.filename), self.out, password=password)
+
+ renice(p.pid, self.renice)
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if err:
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ elif self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ else: #: raise error if anything is on stderr
+ raise ArchiveError(err)
+
+ if p.returncode:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ self.files = self.list(password)
+
+
+ def getDeleteFiles(self):
+ dir, name = os.path.split(self.filename)
+
+ # actually extracted file
+ files = [self.filename]
+
+ # eventually Multipart Files
+ files.extend(fs_join(dir, os.path.basename(file)) for file in filter(self.isMultipart, os.listdir(dir))
+ if re.sub(self.re_multipart, ".rar", name) == re.sub(self.re_multipart, ".rar", file))
+
+ return files
+
+
+ def list(self, password=None):
+ command = "vb" if self.fullpath else "lb"
+
+ p = self.call_cmd(command, "-v", fs_encode(self.filename), password=password)
+ out, err = p.communicate()
+
+ if "Cannot open" in err:
+ raise ArchiveError(_("Cannot open file"))
+
+ if err.strip(): #: only log error at this point
+ self.manager.logError(err.strip())
+
+ result = set()
+ if not self.fullpath and self.VERSION.startswith('5'):
+ # NOTE: Unrar 5 always list full path
+ for f in fs_decode(out).splitlines():
+ f = fs_join(self.out, os.path.basename(f.strip()))
+ if os.path.isfile(f):
+ result.add(fs_join(self.out, os.path.basename(f)))
+ else:
+ for f in fs_decode(out).splitlines():
+ f = f.strip()
+ result.add(fs_join(self.out, f))
+
+ return list(result)
+
+
+ def call_cmd(self, command, *xargs, **kwargs):
+ args = []
+
+ # overwrite flag
+ if self.overwrite:
+ args.append("-o+")
+ else:
+ args.append("-o-")
+ if self.delete != 'No':
+ args.append("-or")
+
+ for word in self.excludefiles:
+ args.append("-x'%s'" % word.strip())
+
+ # assume yes on all queries
+ args.append("-y")
+
+ # set a password
+ if "password" in kwargs and kwargs['password']:
+ args.append("-p%s" % kwargs['password'])
+ else:
+ args.append("-p-")
+
+ 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)
+
+ self.manager.logDebug(" ".join(call))
+
+ p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return p
diff --git a/pyload/plugin/extractor/UnZip.py b/pyload/plugin/extractor/UnZip.py
new file mode 100644
index 000000000..8bfd63480
--- /dev/null
+++ b/pyload/plugin/extractor/UnZip.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import sys
+import zipfile
+
+from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError
+from pyload.utils import fs_encode
+
+
+class UnZip(Extractor):
+ __name = "UnZip"
+ __type = "extractor"
+ __version = "1.12"
+
+ __description = """Zip extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ EXTENSIONS = [".zip", ".zip64"]
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = "(python %s.%s.%s)" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
+
+
+ @classmethod
+ def isUsable(cls):
+ return sys.version_info[:2] >= (2, 6)
+
+
+ def list(self, password=None):
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ z.setpassword(password)
+ return z.namelist()
+
+
+ def check(self, password):
+ pass
+
+
+ def verify(self):
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ badfile = z.testzip()
+
+ if badfile:
+ raise CRCError(badfile)
+ else:
+ raise PasswordError
+
+
+ def extract(self, password=None):
+ try:
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ z.setpassword(password)
+
+ badfile = z.testzip()
+
+ if badfile:
+ raise CRCError(badfile)
+ else:
+ z.extractall(self.out)
+
+ except (zipfile.BadZipfile, zipfile.LargeZipFile), e:
+ raise ArchiveError(e)
+
+ except RuntimeError, e:
+ if "encrypted" in e:
+ raise PasswordError
+ else:
+ raise ArchiveError(e)
+ else:
+ self.files = z.namelist()
diff --git a/pyload/plugin/extractor/__init__.py b/pyload/plugin/extractor/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/extractor/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/hook/AlldebridCom.py b/pyload/plugin/hook/AlldebridCom.py
new file mode 100644
index 000000000..141f86779
--- /dev/null
+++ b/pyload/plugin/hook/AlldebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class AlldebridCom(MultiHook):
+ __name = "AlldebridCom"
+ __type = "hook"
+ __version = "0.16"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Alldebrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip()
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/BypassCaptcha.py b/pyload/plugin/hook/BypassCaptcha.py
new file mode 100644
index 000000000..8e8a27d8c
--- /dev/null
+++ b/pyload/plugin/hook/BypassCaptcha.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class BypassCaptchaException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+
+ def getCode(self):
+ return self.err
+
+
+ def __str__(self):
+ return "<BypassCaptchaException %s>" % self.err
+
+
+ def __repr__(self):
+ return "<BypassCaptchaException %s>" % self.err
+
+
+class BypassCaptcha(Hook):
+ __name = "BypassCaptcha"
+ __type = "hook"
+ __version = "0.06"
+
+ __config = [("force", "bool", "Force BC even if client is connected", False),
+ ("passkey", "password", "Passkey", "")]
+
+ __description = """Send captchas to BypassCaptcha.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("Godofdream", "soilfcition@gmail.com"),
+ ("zoidberg" , "zoidberg@mujmail.cz")]
+
+
+ PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32"
+
+ SUBMIT_URL = "http://bypasscaptcha.com/upload.php"
+ RESPOND_URL = "http://bypasscaptcha.com/check_value.php"
+ GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php"
+
+
+ def getCredits(self):
+ res = getURL(self.GETCREDITS_URL, post={"key": self.getConfig('passkey')})
+
+ data = dict(x.split(' ', 1) for x in res.splitlines())
+ return int(data['Left'])
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ res = req.load(self.SUBMIT_URL,
+ post={'vendor_key': self.PYLOAD_KEY,
+ 'key': self.getConfig('passkey'),
+ 'gen_task_id': "1",
+ 'file': (pycurl.FORM_FILE, captcha)},
+ multipart=True)
+ finally:
+ req.close()
+
+ data = dict(x.split(' ', 1) for x in res.splitlines())
+ if not data or "Value" not in data:
+ raise BypassCaptchaException(res)
+
+ result = data['Value']
+ ticket = data['TaskId']
+ self.logDebug("Result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+
+ def respond(self, ticket, success):
+ try:
+ res = getURL(self.RESPOND_URL, post={"task_id": ticket, "key": self.getConfig('passkey'),
+ "cv": 1 if success else 0})
+ except BadHeader, e:
+ self.logError(_("Could not send response"), e)
+
+
+ def captchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig('passkey'):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.data['service'] = self.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your %s account has not enough credits") % self.getClassName())
+
+
+ def captchaCorrect(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ self.respond(task.data['ticket'], True)
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ self.respond(task.data['ticket'], False)
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except BypassCaptchaException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugin/hook/Captcha9Kw.py b/pyload/plugin/hook/Captcha9Kw.py
new file mode 100644
index 000000000..9ceab4b2b
--- /dev/null
+++ b/pyload/plugin/hook/Captcha9Kw.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+import time
+
+from base64 import b64encode
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+
+from pyload.plugin.Hook import Hook, threaded
+
+
+class Captcha9Kw(Hook):
+ __name = "Captcha9Kw"
+ __type = "hook"
+ __version = "0.28"
+
+ __config = [("ssl" , "bool" , "Use HTTPS" , True),
+ ("force" , "bool" , "Force captcha resolving even if client is connected" , True),
+ ("confirm" , "bool" , "Confirm Captcha (cost +6 credits)" , False),
+ ("captchaperhour", "int" , "Captcha per hour" , "9999"),
+ ("captchapermin" , "int" , "Captcha per minute" , "9999"),
+ ("prio" , "int" , "Priority (max 10)(cost +0 -> +10 credits)" , "0"),
+ ("queue" , "int" , "Max. Queue (max 999)" , "50"),
+ ("hoster_options", "string" , "Hoster options (format: pluginname:prio=1:selfsolfe=1:confirm=1:timeout=900|...)", "ShareonlineBiz:prio=0:timeout=999 | UploadedTo:prio=0:timeout=999"),
+ ("selfsolve" , "bool" , "Selfsolve (manually solve your captcha in your 9kw client if active)" , "0"),
+ ("passkey" , "password", "API key" , ""),
+ ("timeout" , "int" , "Timeout in seconds (min 60, max 3999)" , "900")]
+
+ __description = """Send captchas to 9kw.eu"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "http://www.9kw.eu/index.cgi"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL,
+ get={'apikey': self.getConfig('passkey'),
+ 'pyload': "1",
+ 'source': "pyload",
+ 'action': "usercaptchaguthaben"})
+
+ if res.isdigit():
+ self.logInfo(_("%s credits left") % res)
+ credits = self.info['credits'] = int(res)
+ return credits
+ else:
+ self.logError(res)
+ return 0
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ try:
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+
+ except IOError, e:
+ self.logError(e)
+ return
+
+ pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
+ option = {'min' : 2,
+ 'max' : 50,
+ 'phrase' : 0,
+ 'numeric' : 0,
+ 'case_sensitive': 0,
+ 'math' : 0,
+ 'prio' : min(max(self.getConfig('prio'), 0), 10),
+ 'confirm' : self.getConfig('confirm'),
+ 'timeout' : min(max(self.getConfig('timeout'), 300), 3999),
+ 'selfsolve' : self.getConfig('selfsolve'),
+ 'cph' : self.getConfig('captchaperhour'),
+ 'cpm' : self.getConfig('captchapermin')}
+
+ for opt in str(self.getConfig('hoster_options').split('|')):
+
+ details = map(str.strip, opt.split(':'))
+
+ if not details or details[0].lower() != pluginname.lower():
+ continue
+
+ for d in details:
+ hosteroption = d.split("=")
+
+ if len(hosteroption) < 2 or not hosteroption[1].isdigit():
+ continue
+
+ o = hosteroption[0].lower()
+ if o in option:
+ option[o] = hosteroption[1]
+
+ break
+
+ post_data = {'apikey' : self.getConfig('passkey'),
+ 'prio' : option['prio'],
+ 'confirm' : option['confirm'],
+ 'maxtimeout' : option['timeout'],
+ 'selfsolve' : option['selfsolve'],
+ 'captchaperhour': option['cph'],
+ 'captchapermin' : option['cpm'],
+ 'case-sensitive': option['case_sensitive'],
+ 'min_len' : option['min'],
+ 'max_len' : option['max'],
+ 'phrase' : option['phrase'],
+ 'numeric' : option['numeric'],
+ 'math' : option['math'],
+ 'oldsource' : pluginname,
+ 'pyload' : "1",
+ 'source' : "pyload",
+ 'base64' : "1",
+ 'mouse' : 1 if task.isPositional() else 0,
+ 'file-upload-01': b64encode(data),
+ 'action' : "usercaptchaupload"}
+
+ for _i in xrange(5):
+ try:
+ res = getURL(self.API_URL, post=post_data)
+ except BadHeader, e:
+ time.sleep(3)
+ else:
+ if res and res.isdigit():
+ break
+ else:
+ self.logError(_("Bad upload: %s") % res)
+ return
+
+ self.logDebug(_("NewCaptchaID ticket: %s") % res, task.captchaFile)
+
+ task.data['ticket'] = res
+
+ for _i in xrange(int(self.getConfig('timeout') / 5)):
+ result = getURL(self.API_URL,
+ get={'apikey': self.getConfig('passkey'),
+ 'id' : res,
+ 'pyload': "1",
+ 'info' : "1",
+ 'source': "pyload",
+ 'action': "usercaptchacorrectdata"})
+
+ if not result or result == "NO DATA":
+ time.sleep(5)
+ else:
+ break
+ else:
+ self.logDebug("Could not send request: %s" % res)
+ result = None
+
+ self.logInfo(_("Captcha result for ticket %s: %s") % (res, result))
+
+ task.setResult(result)
+
+
+ def captchaTask(self, task):
+ if not task.isTextual() and not task.isPositional():
+ return
+
+ if not self.getConfig('passkey'):
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return
+
+ credits = self.getCredits()
+
+ if not credits:
+ self.logError(_("Your captcha 9kw.eu account has not enough credits"))
+ return
+
+ queue = min(self.getConfig('queue'), 999)
+ timeout = min(max(self.getConfig('timeout'), 300), 3999)
+ pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
+
+ for _i in xrange(5):
+ servercheck = getURL("http://www.9kw.eu/grafik/servercheck.txt")
+ if queue < re.search(r'queue=(\d+)', servercheck).group(1):
+ break
+
+ time.sleep(10)
+ else:
+ self.fail(_("Too many captchas in queue"))
+
+ for opt in str(self.getConfig('hoster_options').split('|')):
+ details = map(str.strip, opt.split(':'))
+
+ if not details or details[0].lower() != pluginname.lower():
+ continue
+
+ for d in details:
+ hosteroption = d.split("=")
+
+ if len(hosteroption) > 1 \
+ and hosteroption[0].lower() == 'timeout' \
+ and hosteroption[1].isdigit():
+ timeout = int(hosteroption[1])
+
+ break
+
+ task.handler.append(self)
+
+ task.setWaiting(timeout)
+
+ self._processCaptcha(task)
+
+
+ def _captchaResponse(self, task, correct):
+ type = "correct" if correct else "refund"
+
+ if 'ticket' not in task.data:
+ self.logDebug("No CaptchaID for %s request (task: %s)" % (type, task))
+ return
+
+ passkey = self.getConfig('passkey')
+
+ for _i in xrange(3):
+ res = getURL(self.API_URL,
+ get={'action' : "usercaptchacorrectback",
+ 'apikey' : passkey,
+ 'api_key': passkey,
+ 'correct': "1" if correct else "2",
+ 'pyload' : "1",
+ 'source' : "pyload",
+ 'id' : task.data['ticket']})
+
+ self.logDebug("Request %s: %s" % (type, res))
+
+ if res == "OK":
+ break
+
+ time.sleep(5)
+ else:
+ self.logDebug("Could not send %s request: %s" % (type, res))
+
+
+ def captchaCorrect(self, task):
+ self._captchaResponse(task, True)
+
+
+ def captchaInvalid(self, task):
+ self._captchaResponse(task, False)
diff --git a/pyload/plugin/hook/CaptchaBrotherhood.py b/pyload/plugin/hook/CaptchaBrotherhood.py
new file mode 100644
index 000000000..75c7d55b7
--- /dev/null
+++ b/pyload/plugin/hook/CaptchaBrotherhood.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import StringIO
+import pycurl
+import time
+import urllib
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class CaptchaBrotherhoodException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+
+ def getCode(self):
+ return self.err
+
+
+ def __str__(self):
+ return "<CaptchaBrotherhoodException %s>" % self.err
+
+
+ def __repr__(self):
+ return "<CaptchaBrotherhoodException %s>" % self.err
+
+
+class CaptchaBrotherhood(Hook):
+ __name = "CaptchaBrotherhood"
+ __type = "hook"
+ __version = "0.08"
+
+ __config = [("username", "str", "Username", ""),
+ ("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Password", "")]
+
+ __description = """Send captchas to CaptchaBrotherhood.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://www.captchabrotherhood.com/"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL + "askCredits.aspx",
+ get={"username": self.getConfig('username'), "password": self.getConfig('passkey')})
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException(res)
+ else:
+ credits = int(res[3:])
+ self.logInfo(_("%d credits left") % credits)
+ self.info['credits'] = credits
+ return credits
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ try:
+ img = Image.open(captcha)
+ output = StringIO.StringIO()
+ self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode)
+ if img.format in ("GIF", "JPEG"):
+ img.save(output, img.format)
+ else:
+ if img.mode != "RGB":
+ img = img.convert("RGB")
+ img.save(output, "JPEG")
+ data = output.getvalue()
+ output.close()
+ except Exception, e:
+ raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e)
+
+ req = getRequest()
+
+ url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL,
+ urllib.urlencode({'username' : self.getConfig('username'),
+ 'password' : self.getConfig('passkey'),
+ 'captchaSource': "pyLoad",
+ 'timeout' : "80"}))
+
+ req.c.setopt(pycurl.URL, url)
+ req.c.setopt(pycurl.POST, 1)
+ req.c.setopt(pycurl.POSTFIELDS, data)
+ req.c.setopt(pycurl.HTTPHEADER, ["Content-Type: text/html"])
+
+ try:
+ req.c.perform()
+ res = req.getResponse()
+ except Exception, e:
+ raise CaptchaBrotherhoodException("Submit captcha image failed")
+
+ req.close()
+
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException(res[1])
+
+ ticket = res[3:]
+
+ for _i in xrange(15):
+ time.sleep(5)
+ res = self.api_response("askCaptchaResult", ticket)
+ if res.startswith("OK-answered"):
+ return ticket, res[12:]
+
+ raise CaptchaBrotherhoodException("No solution received in time")
+
+
+ def api_response(self, api, ticket):
+ res = getURL("%s%s.aspx" % (self.API_URL, api),
+ get={"username": self.getConfig('username'),
+ "password": self.getConfig('passkey'),
+ "captchaID": ticket})
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException("Unknown response: %s" % res)
+
+ return res
+
+
+ def captchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig('username') or not self.getConfig('passkey'):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return False
+
+ if self.getCredits() > 10:
+ task.handler.append(self)
+ task.data['service'] = self.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+ else:
+ self.logInfo(_("Your CaptchaBrotherhood Account has not enough credits"))
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ res = self.api_response("complainCaptcha", task.data['ticket'])
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except CaptchaBrotherhoodException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugin/hook/DeathByCaptcha.py b/pyload/plugin/hook/DeathByCaptcha.py
new file mode 100644
index 000000000..670807bf5
--- /dev/null
+++ b/pyload/plugin/hook/DeathByCaptcha.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import pycurl
+import re
+import time
+
+from base64 import b64encode
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class DeathByCaptchaException(Exception):
+ DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials',
+ 'invalid-credentials': 'Access denied, check your credentials',
+ 'banned': 'Access denied, account is suspended',
+ 'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA',
+ 'invalid-captcha': 'CAPTCHA is not a valid image',
+ 'service-overload': 'CAPTCHA was rejected due to service overload, try again later',
+ 'invalid-request': 'Invalid request',
+ 'timed-out': 'No CAPTCHA solution received in time'}
+
+
+ def __init__(self, err):
+ self.err = err
+
+
+ def getCode(self):
+ return self.err
+
+
+ def getDesc(self):
+ if self.err in self.DBC_ERRORS.keys():
+ return self.DBC_ERRORS[self.err]
+ else:
+ return self.err
+
+
+ def __str__(self):
+ return "<DeathByCaptchaException %s>" % self.err
+
+
+ def __repr__(self):
+ return "<DeathByCaptchaException %s>" % self.err
+
+
+class DeathByCaptcha(Hook):
+ __name = "DeathByCaptcha"
+ __type = "hook"
+ __version = "0.06"
+
+ __config = [("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force DBC even if client is connected", False)]
+
+ __description = """Send captchas to DeathByCaptcha.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://api.dbcapi.me/api/"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def api_response(self, api="captcha", post=False, multipart=False):
+ req = getRequest()
+ req.c.setopt(pycurl.HTTPHEADER, ["Accept: application/json", "User-Agent: pyLoad %s" % self.core.version])
+
+ if post:
+ if not isinstance(post, dict):
+ post = {}
+ post.update({"username": self.getConfig('username'),
+ "password": self.getConfig('passkey')})
+
+ res = None
+ try:
+ json = req.load("%s%s" % (self.API_URL, api),
+ post=post,
+ multipart=multipart)
+ self.logDebug(json)
+ res = json_loads(json)
+
+ if "error" in res:
+ raise DeathByCaptchaException(res['error'])
+ elif "status" not in res:
+ raise DeathByCaptchaException(str(res))
+
+ except BadHeader, e:
+ if 403 == e.code:
+ raise DeathByCaptchaException('not-logged-in')
+ elif 413 == e.code:
+ raise DeathByCaptchaException('invalid-captcha')
+ elif 503 == e.code:
+ raise DeathByCaptchaException('service-overload')
+ elif e.code in (400, 405):
+ raise DeathByCaptchaException('invalid-request')
+ else:
+ raise
+
+ finally:
+ req.close()
+
+ return res
+
+
+ def getCredits(self):
+ res = self.api_response("user", True)
+
+ if 'is_banned' in res and res['is_banned']:
+ raise DeathByCaptchaException('banned')
+ elif 'balance' in res and 'rate' in res:
+ self.info.update(res)
+ else:
+ raise DeathByCaptchaException(res)
+
+
+ def getStatus(self):
+ res = self.api_response("status", False)
+
+ if 'is_service_overloaded' in res and res['is_service_overloaded']:
+ raise DeathByCaptchaException('service-overload')
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ #@NOTE: Workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig('passkey')):
+ multipart = True
+ data = (pycurl.FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = "base64:" + b64encode(data)
+
+ res = self.api_response("captcha", {"captchafile": data}, multipart)
+
+ if "captcha" not in res:
+ raise DeathByCaptchaException(res)
+ ticket = res['captcha']
+
+ for _i in xrange(24):
+ time.sleep(5)
+ res = self.api_response("captcha/%d" % ticket, False)
+ if res['text'] and res['is_correct']:
+ break
+ else:
+ raise DeathByCaptchaException('timed-out')
+
+ result = res['text']
+ self.logDebug("Result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+
+ def captchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig('username') or not self.getConfig('passkey'):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return False
+
+ try:
+ self.getStatus()
+ self.getCredits()
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+ return False
+
+ balance, rate = self.info['balance'], self.info['rate']
+ self.logInfo(_("Account balance"),
+ _("US$%.3f (%d captchas left at %.2f cents each)") % (balance / 100,
+ balance // rate, rate))
+
+ if balance > rate:
+ task.handler.append(self)
+ task.data['service'] = self.getClassName()
+ task.setWaiting(180)
+ self._processCaptcha(task)
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ try:
+ res = self.api_response("captcha/%d/report" % task.data['ticket'], True)
+
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+
+ except Exception, e:
+ self.logError(e)
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except DeathByCaptchaException, e:
+ task.error = e.getCode()
+ self.logError(e.getDesc())
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugin/hook/DebridItaliaCom.py b/pyload/plugin/hook/DebridItaliaCom.py
new file mode 100644
index 000000000..e15f7d6ec
--- /dev/null
+++ b/pyload/plugin/hook/DebridItaliaCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class DebridItaliaCom(MultiHook):
+ __name = "DebridItaliaCom"
+ __type = "hook"
+ __version = "0.12"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Debriditalia.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell" , "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ def getHosters(self):
+ return self.getURL("http://debriditalia.com/api.php", get={'hosts': ""}).replace('"', '').split(',')
diff --git a/pyload/plugin/hook/EasybytezCom.py b/pyload/plugin/hook/EasybytezCom.py
new file mode 100644
index 000000000..68b4d96d2
--- /dev/null
+++ b/pyload/plugin/hook/EasybytezCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class EasybytezCom(MultiHook):
+ __name = "EasybytezCom"
+ __type = "hook"
+ __version = "0.07"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """EasyBytez.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+
+ req = self.account.getAccountRequest(user)
+ html = req.load("http://www.easybytez.com")
+
+ return re.search(r'</textarea>\s*Supported sites:(.*)', html).group(1).split(',')
diff --git a/pyload/plugin/hook/ExpertDecoders.py b/pyload/plugin/hook/ExpertDecoders.py
new file mode 100644
index 000000000..0727c0407
--- /dev/null
+++ b/pyload/plugin/hook/ExpertDecoders.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import pycurl
+import uuid
+
+from base64 import b64encode
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class ExpertDecoders(Hook):
+ __name = "ExpertDecoders"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Access key", "")]
+
+ __description = """Send captchas to expertdecoders.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org" ),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://www.fasttypers.org/imagepost.ashx"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL, post={"key": self.getConfig('passkey'), "action": "balance"})
+
+ if res.isdigit():
+ self.logInfo(_("%s credits left") % res)
+ self.info['credits'] = credits = int(res)
+ return credits
+ else:
+ self.logError(res)
+ return 0
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ task.data['ticket'] = ticket = uuid.uuid4()
+ result = None
+
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+
+ req = getRequest()
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ result = req.load(self.API_URL,
+ post={'action' : "upload",
+ 'key' : self.getConfig('passkey'),
+ 'file' : b64encode(data),
+ 'gen_task_id': ticket})
+ finally:
+ req.close()
+
+ self.logDebug("Result %s : %s" % (ticket, result))
+ task.setResult(result)
+
+
+ def captchaTask(self, task):
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig('passkey'):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.setWaiting(100)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
+
+
+ def captchaInvalid(self, task):
+ if "ticket" in task.data:
+
+ try:
+ res = getURL(self.API_URL,
+ post={'action': "refund", 'key': self.getConfig('passkey'), 'gen_task_id': task.data['ticket']})
+ self.logInfo(_("Request refund"), res)
+
+ except BadHeader, e:
+ self.logError(_("Could not send refund request"), e)
diff --git a/pyload/plugin/hook/FastixRu.py b/pyload/plugin/hook/FastixRu.py
new file mode 100644
index 000000000..6c71384c8
--- /dev/null
+++ b/pyload/plugin/hook/FastixRu.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class FastixRu(MultiHook):
+ __name = "FastixRu"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Fastix.ru hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://fastix.ru/api_v2",
+ get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y",
+ 'sub' : "allowed_sources"})
+ host_list = json_loads(html)
+ host_list = host_list['allow']
+ return host_list
diff --git a/pyload/plugin/hook/FreeWayMe.py b/pyload/plugin/hook/FreeWayMe.py
new file mode 100644
index 000000000..fd1017ba9
--- /dev/null
+++ b/pyload/plugin/hook/FreeWayMe.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class FreeWayMe(MultiHook):
+ __name = "FreeWayMe"
+ __type = "hook"
+ __version = "0.15"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """FreeWay.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def getHosters(self):
+ # Get account data
+ if not self.account or not self.account.canUse():
+ hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
+ else:
+ self.logDebug("AccountInfo available - Get HosterList with User Pass")
+ (user, data) = self.account.selectAccount()
+ hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3, "user": user, "pass": data['password']}).replace("\"", "").strip()
+
+ self.logDebug("hosters: %s" % hostis)
+ return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/ImageTyperz.py b/pyload/plugin/hook/ImageTyperz.py
new file mode 100644
index 000000000..3f0147776
--- /dev/null
+++ b/pyload/plugin/hook/ImageTyperz.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import pycurl
+import re
+
+from base64 import b64encode
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class ImageTyperzException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+
+ def getCode(self):
+ return self.err
+
+
+ def __str__(self):
+ return "<ImageTyperzException %s>" % self.err
+
+
+ def __repr__(self):
+ return "<ImageTyperzException %s>" % self.err
+
+
+class ImageTyperz(Hook):
+ __name = "ImageTyperz"
+ __type = "hook"
+ __version = "0.06"
+
+ __config = [("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force IT even if client is connected", False)]
+
+ __description = """Send captchas to ImageTyperz.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx"
+ RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx"
+ GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx"
+
+
+ def getCredits(self):
+ res = getURL(self.GETCREDITS_URL,
+ post={'action': "REQUESTBALANCE",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey')})
+
+ if res.startswith('ERROR'):
+ raise ImageTyperzException(res)
+
+ try:
+ balance = float(res)
+ except Exception:
+ raise ImageTyperzException("Invalid response")
+
+ self.logInfo(_("Account balance: $%s left") % res)
+ return balance
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ #@NOTE: Workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig('passkey')):
+ multipart = True
+ data = (pycurl.FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = b64encode(data)
+
+ res = req.load(self.SUBMIT_URL,
+ post={'action': "UPLOADCAPTCHA",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey'), "file": data},
+ multipart=multipart)
+ finally:
+ req.close()
+
+ if res.startswith("ERROR"):
+ raise ImageTyperzException(res)
+ else:
+ data = res.split('|')
+ if len(data) == 2:
+ ticket, result = data
+ else:
+ raise ImageTyperzException("Unknown response: %s" % res)
+
+ return ticket, result
+
+
+ def captchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig('username') or not self.getConfig('passkey'):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.data['service'] = self.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your %s account has not enough credits") % self.getClassName())
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ res = getURL(self.RESPOND_URL,
+ post={'action': "SETBADIMAGE",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey'),
+ 'imageid': task.data['ticket']})
+
+ if res == "SUCCESS":
+ self.logInfo(_("Bad captcha solution received, requested refund"))
+ else:
+ self.logError(_("Bad captcha solution received, refund request failed"), res)
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except ImageTyperzException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugin/hook/LinkdecrypterCom.py b/pyload/plugin/hook/LinkdecrypterCom.py
new file mode 100644
index 000000000..d3f5c0afc
--- /dev/null
+++ b/pyload/plugin/hook/LinkdecrypterCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class LinkdecrypterCom(MultiHook):
+ __name = "LinkdecrypterCom"
+ __type = "hook"
+ __version = "1.04"
+
+ __config = [("activated" , "bool" , "Activated" , True ),
+ ("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Linkdecrypter.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def getCrypters(self):
+ return re.search(r'>Supported\(\d+\)</b>: <i>(.[\w.\-, ]+)',
+ self.getURL("http://linkdecrypter.com/", decode=True).replace("(g)", "")).group(1).split(', ')
diff --git a/pyload/plugin/hook/LinksnappyCom.py b/pyload/plugin/hook/LinksnappyCom.py
new file mode 100644
index 000000000..e09021d6d
--- /dev/null
+++ b/pyload/plugin/hook/LinksnappyCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class LinksnappyCom(MultiHook):
+ __name = "LinksnappyCom"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Linksnappy.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://gen.linksnappy.com/lseAPI.php", get={'act': "FILEHOSTS"})
+ json_data = json_loads(json_data)
+
+ return json_data['return'].keys()
diff --git a/pyload/plugin/hook/MegaDebridEu.py b/pyload/plugin/hook/MegaDebridEu.py
new file mode 100644
index 000000000..41abce37b
--- /dev/null
+++ b/pyload/plugin/hook/MegaDebridEu.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MegaDebridEu(MultiHook):
+ __name = "MegaDebridEu"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Mega-debrid.eu hook plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ def getHosters(self):
+ reponse = self.getURL("http://www.mega-debrid.eu/api.php", get={'action': "getHosters"})
+ json_data = json_loads(reponse)
+
+ if json_data['response_code'] == "ok":
+ host_list = [element[0] for element in json_data['hosters']]
+ else:
+ self.logError(_("Unable to retrieve hoster list"))
+ host_list = []
+
+ return host_list
diff --git a/pyload/plugin/hook/MegaRapidoNet.py b/pyload/plugin/hook/MegaRapidoNet.py
new file mode 100644
index 000000000..4650d8b6d
--- /dev/null
+++ b/pyload/plugin/hook/MegaRapidoNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MegaRapidoNet(MultiHook):
+ __name = "MegaRapidoNet"
+ __type = "hook"
+ __version = "0.02"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """MegaRapido.net hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def getHosters(self):
+ hosters = {'1fichier' : [],#leave it there are so many possible addresses?
+ '1st-files' : ['1st-files.com'],
+ '2shared' : ['2shared.com'],
+ '4shared' : ['4shared.com', '4shared-china.com'],
+ 'asfile' : ['http://asfile.com/'],
+ 'bitshare' : ['bitshare.com'],
+ 'brupload' : ['brupload.net'],
+ 'crocko' : ['crocko.com','easy-share.com'],
+ 'dailymotion' : ['dailymotion.com'],
+ 'depfile' : ['depfile.com'],
+ 'depositfiles': ['depositfiles.com', 'dfiles.eu'],
+ 'dizzcloud' : ['dizzcloud.com'],
+ 'dl.dropbox' : [],
+ 'extabit' : ['extabit.com'],
+ 'extmatrix' : ['extmatrix.com'],
+ 'facebook' : [],
+ 'file4go' : ['file4go.com'],
+ 'filecloud' : ['filecloud.io','ifile.it','mihd.net'],
+ 'filefactory' : ['filefactory.com'],
+ 'fileom' : ['fileom.com'],
+ 'fileparadox' : ['fileparadox.in'],
+ 'filepost' : ['filepost.com', 'fp.io'],
+ 'filerio' : ['filerio.in','filerio.com','filekeen.com'],
+ 'filesflash' : ['filesflash.com'],
+ 'firedrive' : ['firedrive.com', 'putlocker.com'],
+ 'flashx' : [],
+ 'freakshare' : ['freakshare.net', 'freakshare.com'],
+ 'gigasize' : ['gigasize.com'],
+ 'hipfile' : ['hipfile.com'],
+ 'junocloud' : ['junocloud.me'],
+ 'letitbit' : ['letitbit.net','shareflare.net'],
+ 'mediafire' : ['mediafire.com'],
+ 'mega' : ['mega.co.nz'],
+ 'megashares' : ['megashares.com'],
+ 'metacafe' : ['metacafe.com'],
+ 'netload' : ['netload.in'],
+ 'oboom' : ['oboom.com'],
+ 'rapidgator' : ['rapidgator.net'],
+ 'rapidshare' : ['rapidshare.com'],
+ 'rarefile' : ['rarefile.net'],
+ 'ryushare' : ['ryushare.com'],
+ 'sendspace' : ['sendspace.com'],
+ 'turbobit' : ['turbobit.net', 'unextfiles.com'],
+ 'uploadable' : ['uploadable.ch'],
+ 'uploadbaz' : ['uploadbaz.com'],
+ 'uploaded' : ['uploaded.to', 'uploaded.net', 'ul.to'],
+ 'uploadhero' : ['uploadhero.com'],
+ 'uploading' : ['uploading.com'],
+ 'uptobox' : ['uptobox.com'],
+ 'xvideos' : ['xvideos.com'],
+ 'youtube' : ['youtube.com']}
+
+ hoster_list = []
+
+ for item in hosters.itervalues():
+ hoster_list.extend(item)
+
+ return hoster_list
diff --git a/pyload/plugin/hook/MultihostersCom.py b/pyload/plugin/hook/MultihostersCom.py
new file mode 100644
index 000000000..5cb2396ee
--- /dev/null
+++ b/pyload/plugin/hook/MultihostersCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hook.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "hook"
+ __version = "0.02"
+
+ __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed", "bool" , "Revert to standard download if download fails", False),
+ ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
+
+ __description = """Multihosters.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
diff --git a/pyload/plugin/hook/MultishareCz.py b/pyload/plugin/hook/MultishareCz.py
new file mode 100644
index 000000000..39817d7ce
--- /dev/null
+++ b/pyload/plugin/hook/MultishareCz.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MultishareCz(MultiHook):
+ __name = "MultishareCz"
+ __type = "hook"
+ __version = "0.07"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """MultiShare.cz hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="(.+?)"></td>\s*<td class="stav">[^>]*?alt="OK"'
+
+
+ def getHosters(self):
+ html = self.getURL("http://www.multishare.cz/monitoring/")
+ return re.findall(self.HOSTER_PATTERN, html)
diff --git a/pyload/plugin/hook/MyfastfileCom.py b/pyload/plugin/hook/MyfastfileCom.py
new file mode 100644
index 000000000..b6573e56d
--- /dev/null
+++ b/pyload/plugin/hook/MyfastfileCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(MultiHook):
+ __name = "MyfastfileCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Myfastfile.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://myfastfile.com/api.php", get={'hosts': ""}, decode=True)
+ self.logDebug("JSON data", json_data)
+ json_data = json_loads(json_data)
+
+ return json_data['hosts']
diff --git a/pyload/plugin/hook/NoPremiumPl.py b/pyload/plugin/hook/NoPremiumPl.py
new file mode 100644
index 000000000..527413a88
--- /dev/null
+++ b/pyload/plugin/hook/NoPremiumPl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class NoPremiumPl(MultiHook):
+ __name = "NoPremiumPl"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """NoPremium.pl hook plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+
+ def getHosters(self):
+ hostings = json_loads(self.getURL("https://www.nopremium.pl/clipboard.php?json=3").strip())
+ hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"]
+
+ self.logDebug(hostings_domains)
+
+ return hostings_domains
diff --git a/pyload/plugin/hook/OverLoadMe.py b/pyload/plugin/hook/OverLoadMe.py
new file mode 100644
index 000000000..5fc04e6f4
--- /dev/null
+++ b/pyload/plugin/hook/OverLoadMe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class OverLoadMe(MultiHook):
+ __name = "OverLoadMe"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Over-Load.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://api.over-load.me/hoster.php",
+ get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip()
+ self.logDebug("Hosterlist", html)
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/PremiumTo.py b/pyload/plugin/hook/PremiumTo.py
new file mode 100644
index 000000000..361f072a3
--- /dev/null
+++ b/pyload/plugin/hook/PremiumTo.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class PremiumTo(MultiHook):
+ __name = "PremiumTo"
+ __type = "hook"
+ __version = "0.08"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Premium.to hook plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org" ),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://premium.to/api/hosters.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ return [x.strip() for x in html.replace("\"", "").split(";")]
diff --git a/pyload/plugin/hook/PremiumizeMe.py b/pyload/plugin/hook/PremiumizeMe.py
new file mode 100644
index 000000000..1e4a58592
--- /dev/null
+++ b/pyload/plugin/hook/PremiumizeMe.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class PremiumizeMe(MultiHook):
+ __name = "PremiumizeMe"
+ __type = "hook"
+ __version = "0.17"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Premiumize.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def getHosters(self):
+ # Get account data
+ user, data = self.account.selectAccount()
+
+ # Get supported hosters list from premiumize.me using the
+ # json API v1 (see https://secure.premiumize.me/?show=api)
+ answer = self.getURL("https://api.premiumize.me/pm-api/v1.php",
+ get={'method': "hosterlist", 'params[login]': user, 'params[pass]': data['password']})
+ data = json_loads(answer)
+
+ # If account is not valid thera are no hosters available
+ if data['status'] != 200:
+ return []
+
+ # Extract hosters from json file
+ return data['result']['hosterlist']
diff --git a/pyload/plugin/hook/PutdriveCom.py b/pyload/plugin/hook/PutdriveCom.py
new file mode 100644
index 000000000..126eb86e8
--- /dev/null
+++ b/pyload/plugin/hook/PutdriveCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hook.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "hook"
+ __version = "0.01"
+
+ __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed", "bool" , "Revert to standard download if download fails", False),
+ ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
+
+ __description = """Putdrive.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hook/RPNetBiz.py b/pyload/plugin/hook/RPNetBiz.py
new file mode 100644
index 000000000..d9d3e6177
--- /dev/null
+++ b/pyload/plugin/hook/RPNetBiz.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RPNetBiz(MultiHook):
+ __name = "RPNetBiz"
+ __type = "hook"
+ __version = "0.14"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """RPNet.biz hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def getHosters(self):
+ # Get account data
+ user, data = self.account.selectAccount()
+
+ res = self.getURL("https://premium.rpnet.biz/client_api.php",
+ get={'username': user, 'password': data['password'], 'action': "showHosterList"})
+ hoster_list = json_loads(res)
+
+ # If account is not valid thera are no hosters available
+ if 'error' in hoster_list:
+ return []
+
+ # Extract hosters from json file
+ return hoster_list['hosters']
diff --git a/pyload/plugin/hook/RapideoPl.py b/pyload/plugin/hook/RapideoPl.py
new file mode 100644
index 000000000..1761659db
--- /dev/null
+++ b/pyload/plugin/hook/RapideoPl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RapideoPl(MultiHook):
+ __name = "RapideoPl"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Rapideo.pl hook plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+
+ def getHosters(self):
+ hostings = json_loads(self.getURL("https://www.rapideo.pl/clipboard.php?json=3").strip())
+ hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"]
+
+ self.logDebug(hostings_domains)
+
+ return hostings_domains
diff --git a/pyload/plugin/hook/RealdebridCom.py b/pyload/plugin/hook/RealdebridCom.py
new file mode 100644
index 000000000..916737cd8
--- /dev/null
+++ b/pyload/plugin/hook/RealdebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RealdebridCom(MultiHook):
+ __name = "RealdebridCom"
+ __type = "hook"
+ __version = "0.46"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Real-Debrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/RehostTo.py b/pyload/plugin/hook/RehostTo.py
new file mode 100644
index 000000000..087701c5b
--- /dev/null
+++ b/pyload/plugin/hook/RehostTo.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RehostTo(MultiHook):
+ __name = "RehostTo"
+ __type = "hook"
+ __version = "0.50"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Rehost.to hook plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+ html = self.getURL("http://rehost.to/api.php",
+ get={'cmd' : "get_supported_och_dl",
+ 'long_ses': self.account.getAccountInfo(user)['session']})
+ return [x.strip() for x in html.replace("\"", "").split(",")]
diff --git a/pyload/plugin/hook/SimplyPremiumCom.py b/pyload/plugin/hook/SimplyPremiumCom.py
new file mode 100644
index 000000000..f095cd7e6
--- /dev/null
+++ b/pyload/plugin/hook/SimplyPremiumCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SimplyPremiumCom(MultiHook):
+ __name = "SimplyPremiumCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Simply-Premium.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://www.simply-premium.com/api/hosts.php", get={'format': "json", 'online': 1})
+ json_data = json_loads(json_data)
+
+ host_list = [element['regex'] for element in json_data['result']]
+
+ return host_list
diff --git a/pyload/plugin/hook/SimplydebridCom.py b/pyload/plugin/hook/SimplydebridCom.py
new file mode 100644
index 000000000..d831bf532
--- /dev/null
+++ b/pyload/plugin/hook/SimplydebridCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SimplydebridCom(MultiHook):
+ __name = "SimplydebridCom"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Simply-Debrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://simply-debrid.com/api.php", get={'list': 1})
+ return [x.strip() for x in html.rstrip(';').replace("\"", "").split(";")]
diff --git a/pyload/plugin/hook/SmoozedCom.py b/pyload/plugin/hook/SmoozedCom.py
new file mode 100644
index 000000000..caecb8bc2
--- /dev/null
+++ b/pyload/plugin/hook/SmoozedCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SmoozedCom(MultiHook):
+ __name = "SmoozedCom"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Smoozed.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+ return self.account.getAccountInfo(user)["hosters"]
diff --git a/pyload/plugin/hook/UnrestrictLi.py b/pyload/plugin/hook/UnrestrictLi.py
new file mode 100644
index 000000000..124a5109a
--- /dev/null
+++ b/pyload/plugin/hook/UnrestrictLi.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class UnrestrictLi(MultiHook):
+ __name = "UnrestrictLi"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("history" , "bool" , "Delete History" , False)]
+
+ __description = """Unrestrict.li hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://unrestrict.li/api/jdownloader/hosts.php", get={'format': "json"})
+ json_data = json_loads(json_data)
+
+ return [element['host'] for element in json_data['result']]
diff --git a/pyload/plugin/hook/XFileSharingPro.py b/pyload/plugin/hook/XFileSharingPro.py
new file mode 100644
index 000000000..3c305c74b
--- /dev/null
+++ b/pyload/plugin/hook/XFileSharingPro.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hook import Hook
+
+
+class XFileSharingPro(Hook):
+ __name = "XFileSharingPro"
+ __type = "hook"
+ __version = "0.37"
+
+ __config = [("activated" , "bool", "Activated" , True ),
+ ("use_hoster_list" , "bool", "Load listed hosters only" , False),
+ ("use_crypter_list", "bool", "Load listed crypters only" , False),
+ ("use_builtin_list", "bool", "Load built-in plugin list" , True ),
+ ("hoster_list" , "str" , "Hoster list (comma separated)" , "" ),
+ ("crypter_list" , "str" , "Crypter list (comma separated)", "" )]
+
+ __description = """Load XFileSharingPro based hosters and crypter which don't need a own plugin to run"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+ regexp = {'hoster' : (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)',
+ r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:embed-)?\w+'),
+ 'crypter': (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+',
+ r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:user|folder)s?/\w+')}
+
+ HOSTER_BUILTIN = [#WORKING HOSTERS:
+ "backin.net", "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "fileparadox.in",
+ "filevice.com", "hostingbulk.com", "junkyvideo.com", "linestorage.com", "ravishare.com", "ryushare.com",
+ "salefiles.com", "sendmyway.com", "sharesix.com", "thefile.me", "verzend.be", "xvidstage.com",
+ # NOT TESTED:
+ "101shared.com", "4upfiles.com", "filemaze.ws", "filenuke.com", "linkzhost.com", "mightyupload.com",
+ "rockdizfile.com", "sharebeast.com", "sharerepo.com", "shareswift.com", "uploadbaz.com", "uploadc.com",
+ "vidbull.com", "worldbytez.com", "zalaa.com", "zomgupload.com",
+ # NOT WORKING:
+ "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"]
+ CRYPTER_BUILTIN = ["junocloud.me", "rapidfileshare.net"]
+
+
+ # def pluginConfigChanged(self, plugin, name, value):
+ # self.loadPattern()
+
+
+ def activate(self):
+ self.loadPattern()
+
+
+ def loadPattern(self):
+ use_builtin_list = self.getConfig("use_builtin_list")
+
+ for type in ("hoster", "crypter"):
+ every_plugin = not self.getConfig('use_%s_list' % type)
+
+ if every_plugin:
+ self.logInfo(_("Handling any %s I can!") % type)
+ pattern = self.regexp[type][0]
+ else:
+ plugins = self.getConfig('%s_list' % type)
+ plugin_set = set(plugins.replace(' ', '').replace('\\', '').replace('|', ',').replace(';', ',').lower().split(','))
+
+ if use_builtin_list:
+ plugin_set |= set(x.lower() for x in getattr(self, "%s_BUILTIN" % type.upper()))
+
+ plugin_set -= set(('', u''))
+
+ if not plugin_set:
+ self.logInfo(_("No %s to handle") % type)
+ self._unload(type)
+ return
+
+ match_list = '|'.join(sorted(plugin_set))
+
+ len_match_list = len(plugin_set)
+ self.logInfo(_("Handling %d %s%s: %s") % (len_match_list,
+ type,
+ "" if len_match_list == 1 else "s",
+ match_list.replace('|', ', ')))
+
+ pattern = self.regexp[type][1] % match_list.replace('.', '\.')
+
+ dict = self.core.pluginManager.plugins[type]['XFileSharingPro']
+ dict['pattern'] = pattern
+ dict['re'] = re.compile(pattern)
+
+ self.logDebug("Loaded %s pattern: %s" % (type, pattern))
+
+
+ def _unload(self, type):
+ dict = self.core.pluginManager.plugins[type]['XFileSharingPro']
+ dict['pattern'] = r'^unmatchable$'
+ dict['re'] = re.compile(dict['pattern'])
+
+
+ def deactivate(self):
+ # self.unloadHoster("BasePlugin")
+ for type in ("hoster", "crypter"):
+ self._unload(type, "XFileSharingPro")
+
+
+ # def downloadFailed(self, pyfile):
+ # if pyfile.pluginname == "BasePlugin" \
+ # and pyfile.hasStatus("failed") \
+ # and not self.getConfig('use_hoster_list') \
+ # and self.unloadHoster("BasePlugin"):
+ # self.logDebug("Unloaded XFileSharingPro from BasePlugin")
+ # pyfile.setStatus("queued")
diff --git a/pyload/plugin/hook/ZeveraCom.py b/pyload/plugin/hook/ZeveraCom.py
new file mode 100644
index 000000000..e52ec8eed
--- /dev/null
+++ b/pyload/plugin/hook/ZeveraCom.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class ZeveraCom(MultiHook):
+ __name = "ZeveraCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Zevera.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ def getHosters(self):
+ html = self.account.api_response(pyreq.getHTTPRequest(timeout=120), cmd="gethosters")
+ return [x.strip() for x in html.split(",")]
diff --git a/pyload/plugin/hook/__init__.py b/pyload/plugin/hook/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/hook/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/hoster/AlldebridCom.py b/pyload/plugin/hoster/AlldebridCom.py
new file mode 100644
index 000000000..7474e62e8
--- /dev/null
+++ b/pyload/plugin/hoster/AlldebridCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class AlldebridCom(MultiHoster):
+ __name = "AlldebridCom"
+ __type = "hoster"
+ __version = "0.46"
+
+ __pattern = r'https?://(?:www\.|s\d+\.)?alldebrid\.com/dl/[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Alldebrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+
+
+ def handlePremium(self, pyfile):
+ password = self.getPassword()
+
+ data = json_loads(self.load("http://www.alldebrid.com/service.php",
+ get={'link': pyfile.url, 'json': "true", 'pw': password}))
+
+ self.logDebug("Json data", data)
+
+ if data['error']:
+ if data['error'] == "This link isn't available on the hoster website.":
+ self.offline()
+ else:
+ self.logWarning(data['error'])
+ self.tempOffline()
+ else:
+ if pyfile.name and not pyfile.name.endswith('.tmp'):
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+ self.link = data['link']
+
+ if self.getConfig('ssl'):
+ self.link = self.link.replace("http://", "https://")
+ else:
+ self.link = self.link.replace("https://", "http://")
diff --git a/pyload/plugin/hoster/AndroidfilehostCom.py b/pyload/plugin/hoster/AndroidfilehostCom.py
new file mode 100644
index 000000000..584001725
--- /dev/null
+++ b/pyload/plugin/hoster/AndroidfilehostCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://www.androidfilehost.com/?fid=95916177934518197
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class AndroidfilehostCom(SimpleHoster):
+ __name = "AndroidfilehostCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?androidfilehost\.com/\?fid=\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Androidfilehost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<br />(?P<N>.*?)</h1>'
+ SIZE_PATTERN = r'<h4>size</h4>\s*<p>(?P<S>[\d.,]+)(?P<U>[\w^_]+)</p>'
+ HASHSUM_PATTERN = r'<h4>(?P<T>.*?)</h4>\s*<p><code>(?P<H>.*?)</code></p>'
+
+ OFFLINE_PATTERN = r'404 not found'
+
+ WAIT_PATTERN = r'users must wait <strong>(\d+) secs'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ wait = re.search(self.WAIT_PATTERN, self.html)
+ self.logDebug("Waiting time: %s seconds" % wait.group(1))
+
+ fid = re.search(r'id="fid" value="(\d+)" />', self.html).group(1)
+ self.logDebug("fid: %s" % fid)
+
+ html = self.load("https://www.androidfilehost.com/libs/otf/mirrors.otf.php",
+ post={'submit': 'submit',
+ 'action': 'getdownloadmirrors',
+ 'fid' : fid},
+ decode=True)
+
+ self.link = re.findall('"url":"(.*?)"', html)[0].replace("\\", "")
+ mirror_host = self.link.split("/")[2]
+
+ self.logDebug("Mirror Host: %s" % mirror_host)
+
+ html = self.load("https://www.androidfilehost.com/libs/otf/stats.otf.php",
+ get={'fid' : fid,
+ 'w' : 'download',
+ 'mirror': mirror_host},
+ decode=True)
diff --git a/pyload/plugin/hoster/BasketbuildCom.py b/pyload/plugin/hoster/BasketbuildCom.py
new file mode 100644
index 000000000..d005eae74
--- /dev/null
+++ b/pyload/plugin/hoster/BasketbuildCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://s.basketbuild.com/filedl/devs?dev=pacman&dl=pacman/falcon/RC-3/pac_falcon-RC-3-20141103.zip
+# https://s.basketbuild.com/filedl/gapps?dl=gapps-gb-20110828-signed.zip
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BasketbuildCom(SimpleHoster):
+ __name = "BasketbuildCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(?:\w\.)?basketbuild\.com/filedl/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """basketbuild.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'File Name:</strong> (?P<N>.+?)<br/>'
+ SIZE_PATTERN = r'File Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'404 - Page Not Found'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ try:
+ link1 = re.search(r'href="(.+dlgate/.+)"', self.html).group(1)
+ self.html = self.load(link1)
+
+ except AttributeError:
+ self.error(_("Hop #1 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link1)
+
+ try:
+ wait = re.search(r'var sec = (\d+)', self.html).group(1)
+ self.logDebug("Wait %s seconds" % wait)
+ self.wait(wait)
+
+ except AttributeError:
+ self.logDebug("No wait time found")
+
+ try:
+ self.link = re.search(r'id="dlLink">\s*<a href="(.+?)"', self.html).group(1)
+
+ except AttributeError:
+ self.error(_("DL-Link not found"))
diff --git a/pyload/plugin/hoster/BayfilesCom.py b/pyload/plugin/hoster/BayfilesCom.py
new file mode 100644
index 000000000..b14e28dcb
--- /dev/null
+++ b/pyload/plugin/hoster/BayfilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class BayfilesCom(DeadHoster):
+ __name = "BayfilesCom"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)'
+ __config = []
+
+ __description = """Bayfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/BezvadataCz.py b/pyload/plugin/hoster/BezvadataCz.py
new file mode 100644
index 000000000..fa29c83d5
--- /dev/null
+++ b/pyload/plugin/hoster/BezvadataCz.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BezvadataCz(SimpleHoster):
+ __name = "BezvadataCz"
+ __type = "hoster"
+ __version = "0.26"
+
+ __pattern = r'http://(?:www\.)?bezvadata\.cz/stahnout/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """BezvaData.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
+ SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
+ OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ # download button
+ m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
+ if m is None:
+ self.error(_("Page 1 URL not found"))
+ url = "http://bezvadata.cz%s" % m.group(1)
+
+ # captcha form
+ self.html = self.load(url)
+ self.checkErrors()
+ for _i in xrange(5):
+ action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
+ if not inputs:
+ self.error(_("FreeForm"))
+
+ m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
+ if m is None:
+ self.error(_("Wrong captcha image"))
+
+ # captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url
+ self.load, proper_load = self.loadcaptcha, self.load
+ try:
+ inputs['captcha'] = self.decryptCaptcha(m.group(1), imgtype='png')
+ finally:
+ self.load = proper_load
+
+ if '<img src="data:image/png;base64' in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("No valid captcha code entered"))
+
+ # download url
+ self.html = self.load("http://bezvadata.cz%s" % action, post=inputs)
+ self.checkErrors()
+ m = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html)
+ if m is None:
+ self.error(_("Page 2 URL not found"))
+ url = "http://bezvadata.cz%s" % m.group(1)
+ self.logDebug("DL URL %s" % url)
+
+ # countdown
+ m = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html)
+ wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 120
+ self.wait(wait_time, False)
+
+ self.link = url
+
+
+ def checkErrors(self):
+ if 'images/button-download-disable.png' in self.html:
+ self.longWait(5 * 60, 24) #: parallel dl limit
+ elif '<div class="infobox' in self.html:
+ self.tempOffline()
+
+ self.info.pop('error', None)
+
+
+ def loadcaptcha(self, data, *args, **kwargs):
+ return data.decode('base64')
diff --git a/pyload/plugin/hoster/BillionuploadsCom.py b/pyload/plugin/hoster/BillionuploadsCom.py
new file mode 100644
index 000000000..e34584868
--- /dev/null
+++ b/pyload/plugin/hoster/BillionuploadsCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class BillionuploadsCom(XFSHoster):
+ __name = "BillionuploadsCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?billionuploads\.com/\w{12}'
+
+ __description = """Billionuploads.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<td class="dofir" title="(?P<N>.+?)"'
+ SIZE_PATTERN = r'<td class="dofir">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
diff --git a/pyload/plugin/hoster/BitshareCom.py b/pyload/plugin/hoster/BitshareCom.py
new file mode 100644
index 000000000..0d26c2d53
--- /dev/null
+++ b/pyload/plugin/hoster/BitshareCom.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BitshareCom(SimpleHoster):
+ __name = "BitshareCom"
+ __type = "hoster"
+ __version = "0.53"
+
+ __pattern = r'http://(?:www\.)?bitshare\.com/(files/)?(?(1)|\?f=)(?P<ID>\w+)(?(1)/(?P<NAME>.+?)\.html)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Bitshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Paul King", ""),
+ ("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ COOKIES = [("bitshare.com", "language_selection", "EN")]
+
+ INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>'
+ OFFLINE_PATTERN = r'[Ff]ile (not available|was deleted|was not found)'
+
+ AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
+ TRAFFIC_USED_UP = r'Your Traffic is used up for today'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if self.premium:
+ self.account.relogin(self.user)
+
+ # File id
+ m = re.match(self.__pattern, pyfile.url)
+ self.file_id = max(m.group('ID1'), m.group('ID2'))
+ self.logDebug("File id is [%s]" % self.file_id)
+
+ # Load main page
+ self.html = self.load(pyfile.url, ref=False, decode=True)
+
+ # Check offline
+ if re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ # Check Traffic used up
+ if re.search(self.TRAFFIC_USED_UP, self.html):
+ self.logInfo(_("Your Traffic is used up for today"))
+ self.wait(30 * 60, True)
+ self.retry()
+
+ # File name
+ m = re.match(self.__pattern, pyfile.url)
+ name1 = m.group('NAME') if m else None
+
+ m = re.search(self.INFO_PATTERN, self.html)
+ name2 = m.group('N') if m else None
+
+ pyfile.name = max(name1, name2)
+
+ # Ajax file id
+ self.ajaxid = re.search(self.AJAXID_PATTERN, self.html).group(1)
+ self.logDebug("File ajax id is [%s]" % self.ajaxid)
+
+ # This may either download our file or forward us to an error page
+ self.link = self.getDownloadUrl()
+
+ if self.checkDownload({"error": ">Error occured<"}):
+ self.retry(5, 5 * 60, "Bitshare host : Error occured")
+
+
+ def getDownloadUrl(self):
+ # Return location if direct download is active
+ if self.premium:
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header:
+ return header['location']
+
+ # Get download info
+ self.logDebug("Getting download info")
+ res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "generateID", "ajaxid": self.ajaxid})
+
+ self.handleErrors(res, ':')
+
+ parts = res.split(":")
+ filetype = parts[0]
+ wait = int(parts[1])
+ captcha = int(parts[2])
+
+ self.logDebug("Download info [type: '%s', waiting: %d, captcha: %d]" % (filetype, wait, captcha))
+
+ # Waiting
+ if wait > 0:
+ self.logDebug("Waiting %d seconds." % wait)
+ if wait < 120:
+ self.wait(wait, False)
+ else:
+ self.wait(wait - 55, True)
+ self.retry()
+
+ # Resolve captcha
+ if captcha == 1:
+ self.logDebug("File is captcha protected")
+ recaptcha = ReCaptcha(self)
+
+ # Try up to 3 times
+ for _i in xrange(3):
+ response, challenge = recaptcha.challenge()
+ res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request" : "validateCaptcha",
+ "ajaxid" : self.ajaxid,
+ "recaptcha_challenge_field": challenge,
+ "recaptcha_response_field" : response})
+ if self.handleCaptchaErrors(res):
+ break
+
+ # Get download URL
+ self.logDebug("Getting download url")
+ res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
+
+ self.handleErrors(res, '#')
+
+ url = res.split("#")[-1]
+
+ return url
+
+
+ def handleErrors(self, res, separator):
+ self.logDebug("Checking response [%s]" % res)
+ if "ERROR:Session timed out" in res:
+ self.retry()
+ elif "ERROR" in res:
+ msg = res.split(separator)[-1]
+ self.fail(msg)
+
+
+ def handleCaptchaErrors(self, res):
+ self.logDebug("Result of captcha resolving [%s]" % res)
+ if "SUCCESS" in res:
+ self.correctCaptcha()
+ return True
+ elif "ERROR:SESSION ERROR" in res:
+ self.retry()
+
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/BoltsharingCom.py b/pyload/plugin/hoster/BoltsharingCom.py
new file mode 100644
index 000000000..10716e695
--- /dev/null
+++ b/pyload/plugin/hoster/BoltsharingCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class BoltsharingCom(DeadHoster):
+ __name = "BoltsharingCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?boltsharing\.com/\w{12}'
+ __config = []
+
+ __description = """Boltsharing.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/CatShareNet.py b/pyload/plugin/hoster/CatShareNet.py
new file mode 100644
index 000000000..fa3c13b3f
--- /dev/null
+++ b/pyload/plugin/hoster/CatShareNet.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CatShareNet(SimpleHoster):
+ __name = "CatShareNet"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?catshare\.net/\w{16}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """CatShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com"),
+ ("prOq", ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ TEXT_ENCODING = True
+
+ INFO_PATTERN = r'<title>(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<'
+ OFFLINE_PATTERN = r'<div class="alert alert-error"'
+
+ IP_BLOCKED_PATTERN = ur'>Nasz serwis wykrył ÅŒe Twój adres IP nie pochodzi z Polski.<'
+ WAIT_PATTERN = r'var\scount\s=\s(\d+);'
+
+ LINK_FREE_PATTERN = r'<form action="(.+?)" method="GET">'
+ LINK_PREMIUM_PATTERN = r'<form action="(.+?)" method="GET">'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.fail(_("Only connections from Polish IP address are allowed"))
+
+ return super(CatShareNet, self).checkErrors()
+
+
+ def handleFree(self, pyfile):
+ recaptcha = ReCaptcha(self)
+
+ response, challenge = recaptcha.challenge()
+ self.html = self.load(pyfile.url,
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/CloudzerNet.py b/pyload/plugin/hoster/CloudzerNet.py
new file mode 100644
index 000000000..c1b3beae8
--- /dev/null
+++ b/pyload/plugin/hoster/CloudzerNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class CloudzerNet(DeadHoster):
+ __name = "CloudzerNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+'
+ __config = []
+
+ __description = """Cloudzer.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("gs", "I-_-I-_-I@web.de"),
+ ("z00nx", "z00nx0@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/CloudzillaTo.py b/pyload/plugin/hoster/CloudzillaTo.py
new file mode 100644
index 000000000..a244b1fae
--- /dev/null
+++ b/pyload/plugin/hoster/CloudzillaTo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CloudzillaTo(SimpleHoster):
+ __name = "CloudzillaTo"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?cloudzilla\.to/share/file/(?P<ID>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Cloudzilla.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'title="(?P<N>.+?)">\1</span> <span class="size">\((?P<S>[\d.]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>File not found...<'
+
+ PASSWORD_PATTERN = r'<div id="pwd_protected">'
+
+
+ def checkErrors(self):
+ m = re.search(self.PASSWORD_PATTERN, self.html)
+ if m:
+ self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
+
+ if re.search(self.PASSWORD_PATTERN, self.html):
+ self.retry(reason="Wrong password")
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load("http://www.cloudzilla.to/generateticket/",
+ post={'file_id': self.info['pattern']['ID'], 'key': self.getPassword()})
+
+ ticket = dict(re.findall(r'<(.+?)>([^<>]+?)</', self.html))
+
+ self.logDebug(ticket)
+
+ if 'error' in ticket:
+ if "File is password protected" in ticket['error']:
+ self.retry(reason="Wrong password")
+ else:
+ self.fail(ticket['error'])
+
+ if 'wait' in ticket:
+ self.wait(ticket['wait'], int(ticket['wait']) > 5)
+
+ self.link = "http://%(server)s/download/%(file_id)s/%(ticket_id)s" % {'server' : ticket['server'],
+ 'file_id' : self.info['pattern']['ID'],
+ 'ticket_id': ticket['ticket_id']}
+
+
+ def handlePremium(self, pyfile):
+ return self.handleFree(pyfile)
diff --git a/pyload/plugin/hoster/CramitIn.py b/pyload/plugin/hoster/CramitIn.py
new file mode 100644
index 000000000..9d8d4960d
--- /dev/null
+++ b/pyload/plugin/hoster/CramitIn.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class CramitIn(XFSHoster):
+ __name = "CramitIn"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?cramit\.in/\w{12}'
+
+ __description = """Cramit.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
+
+ LINK_PATTERN = r'href="(http://cramit\.in/file_download/.*?)"'
diff --git a/pyload/plugin/hoster/CrockoCom.py b/pyload/plugin/hoster/CrockoCom.py
new file mode 100644
index 000000000..3be0c5909
--- /dev/null
+++ b/pyload/plugin/hoster/CrockoCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CrockoCom(SimpleHoster):
+ __name = "CrockoCom"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Crocko hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
+ SIZE_PATTERN = r'<span class="tip1"><span class="inner">(?P<S>[^<]+)</span></span>'
+ OFFLINE_PATTERN = r'<h1>Sorry,<br />the page you\'re looking for <br />isn\'t here.</h1>|File not found'
+
+ CAPTCHA_PATTERN = r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';"
+
+ FORM_PATTERN = r'<form method="post" action="(.+?)">(.*?)</form>'
+ FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?.*?>'
+
+ NAME_REPLACEMENTS = [(r'<.*?>', '')]
+
+
+ def handleFree(self, pyfile):
+ if "You need Premium membership to download this file." in self.html:
+ self.fail(_("You need Premium membership to download this file"))
+
+ for _i in xrange(5):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ url, wait_time = 'http://crocko.com' + m.group(1), int(m.group(2))
+ self.wait(wait_time)
+ self.html = self.load(url)
+ else:
+ break
+
+ m = re.search(self.FORM_PATTERN, self.html, re.S)
+ if m is None:
+ self.error(_("FORM_PATTERN not found"))
+
+ action, form = m.groups()
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
+ self.download(action, post=inputs)
+
+ if self.checkDownload({"captcha": recaptcha.KEY_AJAX_PATTERN}):
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("No valid captcha solution received"))
diff --git a/pyload/plugin/hoster/CyberlockerCh.py b/pyload/plugin/hoster/CyberlockerCh.py
new file mode 100644
index 000000000..b56736a8e
--- /dev/null
+++ b/pyload/plugin/hoster/CyberlockerCh.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class CyberlockerCh(DeadHoster):
+ __name = "CyberlockerCh"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?cyberlocker\.ch/\w+'
+ __config = []
+
+ __description = """Cyberlocker.ch hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/CzshareCom.py b/pyload/plugin/hoster/CzshareCom.py
new file mode 100644
index 000000000..9926c8d74
--- /dev/null
+++ b/pyload/plugin/hoster/CzshareCom.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://czshare.com/5278880/random.bin
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import parseFileSize
+
+
+class CzshareCom(SimpleHoster):
+ __name = "CzshareCom"
+ __type = "hoster"
+ __version = "0.99"
+
+ __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """CZshare.com hoster plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=.*?>(?P<N>[^<]+)</a>'
+ SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[\d .,]+)(?P<U>[\w^_]+)\s*</div>'
+ OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
+
+ SIZE_REPLACEMENTS = [(' ', '')]
+ URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
+
+ CHECK_TRAFFIC = True
+
+ FREE_URL_PATTERN = r'<a href="(.+?)" class="page-download">[^>]*alt="(.+?)" /></a>'
+ FREE_FORM_PATTERN = r'<form action="download\.php" method="post">\s*<img src="captcha\.php" id="captcha" />(.*?)</form>'
+ PREMIUM_FORM_PATTERN = r'<form action="/profi_down\.php" method="post">(.*?)</form>'
+ FORM_INPUT_PATTERN = r'<input[^>]* name="(.+?)" value="(.+?)"[^>]*/>'
+ MULTIDL_PATTERN = r'<p><font color=\'red\'>Z[^<]*PROFI.</font></p>'
+ USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([\d .,]+)(\w+)</strong>\s*</div><!-- .credit -->'
+
+
+ def checkTrafficLeft(self):
+ # check if user logged in
+ m = re.search(self.USER_CREDIT_PATTERN, self.html)
+ if m is None:
+ self.account.relogin(self.user)
+ self.html = self.load(self.pyfile.url, decode=True)
+ m = re.search(self.USER_CREDIT_PATTERN, self.html)
+ if m is None:
+ return False
+
+ # check user credit
+ try:
+ credit = parseFileSize(m.group(1).replace(' ', ''), m.group(2))
+ self.logInfo(_("Premium download for %i KiB of Credit") % (self.pyfile.size / 1024))
+ self.logInfo(_("User %s has %i KiB left") % (self.user, credit / 1024))
+ if credit < self.pyfile.size:
+ self.logInfo(_("Not enough credit to download file: %s") % self.pyfile.name)
+ return False
+ except Exception, e:
+ # let's continue and see what happens...
+ self.logError(e)
+
+ return True
+
+
+ def handlePremium(self, pyfile):
+ # parse download link
+ try:
+ form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.S).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ except Exception, e:
+ self.logError(e)
+ self.resetAccount()
+
+ # download the file, destination is determined by pyLoad
+ self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
+
+
+ def handleFree(self, pyfile):
+ # get free url
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.error(_("FREE_URL_PATTERN not found"))
+
+ parsed_url = "http://sdilej.cz" + m.group(1)
+
+ self.logDebug("PARSED_URL:" + parsed_url)
+
+ # get download ticket and parse html
+ self.html = self.load(parsed_url, decode=True)
+ if re.search(self.MULTIDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+
+ try:
+ form = re.search(self.FREE_FORM_PATTERN, self.html, re.S).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ pyfile.size = int(inputs['size'])
+
+ except Exception, e:
+ self.logError(e)
+ self.error(_("Form"))
+
+ # get and decrypt captcha
+ captcha_url = 'http://sdilej.cz/captcha.php'
+ for _i in xrange(5):
+ inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
+ self.html = self.load(parsed_url, post=inputs, decode=True)
+
+ if u"<li>ZadanÜ ověřovací kód nesouhlasí!</li>" in self.html:
+ self.invalidCaptcha()
+
+ elif re.search(self.MULTIDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("No valid captcha code entered"))
+
+ m = re.search("countdown_number = (\d+);", self.html)
+ self.setWait(int(m.group(1)) if m else 50)
+
+ # download the file, destination is determined by pyLoad
+ self.logDebug("WAIT URL", self.req.lastEffectiveURL)
+
+ m = re.search("free_wait.php\?server=(.*?)&(.*)", self.req.lastEffectiveURL)
+ if m is None:
+ self.error(_("Download URL not found"))
+
+ self.link = "http://%s/download.php?%s" % (m.group(1), m.group(2))
+
+ self.wait()
+
+
+ def checkFile(self, rules={}):
+ # check download
+ check = self.checkDownload({
+ "temp offline" : re.compile(r"^Soubor je do.*asn.* nedostupn.*$"),
+ "credit" : re.compile(r"^Nem.*te dostate.*n.* kredit.$"),
+ "multi-dl" : re.compile(self.MULTIDL_PATTERN),
+ "captcha" : "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
+ })
+
+ if check == "temp offline":
+ self.fail(_("File not available - try later"))
+
+ elif check == "credit":
+ self.resetAccount()
+
+ elif check == "multi-dl":
+ self.longWait(5 * 60, 12)
+
+ elif check == "captcha":
+ self.invalidCaptcha()
+ self.retry()
+
+ return super(CzshareCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/DailymotionCom.py b/pyload/plugin/hoster/DailymotionCom.py
new file mode 100644
index 000000000..3fdac761c
--- /dev/null
+++ b/pyload/plugin/hoster/DailymotionCom.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.datatype.File import statusMap
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+
+
+def getInfo(urls):
+ result = []
+ regex = re.compile(DailymotionCom.__pattern)
+ apiurl = "https://api.dailymotion.com/video/%s"
+ request = {"fields": "access_error,status,title"}
+
+ for url in urls:
+ id = regex.match(url).group('ID')
+ html = getURL(apiurl % id, get=request)
+ info = json_loads(html)
+
+ name = info['title'] + ".mp4" if "title" in info else url
+
+ if "error" in info or info['access_error']:
+ status = "offline"
+ else:
+ status = info['status']
+ if status in ("ready", "published"):
+ status = "online"
+ elif status in ("waiting", "processing"):
+ status = "temp. offline"
+ else:
+ status = "offline"
+
+ result.append((name, 0, statusMap[status], url))
+
+ return result
+
+
+class DailymotionCom(Hoster):
+ __name = "DailymotionCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'https?://(?:www\.)?dailymotion\.com/.*video/(?P<ID>[\w^_]+)'
+ __config = [("quality", "Lowest;LD 144p;LD 240p;SD 384p;HQ 480p;HD 720p;HD 1080p;Highest", "Quality", "Highest")]
+
+ __description = """Dailymotion.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def getStreams(self):
+ streams = []
+
+ for result in re.finditer(r"\"(?P<URL>http:\\/\\/www.dailymotion.com\\/cdn\\/H264-(?P<QF>.*?)\\.*?)\"",
+ self.html):
+ url = result.group('URL')
+ qf = result.group('QF')
+
+ link = url.replace("\\", "")
+ quality = tuple(int(x) for x in qf.split("x"))
+
+ streams.append((quality, link))
+
+ return sorted(streams, key=lambda x: x[0][::-1])
+
+
+ def getQuality(self):
+ q = self.getConfig('quality')
+
+ if q == "Lowest":
+ quality = 0
+ elif q == "Highest":
+ quality = -1
+ else:
+ quality = int(q.rsplit(" ")[1][:-1])
+
+ return quality
+
+
+ def getLink(self, streams, quality):
+ if quality > 0:
+ for x, s in [item for item in enumerate(streams)][::-1]:
+ qf = s[0][1]
+ if qf <= quality:
+ idx = x
+ break
+ else:
+ idx = 0
+ else:
+ idx = quality
+
+ s = streams[idx]
+
+ self.logInfo(_("Download video quality %sx%s") % s[0])
+
+ return s[1]
+
+
+ def checkInfo(self, pyfile):
+ pyfile.name, pyfile.size, pyfile.status, pyfile.url = getInfo([pyfile.url])[0]
+
+ if pyfile.status == 1:
+ self.offline()
+
+ elif pyfile.status == 6:
+ self.tempOffline()
+
+
+ def process(self, pyfile):
+ self.checkInfo(pyfile)
+
+ id = re.match(self.__pattern, pyfile.url).group('ID')
+ self.html = self.load("http://www.dailymotion.com/embed/video/" + id, decode=True)
+
+ streams = self.getStreams()
+ quality = self.getQuality()
+
+ self.download(self.getLink(streams, quality))
diff --git a/pyload/plugin/hoster/DataHu.py b/pyload/plugin/hoster/DataHu.py
new file mode 100644
index 000000000..ba3576d10
--- /dev/null
+++ b/pyload/plugin/hoster/DataHu.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://data.hu/get/6381232/random.bin
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DataHu(SimpleHoster):
+ __name = "DataHu"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?data\.hu/get/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Data.hu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("crash", ""),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
+ OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
+ LINK_FREE_PATTERN = r'<div class="download_box_button"><a href="(.+?)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
diff --git a/pyload/plugin/hoster/DataportCz.py b/pyload/plugin/hoster/DataportCz.py
new file mode 100644
index 000000000..9a7ceda46
--- /dev/null
+++ b/pyload/plugin/hoster/DataportCz.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DataportCz(SimpleHoster):
+ __name = "DataportCz"
+ __type = "hoster"
+ __version = "0.41"
+
+ __pattern = r'http://(?:www\.)?dataport\.cz/file/(.+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dataport.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
+ SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
+
+ CAPTCHA_PATTERN = r'<section id="captcha_bg">\s*<img src="(.*?)"'
+ FREE_SLOTS_PATTERN = ur'Počet volnÜch slotů: <span class="darkblue">(\d+)</span><br />'
+
+
+ def handleFree(self, pyfile):
+ captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
+
+ for _i in xrange(60):
+ action, inputs = self.parseHtmlForm('free_download_form')
+ self.logDebug(action, inputs)
+ if not action or not inputs:
+ self.error(_("free_download_form"))
+
+ if "captchaId" in inputs and inputs['captchaId'] in captchas:
+ inputs['captchaCode'] = captchas[inputs['captchaId']]
+ else:
+ self.error(_("captcha"))
+
+ self.download("http://www.dataport.cz%s" % action, post=inputs)
+
+ check = self.checkDownload({"captcha": 'alert("\u0160patn\u011b opsan\u00fd k\u00f3d z obr\u00e1zu");',
+ "slot" : 'alert("Je n\u00e1m l\u00edto, ale moment\u00e1ln\u011b nejsou'})
+ if check == "captcha":
+ self.error(_("invalid captcha"))
+
+ elif check == "slot":
+ self.logDebug("No free slots - wait 60s and retry")
+ self.wait(60, False)
+ self.html = self.load(pyfile.url, decode=True)
+ continue
+
+ else:
+ break
diff --git a/pyload/plugin/hoster/DateiTo.py b/pyload/plugin/hoster/DateiTo.py
new file mode 100644
index 000000000..f967c894a
--- /dev/null
+++ b/pyload/plugin/hoster/DateiTo.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DateiTo(SimpleHoster):
+ __name = "DateiTo"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Datei.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
+ SIZE_PATTERN = r'Dateigr&ouml;&szlig;e:</td>\s*<td colspan="2">(?P<S>.*?)</'
+ OFFLINE_PATTERN = r'>Datei wurde nicht gefunden<|>Bitte wÀhle deine Datei aus... <'
+
+ WAIT_PATTERN = r'countdown\({seconds: (\d+)'
+ MULTIDL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
+
+ DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
+
+
+ def handleFree(self, pyfile):
+ url = 'http://datei.to/ajax/download.php'
+ data = {'P': 'I', 'ID': self.info['pattern']['ID']}
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(10):
+ self.logDebug("URL", url, "POST", data)
+ self.html = self.load(url, post=data)
+ self.checkErrors()
+
+ if url.endswith('download.php') and 'P' in data:
+ if data['P'] == 'I':
+ self.doWait()
+
+ elif data['P'] == 'IV':
+ break
+
+ m = re.search(self.DATA_PATTERN, self.html)
+ if m is None:
+ self.error(_("data"))
+ url = 'http://datei.to/' + m.group(1)
+ data = dict(x.split('=') for x in m.group(2).split('&'))
+
+ if url.endswith('recaptcha.php'):
+ data['recaptcha_response_field'], data['recaptcha_challenge_field'] = recaptcha.challenge()
+ else:
+ self.fail(_("Too bad..."))
+
+ self.link = self.html
+
+
+ def checkErrors(self):
+ m = re.search(self.MULTIDL_PATTERN, self.html)
+ if m:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ wait_time = int(m.group(1)) if m else 30
+
+ errmsg = self.info['error'] = _("Parallel downloads")
+ self.retry(wait_time=wait_time, reason=errmsg)
+
+ self.info.pop('error', None)
+
+
+ def doWait(self):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ wait_time = int(m.group(1)) if m else 30
+
+ self.load('http://datei.to/ajax/download.php', post={'P': 'Ads'})
+ self.wait(wait_time, False)
diff --git a/pyload/plugin/hoster/DdlstorageCom.py b/pyload/plugin/hoster/DdlstorageCom.py
new file mode 100644
index 000000000..53c0df7cb
--- /dev/null
+++ b/pyload/plugin/hoster/DdlstorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DdlstorageCom(DeadHoster):
+ __name = "DdlstorageCom"
+ __type = "hoster"
+ __version = "1.02"
+
+ __pattern = r'https?://(?:www\.)?ddlstorage\.com/\w+'
+ __config = []
+
+ __description = """DDLStorage.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/DebridItaliaCom.py b/pyload/plugin/hoster/DebridItaliaCom.py
new file mode 100644
index 000000000..a0308e28e
--- /dev/null
+++ b/pyload/plugin/hoster/DebridItaliaCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class DebridItaliaCom(MultiHoster):
+ __name = "DebridItaliaCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'https?://(?:www\.|s\d+\.)?debriditalia\.com/dl/\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Debriditalia.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [("https://", "http://")]
+
+
+ def handlePremium(self, pyfile):
+ self.html = self.load("http://www.debriditalia.com/api.php",
+ get={'generate': "on", 'link': pyfile.url, 'p': self.getPassword()})
+
+ if "ERROR:" not in self.html:
+ self.link = self.html.strip()
+ else:
+ self.info['error'] = re.search(r'ERROR:(.*)', self.html).group(1).strip()
+
+ self.html = self.load("http://debriditalia.com/linkgen2.php",
+ post={'xjxfun' : "convertiLink",
+ 'xjxargs[]': "S<![CDATA[%s]]>" % pyfile.url,
+ 'xjxargs[]': "S%s" % self.getPassword()})
+ try:
+ self.link = re.search(r'<a href="(.+?)"', self.html).group(1)
+ except AttributeError:
+ pass
diff --git a/pyload/plugin/hoster/DepositfilesCom.py b/pyload/plugin/hoster/DepositfilesCom.py
new file mode 100644
index 000000000..e16222856
--- /dev/null
+++ b/pyload/plugin/hoster/DepositfilesCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DepositfilesCom(SimpleHoster):
+ __name = "DepositfilesCom"
+ __type = "hoster"
+ __version = "0.55"
+
+ __pattern = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Depositfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
+ SIZE_PATTERN = r': <b>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</b>'
+ OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
+
+ NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
+ (r'.*<b title="(?P<N>.+?)".*', "\g<N>")]
+ URL_REPLACEMENTS = [(__pattern + ".*", "https://dfiles.eu/files/\g<ID>")]
+
+ COOKIES = [("dfiles.eu", "lang_current", "en")]
+
+ WAIT_PATTERN = r'(?:download_waiter_remain">|html_download_api-limit_interval">|>Please wait|>Try in).+'
+ ERROR_PATTER = r'File is checked, please try again in a minute'
+
+ LINK_FREE_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
+ LINK_PREMIUM_PATTERN = r'class="repeat"><a href="(.+?)"'
+ LINK_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load(pyfile.url, post={'gateway_result': "1"})
+
+ self.checkErrors()
+
+ m = re.search(r"var fid = '(\w+)';", self.html)
+ if m is None:
+ self.retry(wait_time=5)
+ params = {'fid': m.group(1)}
+ self.logDebug("FID: %s" % params['fid'])
+
+ self.checkErrors()
+
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+ if captcha_key is None:
+ return
+
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
+ params['response'], params['challenge'] = recaptcha.challenge(captcha_key)
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = urllib.unquote(m.group(1))
+
+
+ def handlePremium(self, pyfile):
+ if '<span class="html_download_api-gold_traffic_limit">' in self.html:
+ self.logWarning(_("Download limit reached"))
+ self.retry(25, 60 * 60, "Download limit reached")
+
+ elif 'onClick="show_gold_offer' in self.html:
+ self.account.relogin(self.user)
+ self.retry()
+
+ else:
+ link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ mirror = re.search(self.LINK_MIRROR_PATTERN, self.html)
+
+ if link:
+ self.link = link.group(1)
+
+ elif mirror:
+ self.link = mirror.group(1)
diff --git a/pyload/plugin/hoster/DevhostSt.py b/pyload/plugin/hoster/DevhostSt.py
new file mode 100644
index 000000000..573b92838
--- /dev/null
+++ b/pyload/plugin/hoster/DevhostSt.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/mM8
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DevhostSt(SimpleHoster):
+ __name = "DevhostSt"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """d-h.st hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<span title="(?P<N>.*?)"'
+ SIZE_PATTERN = r'</span> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<br'
+ HASHSUM_PATTERN = r'>(?P<T>.*?) Sum</span>: &nbsp;(?P<H>.*?)<br'
+
+ OFFLINE_PATTERN = r'>File Not Found'
+ LINK_FREE_PATTERN = r'var product_download_url= \'(.+?)\''
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/DlFreeFr.py b/pyload/plugin/hoster/DlFreeFr.py
new file mode 100644
index 000000000..684da2b6d
--- /dev/null
+++ b/pyload/plugin/hoster/DlFreeFr.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.network.Browser import Browser
+from pyload.network.CookieJar import CookieJar
+from pyload.plugin.captcha.AdYouLike import AdYouLike
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns
+from pyload.utils import json_loads
+
+
+class CustomBrowser(Browser):
+
+ def __init__(self, bucket=None, options={}):
+ Browser.__init__(self, bucket, options)
+
+
+ def load(self, *args, **kwargs):
+ post = kwargs.get("post")
+
+ if post is None and len(args) > 2:
+ post = args[2]
+
+ if post:
+ self.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
+ self.http.c.setopt(pycurl.POST, 1)
+ self.http.c.setopt(pycurl.CUSTOMREQUEST, "POST")
+ else:
+ self.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.http.c.setopt(pycurl.POST, 0)
+ self.http.c.setopt(pycurl.CUSTOMREQUEST, "GET")
+
+ return Browser.load(self, *args, **kwargs)
+
+
+class DlFreeFr(SimpleHoster):
+ __name = "DlFreeFr"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dl.free.fr hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("the-razer", "daniel_ AT gmx DOT net"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Toilal", "toilal.dev@gmail.com")]
+
+
+ NAME_PATTERN = r'Fichier:</td>\s*<td.*?>(?P<N>[^>]*)</td>'
+ SIZE_PATTERN = r'Taille:</td>\s*<td.*?>(?P<S>[\d.,]+\w)o'
+ OFFLINE_PATTERN = r'Erreur 404 - Document non trouv|Fichier inexistant|Le fichier demand&eacute; n\'a pas &eacute;t&eacute; trouv&eacute;'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.limitDL = 5
+ self.chunkLimit = 1
+
+
+ def init(self):
+ factory = self.core.requestFactory
+ self.req = CustomBrowser(factory.bucket, factory.getOptions())
+
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
+ valid_url = pyfile.url
+ headers = self.load(valid_url, just_header=True)
+
+ if headers.get('code') == 302:
+ valid_url = headers.get('location')
+ headers = self.load(valid_url, just_header=True)
+
+ if headers.get('code') == 200:
+ content_type = headers.get('content-type')
+ if content_type and content_type.startswith("text/html"):
+ # Undirect acces to requested file, with a web page providing it (captcha)
+ self.html = self.load(valid_url)
+ self.handleFree(pyfile)
+ else:
+ # Direct access to requested file for users using free.fr as Internet Service Provider.
+ self.link = valid_url
+
+ elif headers.get('code') == 404:
+ self.offline()
+
+ else:
+ self.fail(_("Invalid return code: ") + str(headers.get('code')))
+
+
+ def handleFree(self, pyfile):
+ action, inputs = self.parseHtmlForm('action="getfile.pl"')
+
+ adyoulike = AdYouLike(self)
+ response, challenge = adyoulike.challenge()
+ inputs.update(response)
+
+ self.load("http://dl.free.fr/getfile.pl", post=inputs)
+ headers = self.getLastHeaders()
+ if headers.get("code") == 302 and "set-cookie" in headers and "location" in headers:
+ m = re.search("(.*?)=(.*?); path=(.*?); domain=(.*?)", headers.get("set-cookie"))
+ cj = CookieJar(__name)
+ if m:
+ cj.setCookie(m.group(4), m.group(1), m.group(2), m.group(3))
+ else:
+ self.fail(_("Cookie error"))
+
+ self.link = headers.get("location")
+
+ self.req.setCookieJar(cj)
+ else:
+ self.fail(_("Invalid response"))
+
+
+ def getLastHeaders(self):
+ # parse header
+ header = {"code": self.req.code}
+ for line in self.req.http.header.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
+ return header
diff --git a/pyload/plugin/hoster/DodanePl.py b/pyload/plugin/hoster/DodanePl.py
new file mode 100644
index 000000000..8edbb64c0
--- /dev/null
+++ b/pyload/plugin/hoster/DodanePl.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DodanePl(DeadHoster):
+ __name = "DodanePl"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?dodane\.pl/file/\d+'
+ __config = []
+
+ __description = """Dodane.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
diff --git a/pyload/plugin/hoster/DuploadOrg.py b/pyload/plugin/hoster/DuploadOrg.py
new file mode 100644
index 000000000..decd1a7ff
--- /dev/null
+++ b/pyload/plugin/hoster/DuploadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DuploadOrg(DeadHoster):
+ __name = "DuploadOrg"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?dupload\.org/\w{12}'
+ __config = []
+
+ __description = """Dupload.grg hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/EasybytezCom.py b/pyload/plugin/hoster/EasybytezCom.py
new file mode 100644
index 000000000..8dcb04b98
--- /dev/null
+++ b/pyload/plugin/hoster/EasybytezCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class EasybytezCom(XFSHoster):
+ __name = "EasybytezCom"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?easybytez\.com/\w{12}'
+
+ __description = """Easybytez.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ OFFLINE_PATTERN = r'>File not available'
+
+ LINK_PATTERN = r'(http://(\w+\.(easybytez|easyload|ezbytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/.+?)["\'<]'
diff --git a/pyload/plugin/hoster/EdiskCz.py b/pyload/plugin/hoster/EdiskCz.py
new file mode 100644
index 000000000..7f5f76b73
--- /dev/null
+++ b/pyload/plugin/hoster/EdiskCz.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class EdiskCz(SimpleHoster):
+ __name = "EdiskCz"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Edisk.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<span class="fl" title="(?P<N>.+?)">\s*.*?\((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</h1></span>'
+ OFFLINE_PATTERN = r'<h3>This file does not exist due to one of the following:</h3><ul><li>'
+
+ ACTION_PATTERN = r'/en/download/(\d+/.*\.html)'
+ LINK_FREE_PATTERN = r'http://.*edisk\.cz.*\.html'
+
+
+ def setup(self):
+ self.multiDL = False
+
+
+ def process(self, pyfile):
+ url = re.sub("/(stahni|sk/stahni)/", "/en/download/", pyfile.url)
+
+ self.logDebug("URL:" + url)
+
+ m = re.search(self.ACTION_PATTERN, url)
+ if m is None:
+ self.error(_("ACTION_PATTERN not found"))
+ action = m.group(1)
+
+ self.html = self.load(url, decode=True)
+ self.getFileInfo()
+
+ self.html = self.load(re.sub("/en/download/", "/en/download-slow/", url))
+
+ url = self.load(re.sub("/en/download/", "/x-download/", url), post={
+ "action": action
+ })
+
+ if not re.match(self.LINK_FREE_PATTERN, url):
+ self.fail(_("Unexpected server response"))
+
+ self.link = url
diff --git a/pyload/plugin/hoster/EgoFilesCom.py b/pyload/plugin/hoster/EgoFilesCom.py
new file mode 100644
index 000000000..f87c96dd1
--- /dev/null
+++ b/pyload/plugin/hoster/EgoFilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EgoFilesCom(DeadHoster):
+ __name = "EgoFilesCom"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https?://(?:www\.)?egofiles\.com/\w+'
+ __config = []
+
+ __description = """Egofiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/EnteruploadCom.py b/pyload/plugin/hoster/EnteruploadCom.py
new file mode 100644
index 000000000..e64c284f6
--- /dev/null
+++ b/pyload/plugin/hoster/EnteruploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EnteruploadCom(DeadHoster):
+ __name = "EnteruploadCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?enterupload\.com/\w+'
+ __config = []
+
+ __description = """EnterUpload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/EpicShareNet.py b/pyload/plugin/hoster/EpicShareNet.py
new file mode 100644
index 000000000..05a1aca40
--- /dev/null
+++ b/pyload/plugin/hoster/EpicShareNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EpicShareNet(DeadHoster):
+ __name = "EpicShareNet"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?epicshare\.net/\w{12}'
+ __config = []
+
+ __description = """EpicShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/EuroshareEu.py b/pyload/plugin/hoster/EuroshareEu.py
new file mode 100644
index 000000000..b66369fe0
--- /dev/null
+++ b/pyload/plugin/hoster/EuroshareEu.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class EuroshareEu(SimpleHoster):
+ __name = "EuroshareEu"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Euroshare.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<span style="float: left;"><strong>(?P<N>.+?)</strong> \((?P<S>.+?)\)</span>'
+ OFFLINE_PATTERN = ur'<h2>S.bor sa nena.iel</h2>|Poşadovaná stránka neexistuje!'
+
+ LINK_FREE_PATTERN = r'<a href="(/file/\d+/[^/]*/download/)"><div class="downloadButton"'
+
+ ERR_PARDL_PATTERN = r'<h2>Prebieha s.ahovanie</h2>|<p>Naraz je z jednej IP adresy mo.n. s.ahova. iba jeden s.bor'
+ ERR_NOT_LOGGED_IN_PATTERN = r'href="/customer-zone/login/"'
+
+ URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
+
+
+ def handlePremium(self, pyfile):
+ if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
+ self.account.relogin(self.user)
+ self.retry(reason=_("User not logged in"))
+
+ self.link = pyfile.url.rstrip('/') + "/download/"
+
+ check = self.checkDownload({"login": re.compile(self.ERR_NOT_LOGGED_IN_PATTERN),
+ "json" : re.compile(r'\{"status":"error".*?"message":"(.*?)"')})
+
+ if check == "login" or (check == "json" and self.lastCheck.group(1) == "Access token expired"):
+ self.account.relogin(self.user)
+ self.retry(reason=_("Access token expired"))
+
+ elif check == "json":
+ self.fail(self.lastCheck.group(1))
+
+
+ def handleFree(self, pyfile):
+ if re.search(self.ERR_PARDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = "http://euroshare.eu%s" % m.group(1)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"multi-dl": re.compile(self.ERR_PARDL_PATTERN)})
+ self.longWait(5 * 60, 12)
+
+ return super(EuroshareEu, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/ExashareCom.py b/pyload/plugin/hoster/ExashareCom.py
new file mode 100644
index 000000000..c7b876076
--- /dev/null
+++ b/pyload/plugin/hoster/ExashareCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class ExashareCom(XFSHoster):
+ __name = "ExashareCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?exashare\.com/\w{12}'
+
+ __description = """Exashare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'>(?P<NAME>.+?)<small>\( (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ LINK_FREE_PATTERN = r'file: "(.+?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+ else:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/ExtabitCom.py b/pyload/plugin/hoster/ExtabitCom.py
new file mode 100644
index 000000000..d0bcaa4e6
--- /dev/null
+++ b/pyload/plugin/hoster/ExtabitCom.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+class ExtabitCom(SimpleHoster):
+ __name = "ExtabitCom"
+ __type = "hoster"
+ __version = "0.65"
+
+ __pattern = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Extabit.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>.+?)">'
+ SIZE_PATTERN = r'<th>Size:</th>\s*<td class="col-fileinfo">(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'>File not found<'
+ TEMP_OFFLINE_PATTERN = r'>(File is temporary unavailable|No download mirror)<'
+
+ LINK_FREE_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]'
+
+
+ def handleFree(self, pyfile):
+ if r">Only premium users can download this file" in self.html:
+ self.fail(_("Only premium users can download this file"))
+
+ m = re.search(r"Next free download from your ip will be available in <b>(\d+)\s*minutes", self.html)
+ if m:
+ self.wait(int(m.group(1)) * 60, True)
+ elif "The daily downloads limit from your IP is exceeded" in self.html:
+ self.logWarning(_("You have reached your daily downloads limit for today"))
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ self.logDebug("URL: " + self.req.http.lastEffectiveURL)
+ m = re.match(self.__pattern, self.req.http.lastEffectiveURL)
+ fileID = m.group('ID') if m else self.info['pattern']['ID']
+
+ m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
+ if m:
+ recaptcha = ReCaptcha(self)
+ captcha_key = m.group(1)
+
+ for _i in xrange(5):
+ get_data = {"type": "recaptcha"}
+ get_data['capture'], get_data['challenge'] = recaptcha.challenge(captcha_key)
+ res = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
+ if "ok" in res:
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("Invalid captcha"))
+ else:
+ self.error(_("Captcha"))
+
+ if not "href" in res:
+ self.error(_("Bad JSON response"))
+
+ self.html = self.load("http://extabit.com/file/%s%s" % (fileID, res['href']))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/FastixRu.py b/pyload/plugin/hoster/FastixRu.py
new file mode 100644
index 000000000..dc3b7d6ea
--- /dev/null
+++ b/pyload/plugin/hoster/FastixRu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class FastixRu(MultiHoster):
+ __name = "FastixRu"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?fastix\.(ru|it)/file/\w{24}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Fastix multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def setup(self):
+ self.chunkLimit = 3
+
+
+ def handlePremium(self, pyfile):
+ api_key = self.account.getAccountData(self.user)
+ api_key = api_key['api']
+
+ self.html = self.load("http://fastix.ru/api_v2/",
+ get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url})
+
+ data = json_loads(self.html)
+
+ self.logDebug("Json data", data)
+
+ if "error\":true" in self.html:
+ self.offline()
+ else:
+ self.link = data['downloadlink']
diff --git a/pyload/plugin/hoster/FastshareCz.py b/pyload/plugin/hoster/FastshareCz.py
new file mode 100644
index 000000000..7e688a941
--- /dev/null
+++ b/pyload/plugin/hoster/FastshareCz.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FastshareCz(SimpleHoster):
+ __name = "FastshareCz"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FastShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ URL_REPLACEMENTS = [("#.*", "")]
+
+ COOKIES = [("fastshare.cz", "lang", "en")]
+
+ NAME_PATTERN = r'<h3 class="section_title">(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Size\s*:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
+
+ LINK_FREE_PATTERN = r'>Enter the code\s*:</em>\s*<span><img src="(.+?)"'
+ LINK_PREMIUM_PATTERN = r'(http://\w+\.fastshare\.cz/download\.php\?id=\d+&)'
+
+ SLOT_ERROR = "> 100% of FREE slots are full"
+ CREDIT_ERROR = " credit for "
+
+
+ def checkErrors(self):
+ if self.SLOT_ERROR in self.html:
+ errmsg = self.info['error'] = _("No free slots")
+ self.retry(12, 60, errmsg)
+
+ if self.CREDIT_ERROR in self.html:
+ errmsg = self.info['error'] = _("Not enough traffic left")
+ self.logWarning(errmsg)
+ self.resetAccount()
+
+ self.info.pop('error', None)
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m:
+ action, captcha_src = m.groups()
+ else:
+ self.error(_("FREE_URL_PATTERN not found"))
+
+ baseurl = "http://www.fastshare.cz"
+ captcha = self.decryptCaptcha(urlparse.urljoin(baseurl, captcha_src))
+ self.download(urlparse.urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18})
+
+
+ def checkFile(self, rules={}):
+ check = self.checkDownload({
+ 'paralell-dl' : re.compile(r"<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)"),
+ 'wrong captcha': re.compile(r'Download for FREE'),
+ 'credit' : re.compile(self.CREDIT_ERROR)
+ })
+
+ if check == "paralell-dl":
+ self.retry(6, 10 * 60, _("Paralell download"))
+
+ elif check == "wrong captcha":
+ self.retry(max_tries=5, reason=_("Wrong captcha"))
+
+ elif check == "credit":
+ self.resetAccount()
+
+ return super(FastshareCz, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/FileApeCom.py b/pyload/plugin/hoster/FileApeCom.py
new file mode 100644
index 000000000..87d995817
--- /dev/null
+++ b/pyload/plugin/hoster/FileApeCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FileApeCom(DeadHoster):
+ __name = "FileApeCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+'
+ __config = []
+
+ __description = """FileApe.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("espes", "")]
diff --git a/pyload/plugin/hoster/FileSharkPl.py b/pyload/plugin/hoster/FileSharkPl.py
new file mode 100644
index 000000000..5d37a55c6
--- /dev/null
+++ b/pyload/plugin/hoster/FileSharkPl.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FileSharkPl(SimpleHoster):
+ __name = "FileSharkPl"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?fileshark\.pl/pobierz/\d+/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FileShark.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h2 class="name-file">(?P<N>.+)</h2>'
+ SIZE_PATTERN = r'<p class="size-file">(.*?)<strong>(?P<S>\d+\.?\d*)\s(?P<U>\w+)</strong></p>'
+ OFFLINE_PATTERN = r'(P|p)lik zosta. (usuni.ty|przeniesiony)'
+
+ LINK_FREE_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-free">'
+ LINK_PREMIUM_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-premium">'
+
+ WAIT_PATTERN = r'var timeToDownload = (\d+);'
+ ERROR_PATTERN = r'<p class="lead text-center alert alert-warning">(.*?)</p>'
+ IP_ERROR_PATTERN = r'Strona jest dost.pna wy..cznie dla u.ytkownik.w znajduj.cych si. na terenie Polski'
+ SLOT_ERROR_PATTERN = r'Osi.gni.to maksymaln. liczb. .ci.ganych jednocze.nie plik.w\.'
+
+ CAPTCHA_PATTERN = r'<img src="data:image/jpeg;base64,(.*?)" title="captcha"'
+ TOKEN_PATTERN = r'name="form\[_token\]" value="(.*?)" />'
+
+
+ def setup(self):
+ self.resumeDownload = True
+
+ if self.premium:
+ self.multiDL = True
+ self.limitDL = 20
+ else:
+ self.multiDL = False
+
+
+ def checkErrors(self):
+ # check if file is now available for download (-> file name can be found in html body)
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = _("Another download already run")
+ self.retry(15, int(m.group(1)), errmsg)
+
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ alert = m.group(1)
+
+ if re.match(self.IP_ERROR_PATTERN, alert):
+ self.fail(_("Only connections from Polish IP are allowed"))
+
+ elif re.match(self.SLOT_ERROR_PATTERN, alert):
+ errmsg = self.info['error'] = _("No free download slots available")
+ self.logWarning(errmsg)
+ self.retry(10, 30 * 60, _("Still no free download slots available"))
+
+ else:
+ self.info['error'] = alert
+ self.retry(10, 10 * 60, _("Try again later"))
+
+ self.info.pop('error', None)
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ link = urlparse.urljoin("http://fileshark.pl", m.group(1))
+
+ self.html = self.load(link)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ seconds = int(m.group(1))
+ self.logDebug("Wait %s seconds" % seconds)
+ self.wait(seconds)
+
+ action, inputs = self.parseHtmlForm('action=""')
+
+ m = re.search(self.TOKEN_PATTERN, self.html)
+ if m is None:
+ self.retry(reason=_("Captcha form not found"))
+
+ inputs['form[_token]'] = m.group(1)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.retry(reason=_("Captcha image not found"))
+
+ tmp_load = self.load
+ self.load = self._decode64 #: work-around: injects decode64 inside decryptCaptcha
+
+ inputs['form[captcha]'] = self.decryptCaptcha(m.group(1), imgtype='jpeg')
+ inputs['form[start]'] = ""
+
+ self.load = tmp_load
+
+ self.download(link, post=inputs, disposition=True)
+
+
+ def _decode64(self, data, *args, **kwargs):
+ return data.decode('base64')
diff --git a/pyload/plugin/hoster/FileStoreTo.py b/pyload/plugin/hoster/FileStoreTo.py
new file mode 100644
index 000000000..6315f208d
--- /dev/null
+++ b/pyload/plugin/hoster/FileStoreTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FileStoreTo(SimpleHoster):
+ __name = "FileStoreTo"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FileStore.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = r'File: <span.*?>(?P<N>.+?)<.*>Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
+ TEMP_OFFLINE_PATTERN = r'>Der Download ist nicht bereit !<'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ self.wait(10)
+ self.link = self.load("http://filestore.to/ajax/download.php",
+ get={'D': re.search(r'"D=(\w+)', self.html).group(1)})
diff --git a/pyload/plugin/hoster/FilebeerInfo.py b/pyload/plugin/hoster/FilebeerInfo.py
new file mode 100644
index 000000000..a929a0787
--- /dev/null
+++ b/pyload/plugin/hoster/FilebeerInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilebeerInfo(DeadHoster):
+ __name = "FilebeerInfo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+)'
+ __config = []
+
+ __description = """Filebeer.info plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/FilecloudIo.py b/pyload/plugin/hoster/FilecloudIo.py
new file mode 100644
index 000000000..4f57d3a74
--- /dev/null
+++ b/pyload/plugin/hoster/FilecloudIo.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilecloudIo(SimpleHoster):
+ __name = "FilecloudIo"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filecloud.io hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LOGIN_ACCOUNT = True
+
+ NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
+ SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
+ OFFLINE_PATTERN = r'l10n\.(FILES__DOESNT_EXIST|REMOVED)'
+ TEMP_OFFLINE_PATTERN = r'l10n\.FILES__WARNING'
+
+ UKEY_PATTERN = r'\'ukey\'\s*:\'(\w+)'
+ AB1_PATTERN = r'if\( __ab1 == \'(\w+)\' \)'
+
+ ERROR_MSG_PATTERN = r'var __error_msg\s*=\s*l10n\.(.*?);'
+
+ RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\';'
+
+ LINK_FREE_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ data = {"ukey": self.info['pattern']['ID']}
+
+ m = re.search(self.AB1_PATTERN, self.html)
+ if m is None:
+ self.error(_("__AB1"))
+ data['__ab1'] = m.group(1)
+
+ recaptcha = ReCaptcha(self)
+
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ captcha_key = m.group(1) if m else recaptcha.detect_key()
+
+ if captcha_key is None:
+ self.error(_("ReCaptcha key not found"))
+
+ response, challenge = recaptcha.challenge(captcha_key)
+ self.account.form_data = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field" : response}
+ self.account.relogin(self.user)
+ self.retry(2)
+
+ json_url = "http://filecloud.io/download-request.json"
+ res = self.load(json_url, post=data)
+ self.logDebug(res)
+ res = json_loads(res)
+
+ if "error" in res and res['error']:
+ self.fail(res)
+
+ self.logDebug(res)
+ if res['captcha']:
+ data['ctype'] = "recaptcha"
+
+ for _i in xrange(5):
+ data['recaptcha_response'], data['recaptcha_challenge'] = recaptcha.challenge(captcha_key)
+
+ json_url = "http://filecloud.io/download-request.json"
+ res = self.load(json_url, post=data)
+ self.logDebug(res)
+ res = json_loads(res)
+
+ if "retry" in res and res['retry']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("Incorrect captcha"))
+
+ if res['dl']:
+ self.html = self.load('http://filecloud.io/download.html')
+
+ m = re.search(self.LINK_FREE_PATTERN % self.info['pattern']['ID'], self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ if "size" in self.info and self.info['size']:
+ self.check_data = {"size": int(self.info['size'])}
+
+ self.link = m.group(1)
+ else:
+ self.fail(_("Unexpected server response"))
+
+
+ def handlePremium(self, pyfile):
+ akey = self.account.getAccountData(self.user)['akey']
+ ukey = self.info['pattern']['ID']
+ self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey))
+ rep = self.load("http://api.filecloud.io/api-fetch_download_url.api",
+ post={"akey": akey, "ukey": ukey})
+ self.logDebug("FetchDownloadUrl: " + rep)
+ rep = json_loads(rep)
+ if rep['status'] == 'ok':
+ self.link = rep['download_url']
+ else:
+ self.fail(rep['message'])
diff --git a/pyload/plugin/hoster/FilefactoryCom.py b/pyload/plugin/hoster/FilefactoryCom.py
new file mode 100644
index 000000000..782b6ba48
--- /dev/null
+++ b/pyload/plugin/hoster/FilefactoryCom.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ for url in urls:
+ h = getURL(url, just_header=True)
+ m = re.search(r'Location: (.+)\r\n', h)
+ if m and not re.match(m.group(1), FilefactoryCom.__pattern): #: It's a direct link! Skipping
+ yield (url, 0, 3, url)
+ else: #: It's a standard html page
+ yield parseFileInfo(FilefactoryCom, url, getURL(url))
+
+
+class FilefactoryCom(SimpleHoster):
+ __name = "FilefactoryCom"
+ __type = "hoster"
+ __version = "0.54"
+
+ __pattern = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filefactory.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'<div id="file_name"[^>]*>\s*<h2>(?P<N>[^<]+)</h2>\s*<div id="file_info">\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+) uploaded'
+ OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'"([^"]+filefactory\.com/get.+?)"'
+
+ WAIT_PATTERN = r'<div id="countdown_clock" data-delay="(\d+)">'
+ PREMIUM_ONLY_PATTERN = r'>Premium Account Required'
+
+ COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
+
+
+ def handleFree(self, pyfile):
+ if "Currently only Premium Members can download files larger than" in self.html:
+ self.fail(_("File too large for free download"))
+ elif "All free download slots on this server are currently in use" in self.html:
+ self.retry(50, 15 * 60, _("All free slots are busy"))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+
+ def checkFile(self, rules={}):
+ check = self.checkDownload({'multiple': "You are currently downloading too many files at once.",
+ 'error' : '<div id="errorMessage">'})
+
+ if check == "multiple":
+ self.logDebug("Parallel downloads detected; waiting 15 minutes")
+ self.retry(wait_time=15 * 60, reason=_("Parallel downloads"))
+
+ elif check == "error":
+ self.error(_("Unknown error"))
+
+ return super(FilefactoryCom, self).checkFile(rules)
+
+
+ def handlePremium(self, pyfile):
+ self.link = self.directLink(self.load(pyfile.url, just_header=True))
+
+ if not self.link:
+ html = self.load(pyfile.url)
+ m = re.search(self.LINK_PREMIUM_PATTERN, html)
+ if m:
+ self.link = m.group(1)
+ else:
+ self.error(_("Premium download link not found"))
diff --git a/pyload/plugin/hoster/FilejungleCom.py b/pyload/plugin/hoster/FilejungleCom.py
new file mode 100644
index 000000000..2fe238de8
--- /dev/null
+++ b/pyload/plugin/hoster/FilejungleCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.FileserveCom import FileserveCom, checkFile
+from pyload.plugin.Plugin import chunks
+
+
+class FilejungleCom(FileserveCom):
+ __name = "FilejungleCom"
+ __type = "hoster"
+ __version = "0.51"
+
+ __pattern = r'http://(?:www\.)?filejungle\.com/f/(?P<ID>[^/]+)'
+
+ __description = """Filejungle.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ URLS = ["http://www.filejungle.com/f/", "http://www.filejungle.com/check_links.php",
+ "http://www.filejungle.com/checkReCaptcha.php"]
+ LINKCHECK_TR = r'<li>\s*(<div class="col1">.*?)</li>'
+ LINKCHECK_TD = r'<div class="(?:col )?col\d">(?:<.*?>|&nbsp;)*([^<]*)'
+
+ LONG_WAIT_PATTERN = r'<h1>Please wait for (\d+) (\w+)\s*to download the next file\.</h1>'
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 100):
+ yield checkFile(FilejungleCom, chunk)
diff --git a/pyload/plugin/hoster/FileomCom.py b/pyload/plugin/hoster/FileomCom.py
new file mode 100644
index 000000000..b01b34db0
--- /dev/null
+++ b/pyload/plugin/hoster/FileomCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://fileom.com/gycaytyzdw3g/random.bin.html
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class FileomCom(XFSHoster):
+ __name = "FileomCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?fileom\.com/\w{12}'
+
+ __description = """Fileom.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
+ SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ LINK_PATTERN = r'var url2 = \'(.+?)\';'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
diff --git a/pyload/plugin/hoster/FilepostCom.py b/pyload/plugin/hoster/FilepostCom.py
new file mode 100644
index 000000000..1d2843a2e
--- /dev/null
+++ b/pyload/plugin/hoster/FilepostCom.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilepostCom(SimpleHoster):
+ __name = "FilepostCom"
+ __type = "hoster"
+ __version = "0.33"
+
+ __pattern = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filepost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<input type="text" id="url" value=\'<a href.*?>(?P<N>[^>]+?) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</a>\' class="inp_text"/>'
+ OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>|<div class="file_info file_info_deleted">'
+
+ PREMIUM_ONLY_PATTERN = r'members only. Please upgrade to premium|a premium membership is required to download this file'
+ RECAPTCHA_PATTERN = r'Captcha.init\({\s*key:\s*\'(.+?)\''
+ FLP_TOKEN_PATTERN = r'set_store_options\({token: \'(.+?)\''
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.FLP_TOKEN_PATTERN, self.html)
+ if m is None:
+ self.error(_("Token"))
+ flp_token = m.group(1)
+
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha key"))
+ captcha_key = m.group(1)
+
+ # Get wait time
+ get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time.time() * 10000)) + '-xml'}
+ post_dict = {'action': 'set_download', 'token': flp_token, 'code': self.info['pattern']['ID']}
+ wait_time = int(self.getJsonResponse(get_dict, post_dict, 'wait_time'))
+
+ if wait_time > 0:
+ self.wait(wait_time)
+
+ post_dict = {"token": flp_token, "code": self.info['pattern']['ID'], "file_pass": ''}
+
+ if 'var is_pass_exists = true;' in self.html:
+ # Solve password
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + file_pass)
+
+ get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
+ post_dict['file_pass'] = file_pass
+
+ self.link = self.getJsonResponse(get_dict, post_dict, 'link')
+
+ if not self.link:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ else:
+ # Solve recaptcha
+ recaptcha = ReCaptcha(self)
+
+ for i in xrange(5):
+ get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
+ if i:
+ post_dict['recaptcha_response_field'], post_dict['recaptcha_challenge_field'] = recaptcha.challenge(
+ captcha_key)
+ self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
+ captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
+
+ self.link = self.getJsonResponse(get_dict, post_dict, 'link')
+
+ else:
+ self.fail(_("Invalid captcha"))
+
+
+ def getJsonResponse(self, get_dict, post_dict, field):
+ res = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
+
+ self.logDebug(res)
+
+ if not 'js' in res:
+ self.error(_("JSON %s 1") % field)
+
+ # i changed js_answer to res['js'] since js_answer is nowhere set.
+ # i don't know the JSON-HTTP specs in detail, but the previous author
+ # accessed res['js']['error'] as well as js_answer['error'].
+ # see the two lines commented out with "# ~?".
+ if 'error' in res['js']:
+
+ if res['js']['error'] == 'download_delay':
+ self.retry(wait_time=res['js']['params']['next_download'])
+ # ~? self.retry(wait_time=js_answer['params']['next_download'])
+
+ elif 'Wrong file password' in res['js']['error'] \
+ or 'You entered a wrong CAPTCHA code' in res['js']['error'] \
+ or 'CAPTCHA Code nicht korrekt' in res['js']['error']:
+ return None
+
+ elif 'CAPTCHA' in res['js']['error']:
+ self.logDebug("Error response is unknown, but mentions CAPTCHA")
+ return None
+
+ else:
+ self.fail(res['js']['error'])
+
+ if not 'answer' in res['js'] or not field in res['js']['answer']:
+ self.error(_("JSON %s 2") % field)
+
+ return res['js']['answer'][field]
diff --git a/pyload/plugin/hoster/FilepupNet.py b/pyload/plugin/hoster/FilepupNet.py
new file mode 100644
index 000000000..91d640e00
--- /dev/null
+++ b/pyload/plugin/hoster/FilepupNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.filepup.net/files/k5w4ZVoF1410184283.html
+# http://www.filepup.net/files/R4GBq9XH1410186553.html
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilepupNet(SimpleHoster):
+ __name = "FilepupNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filepup\.net/files/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filepup.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'>(?P<N>.+?)</h1>'
+ SIZE_PATTERN = r'class="fa fa-archive"></i> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>This file has been deleted'
+
+ LINK_FREE_PATTERN = r'(http://www\.filepup\.net/get/.+?)\''
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ dl_link = m.group(1)
+ self.download(dl_link, post={'task': "download"})
diff --git a/pyload/plugin/hoster/FilerNet.py b/pyload/plugin/hoster/FilerNet.py
new file mode 100644
index 000000000..be8445fad
--- /dev/null
+++ b/pyload/plugin/hoster/FilerNet.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filer.net/get/ivgf5ztw53et3ogd
+# http://filer.net/get/hgo14gzcng3scbvv
+
+import pycurl
+import re
+import urlparse
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilerNet(SimpleHoster):
+ __name = "FilerNet"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'https?://(?:www\.)?filer\.net/get/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filer.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+ INFO_PATTERN = r'<h1 class="page-header">Free Download (?P<N>\S+) <small>(?P<S>[\w.]+) (?P<U>[\w^_]+)</small></h1>'
+ OFFLINE_PATTERN = r'Nicht gefunden'
+
+ WAIT_PATTERN = r'musst du <span id="time">(\d+)'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>'
+
+
+ def handleFree(self, pyfile):
+ inputs = self.parseHtmlForm(input_names={'token': re.compile(r'.+')})[1]
+ if 'token' not in inputs:
+ self.error(_("Unable to detect token"))
+
+ self.html = self.load(pyfile.url, post={'token': inputs['token']}, decode=True)
+
+ inputs = self.parseHtmlForm(input_names={'hash': re.compile(r'.+')})[1]
+ if 'hash' not in inputs:
+ self.error(_("Unable to detect hash"))
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ self.load(pyfile.url, post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response,
+ 'hash': inputs['hash']}, follow_location=False)
+
+ if 'location' in self.req.http.header.lower():
+ self.link = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
+ self.correctCaptcha()
+ else:
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/FilerioCom.py b/pyload/plugin/hoster/FilerioCom.py
new file mode 100644
index 000000000..c55fd2c0b
--- /dev/null
+++ b/pyload/plugin/hoster/FilerioCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class FilerioCom(XFSHoster):
+ __name = "FilerioCom"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
+
+ __description = """FileRio.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ URL_REPLACEMENTS = [(r'filekeen\.com', "filerio.in")]
+
+ OFFLINE_PATTERN = r'>&quot;File Not Found|File has been removed'
diff --git a/pyload/plugin/hoster/FilesMailRu.py b/pyload/plugin/hoster/FilesMailRu.py
new file mode 100644
index 000000000..0d7f47536
--- /dev/null
+++ b/pyload/plugin/hoster/FilesMailRu.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+
+
+def getInfo(urls):
+ result = []
+ for chunk in chunks(urls, 10):
+ for url in chunk:
+ html = getURL(url)
+ if r'<div class="errorMessage mb10">' in html:
+ result.append((url, 0, 1, url))
+ elif r'Page cannot be displayed' in html:
+ result.append((url, 0, 1, url))
+ else:
+ try:
+ url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
+ file_name = re.search(url_pattern, html).group(0).split(', event)">')[1].split('</a>')[0]
+ result.append((file_name, 0, 2, url))
+ except Exception:
+ pass
+
+ # status 1=OFFLINE, 2=OK, 3=UNKNOWN
+ # result.append((#name,#size,#status,#url))
+ yield result
+
+
+class FilesMailRu(Hoster):
+ __name = "FilesMailRu"
+ __type = "hoster"
+ __version = "0.32"
+
+ __pattern = r'http://(?:www\.)?files\.mail\.ru/.+'
+
+ __description = """Files.mail.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("oZiRiz", "ich@oziriz.de")]
+
+
+ def setup(self):
+ self.multiDL = bool(self.account)
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url)
+ self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
+
+ # marks the file as "offline" when the pattern was found on the html-page'''
+ if r'<div class="errorMessage mb10">' in self.html:
+ self.offline()
+
+ elif r'Page cannot be displayed' in self.html:
+ self.offline()
+
+ # the filename that will be showed in the list (e.g. test.part1.rar)'''
+ pyfile.name = self.getFileName()
+
+ # prepare and download'''
+ if not self.account:
+ self.prepare()
+ self.download(self.getFileUrl())
+ self.myPostProcess()
+ else:
+ self.download(self.getFileUrl())
+ self.myPostProcess()
+
+
+ def prepare(self):
+ """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected"""
+ self.setWait(10)
+ self.wait()
+ return True
+
+
+ def getFileUrl(self):
+ """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html"""
+ return re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[0]
+
+
+ def getFileName(self):
+ """gives you the Name for each file. Also extracted from the HTML-Page"""
+ return re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0]
+
+
+ def myPostProcess(self):
+ # searches the file for HTMl-Code. Sometimes the Redirect
+ # doesn't work (maybe a curl Problem) and you get only a small
+ # HTML file and the Download is marked as "finished"
+ # then the download will be restarted. It's only bad for these
+ # who want download a HTML-File (it's one in a million ;-) )
+ #
+ # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB
+ # so i set it to check every download because sometimes there are downloads
+ # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file
+ # (Loading 100MB in to ram is not an option)
+ check = self.checkDownload({"html": "<meta name="}, read_size=50000)
+ if check == "html":
+ self.logInfo(_(
+ "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." %
+ self.pyfile.name))
+ self.retry()
diff --git a/pyload/plugin/hoster/FileserveCom.py b/pyload/plugin/hoster/FileserveCom.py
new file mode 100644
index 000000000..1d4179e5c
--- /dev/null
+++ b/pyload/plugin/hoster/FileserveCom.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+from pyload.utils import parseFileSize
+
+
+def checkFile(plugin, urls):
+ html = getURL(plugin.URLS[1], post={"urls": "\n".join(urls)}, decode=True)
+
+ file_info = []
+ for li in re.finditer(plugin.LINKCHECK_TR, html, re.S):
+ try:
+ cols = re.findall(plugin.LINKCHECK_TD, li.group(1))
+ if cols:
+ file_info.append((
+ cols[1] if cols[1] != '--' else cols[0],
+ parseFileSize(cols[2]) if cols[2] != '--' else 0,
+ 2 if cols[3].startswith('Available') else 1,
+ cols[0]))
+ except Exception, e:
+ continue
+
+ return file_info
+
+
+class FileserveCom(Hoster):
+ __name = "FileserveCom"
+ __type = "hoster"
+ __version = "0.54"
+
+ __pattern = r'http://(?:www\.)?fileserve\.com/file/(?P<ID>[^/]+)'
+
+ __description = """Fileserve.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Paul King", ""),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ URLS = ["http://www.fileserve.com/file/", "http://www.fileserve.com/link-checker.php",
+ "http://www.fileserve.com/checkReCaptcha.php"]
+ LINKCHECK_TR = r'<tr>\s*(<td>http://www\.fileserve\.com/file/.*?)</tr>'
+ LINKCHECK_TD = r'<td>(?:<.*?>|&nbsp;)*([^<]*)'
+
+ CAPTCHA_KEY_PATTERN = r'var reCAPTCHA_publickey=\'(.+?)\''
+ LONG_WAIT_PATTERN = r'<li class="title">You need to wait (\d+) (\w+) to start another download\.</li>'
+ LINK_EXPIRED_PATTERN = r'Your download link has expired'
+ DAILY_LIMIT_PATTERN = r'Your daily download limit has been reached'
+ NOT_LOGGED_IN_PATTERN = r'<form (name="loginDialogBoxForm"|id="login_form")|<li><a href="/login\.php">Login</a></li>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+ self.file_id = re.match(self.__pattern, self.pyfile.url).group('ID')
+ self.url = "%s%s" % (self.URLS[0], self.file_id)
+
+ self.logDebug("File ID: %s URL: %s" % (self.file_id, self.url))
+
+
+ def process(self, pyfile):
+ pyfile.name, pyfile.size, status, self.url = checkFile(self, [self.url])[0]
+ if status != 2:
+ self.offline()
+ self.logDebug("File Name: %s Size: %d" % (pyfile.name, pyfile.size))
+
+ if self.premium:
+ self.handlePremium(pyfile)
+ else:
+ self.handleFree(pyfile)
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load(self.url)
+ action = self.load(self.url, post={"checkDownload": "check"}, decode=True)
+ action = json_loads(action)
+ self.logDebug(action)
+
+ if "fail" in action:
+ if action['fail'] == "timeLimit":
+ self.html = self.load(self.url, post={"checkDownload": "showError", "errorType": "timeLimit"},
+ decode=True)
+
+ self.doLongWait(re.search(self.LONG_WAIT_PATTERN, self.html))
+
+ elif action['fail'] == "parallelDownload":
+ self.logWarning(_("Parallel download error, now waiting 60s"))
+ self.retry(wait_time=60, reason=_("parallelDownload"))
+
+ else:
+ self.fail(_("Download check returned: %s") % action['fail'])
+
+ elif "success" in action:
+ if action['success'] == "showCaptcha":
+ self.doCaptcha()
+ self.doTimmer()
+ elif action['success'] == "showTimmer":
+ self.doTimmer()
+
+ else:
+ self.error(_("Unknown server response"))
+
+ # show download link
+ res = self.load(self.url, post={"downloadLink": "show"}, decode=True)
+ self.logDebug("Show downloadLink response: %s" % res)
+ if "fail" in res:
+ self.error(_("Couldn't retrieve download url"))
+
+ # this may either download our file or forward us to an error page
+ self.download(self.url, post={"download": "normal"})
+ self.logDebug(self.req.http.lastEffectiveURL)
+
+ check = self.checkDownload({"expired": self.LINK_EXPIRED_PATTERN,
+ "wait" : re.compile(self.LONG_WAIT_PATTERN),
+ "limit" : self.DAILY_LIMIT_PATTERN})
+
+ if check == "expired":
+ self.logDebug("Download link was expired")
+ self.retry()
+
+ elif check == "wait":
+ self.doLongWait(self.lastCheck)
+
+ elif check == "limit":
+ self.logWarning(_("Download limited reached for today"))
+ self.setWait(secondsToMidnight(gmt=2), True)
+ self.wait()
+ self.retry()
+
+ self.thread.m.reconnecting.wait(3) #: Ease issue with later downloads appearing to be in parallel
+
+
+ def doTimmer(self):
+ res = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
+ self.logDebug("Wait response: %s" % res[:80])
+
+ if "fail" in res:
+ self.fail(_("Failed getting wait time"))
+
+ if self.getClassName() == "FilejungleCom":
+ m = re.search(r'"waitTime":(\d+)', res)
+ if m is None:
+ self.fail(_("Cannot get wait time"))
+ wait_time = int(m.group(1))
+ else:
+ wait_time = int(res) + 3
+
+ self.setWait(wait_time)
+ self.wait()
+
+
+ def doCaptcha(self):
+ captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(captcha_key)
+ res = json_loads(self.load(self.URLS[2],
+ post={'recaptcha_challenge_field' : challenge,
+ 'recaptcha_response_field' : response,
+ 'recaptcha_shortencode_field': self.file_id}))
+ if not res['success']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("Invalid captcha"))
+
+
+ def doLongWait(self, m):
+ wait_time = (int(m.group(1)) * {'seconds': 1, 'minutes': 60, 'hours': 3600}[m.group(2)]) if m else 12 * 60
+ self.setWait(wait_time, True)
+ self.wait()
+ self.retry()
+
+
+ def handlePremium(self, pyfile):
+ premium_url = None
+ if self.getClassName() == "FileserveCom":
+ # try api download
+ res = self.load("http://app.fileserve.com/api/download/premium/",
+ post={"username": self.user,
+ "password": self.account.getAccountData(self.user)['password'],
+ "shorten": self.file_id},
+ decode=True)
+ if res:
+ res = json_loads(res)
+ if res['error_code'] == "302":
+ premium_url = res['next']
+ elif res['error_code'] in ["305", "500"]:
+ self.tempOffline()
+ elif res['error_code'] in ["403", "605"]:
+ self.resetAccount()
+ elif res['error_code'] in ["606", "607", "608"]:
+ self.offline()
+ else:
+ self.logError(res['error_code'], res['error_message'])
+
+ self.download(premium_url or pyfile.url)
+
+ if not premium_url and self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)}):
+ self.account.relogin(self.user)
+ self.retry(reason=_("Not logged in"))
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 100):
+ yield checkFile(FileserveCom, chunk)
diff --git a/pyload/plugin/hoster/FileshareInUa.py b/pyload/plugin/hoster/FileshareInUa.py
new file mode 100644
index 000000000..8b09a086a
--- /dev/null
+++ b/pyload/plugin/hoster/FileshareInUa.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FileshareInUa(DeadHoster):
+ __name = "FileshareInUa"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?fileshare\.in\.ua/\w{7}'
+ __config = []
+
+ __description = """Fileshare.in.ua hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fwannmacher", "felipe@warhammerproject.com")]
diff --git a/pyload/plugin/hoster/FilesonicCom.py b/pyload/plugin/hoster/FilesonicCom.py
new file mode 100644
index 000000000..f02f7bff6
--- /dev/null
+++ b/pyload/plugin/hoster/FilesonicCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilesonicCom(DeadHoster):
+ __name = "FilesonicCom"
+ __type = "hoster"
+ __version = "0.35"
+
+ __pattern = r'http://(?:www\.)?filesonic\.com/file/\w+'
+ __config = []
+
+ __description = """Filesonic.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", "")]
diff --git a/pyload/plugin/hoster/FilezyNet.py b/pyload/plugin/hoster/FilezyNet.py
new file mode 100644
index 000000000..bd6ac5984
--- /dev/null
+++ b/pyload/plugin/hoster/FilezyNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilezyNet(DeadHoster):
+ __name = "FilezyNet"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?filezy\.net/\w{12}'
+ __config = []
+
+ __description = """Filezy.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
diff --git a/pyload/plugin/hoster/FiredriveCom.py b/pyload/plugin/hoster/FiredriveCom.py
new file mode 100644
index 000000000..a5e56191f
--- /dev/null
+++ b/pyload/plugin/hoster/FiredriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FiredriveCom(DeadHoster):
+ __name = "FiredriveCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Firedrive.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/FlyFilesNet.py b/pyload/plugin/hoster/FlyFilesNet.py
new file mode 100644
index 000000000..4f1d71d21
--- /dev/null
+++ b/pyload/plugin/hoster/FlyFilesNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FlyFilesNet(SimpleHoster):
+ __name = "FlyFilesNet"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?flyfiles\.net/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FlyFiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+ SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
+ NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
+
+
+ def process(self, pyfile):
+ name = re.search(self.NAME_PATTERN, pyfile.url).group(1)
+ pyfile.name = urllib.unquote_plus(name)
+
+ session = re.search(self.SESSION_PATTERN, pyfile.url).group(1)
+
+ url = "http://flyfiles.net"
+
+ # get download URL
+ parsed_url = getURL(url, post={"getDownLink": session})
+ self.logDebug("Parsed URL: %s" % parsed_url)
+
+ if parsed_url == '#downlink|' or parsed_url == "#downlink|#":
+ self.logWarning(_("Could not get the download URL. Please wait 10 minutes"))
+ self.wait(10 * 60, True)
+ self.retry()
+
+ self.link = parsed_url.replace('#downlink|', '')
diff --git a/pyload/plugin/hoster/FourSharedCom.py b/pyload/plugin/hoster/FourSharedCom.py
new file mode 100644
index 000000000..7ea5e55b8
--- /dev/null
+++ b/pyload/plugin/hoster/FourSharedCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FourSharedCom(SimpleHoster):
+ __name = "FourSharedCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """4Shared.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
+ SIZE_PATTERN = r'<span title="Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)">'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
+
+ NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
+ SIZE_REPLACEMENTS = [(",", "")]
+
+ DIRECT_LINK = False
+ LOGIN_ACCOUNT = True
+
+ LINK_FREE_PATTERN = r'name="d3link" value="(.*?)"'
+ LINK_BTN_PATTERN = r'id="btnLink" href="(.*?)"'
+
+ ID_PATTERN = r'name="d3fid" value="(.*?)"'
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_BTN_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ else:
+ link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', pyfile.url)
+
+ self.html = self.load(link)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link"))
+
+ self.link = m.group(1)
+
+ try:
+ m = re.search(self.ID_PATTERN, self.html)
+ res = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
+ self.logDebug(res)
+ except Exception:
+ pass
+
+ self.wait(20)
diff --git a/pyload/plugin/hoster/FreakshareCom.py b/pyload/plugin/hoster/FreakshareCom.py
new file mode 100644
index 000000000..6cf447128
--- /dev/null
+++ b/pyload/plugin/hoster/FreakshareCom.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+
+
+class FreakshareCom(Hoster):
+ __name = "FreakshareCom"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
+
+ __description = """Freakshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sitacuisses", "sitacuisses@yahoo.de"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Toilal", "toilal.dev@gmail.com")]
+
+
+ def setup(self):
+ self.multiDL = False
+ self.req_opts = []
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ pyfile.url = pyfile.url.replace("freakshare.net/", "freakshare.com/")
+
+ if self.account:
+ self.html = self.load(pyfile.url, cookies=False)
+ pyfile.name = self.get_file_name()
+ self.download(pyfile.url)
+
+ else:
+ self.prepare()
+ self.get_file_url()
+
+ self.download(pyfile.url, post=self.req_opts)
+
+ check = self.checkDownload({"bad" : "bad try",
+ "paralell" : "> Sorry, you cant download more then 1 files at time. <",
+ "empty" : "Warning: Unknown: Filename cannot be empty",
+ "wrong_captcha" : "Wrong Captcha!",
+ "downloadserver": "No Downloadserver. Please try again later!"})
+
+ if check == "bad":
+ self.fail(_("Bad Try"))
+
+ elif check == "paralell":
+ self.setWait(300, True)
+ self.wait()
+ self.retry()
+
+ elif check == "empty":
+ self.fail(_("File not downloadable"))
+
+ elif check == "wrong_captcha":
+ self.invalidCaptcha()
+ self.retry()
+
+ elif check == "downloadserver":
+ self.retry(5, 15 * 60, _("No Download server"))
+
+
+ def prepare(self):
+ pyfile = self.pyfile
+
+ self.download_html()
+
+ if not self.file_exists():
+ self.offline()
+
+ self.setWait(self.get_waiting_time())
+
+ pyfile.name = self.get_file_name()
+ pyfile.size = self.get_file_size()
+
+ self.wait()
+
+ return True
+
+
+ def download_html(self):
+ self.load("http://freakshare.com/index.php", {"language": "EN"}) #: Set english language in server session
+ self.html = self.load(self.pyfile.url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+ if not self.wantReconnect:
+ self.req_opts = self.get_download_options() #: get the Post options for the Request
+ # file_url = self.pyfile.url
+ # return file_url
+ else:
+ self.offline()
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ if not self.wantReconnect:
+ m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
+ if m:
+ file_name = m.group(1)
+ else:
+ file_name = self.pyfile.url
+
+ return file_name
+ else:
+ return self.pyfile.url
+
+
+ def get_file_size(self):
+ size = 0
+ if not self.html:
+ self.download_html()
+
+ if not self.wantReconnect:
+ m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
+ if m:
+ units = float(m.group(1).replace(",", ""))
+ pow = {'KB': 1, 'MB': 2, 'GB': 3}[m.group(2)]
+ size = int(units * (2 ** 20) ** pow)
+
+ return size
+
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ if "Your Traffic is used up for today" in self.html:
+ self.wantReconnect = True
+ return secondsToMidnight(gmt=2)
+
+ timestring = re.search('\s*var\s(?:downloadWait|time)\s=\s(\d*)[\d.]*;', self.html)
+ if timestring:
+ return int(timestring.group(1))
+ else:
+ return 60
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"This file does not exist!", self.html):
+ return False
+ else:
+ return True
+
+
+ def get_download_options(self):
+ re_envelope = re.search(r".*?value=\"Free\sDownload\".*?\n*?(.*?<.*?>\n*)*?\n*\s*?</form>",
+ self.html).group(0) #: get the whole request
+ to_sort = re.findall(r"<input\stype=\"hidden\"\svalue=\"(.*?)\"\sname=\"(.*?)\"\s\/>", re_envelope)
+ request_options = dict((n, v) for (v, n) in to_sort)
+
+ herewego = self.load(self.pyfile.url, None, request_options) #: the actual download-Page
+
+ to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
+ request_options = dict((n, v) for (v, n) in to_sort)
+
+ challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=(\w+)", herewego)
+
+ if challenge:
+ re_captcha = ReCaptcha(self)
+ (request_options['recaptcha_challenge_field'],
+ request_options['recaptcha_response_field']) = re_captcha.challenge(challenge.group(1))
+
+ return request_options
diff --git a/pyload/plugin/hoster/FreeWayMe.py b/pyload/plugin/hoster/FreeWayMe.py
new file mode 100644
index 000000000..2406ed031
--- /dev/null
+++ b/pyload/plugin/hoster/FreeWayMe.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class FreeWayMe(MultiHoster):
+ __name = "FreeWayMe"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https://(?:www\.)?free-way\.me/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FreeWayMe multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def handlePremium(self, pyfile):
+ user, data = self.account.selectAccount()
+
+ for _i in xrange(5):
+ # try it five times
+ header = self.load("https://www.free-way.me/load.php",
+ get={'multiget': 7,
+ 'url' : pyfile.url,
+ 'user' : user,
+ 'pw' : self.account.getAccountData(user)['password'],
+ 'json' : ""},
+ just_header=True)
+
+ if 'location' in header:
+ headers = self.load(header['location'], just_header=True)
+ if headers['code'] == 500:
+ # error on 2nd stage
+ self.logError(_("Error [stage2]"))
+ else:
+ # seems to work..
+ self.download(header['location'])
+ break
+ else:
+ # error page first stage
+ self.logError(_("Error [stage1]"))
+
+ #@TODO: handle errors
diff --git a/pyload/plugin/hoster/FreevideoCz.py b/pyload/plugin/hoster/FreevideoCz.py
new file mode 100644
index 000000000..f616097f2
--- /dev/null
+++ b/pyload/plugin/hoster/FreevideoCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FreevideoCz(DeadHoster):
+ __name = "FreevideoCz"
+ __type = "hoster"
+ __version = "0.30"
+
+ __pattern = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
+ __config = []
+
+ __description = """Freevideo.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/FshareVn.py b/pyload/plugin/hoster/FshareVn.py
new file mode 100644
index 000000000..05f213680
--- /dev/null
+++ b/pyload/plugin/hoster/FshareVn.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ for url in urls:
+ html = getURL("http://www.fshare.vn/check_link.php",
+ post={'action': "check_link", 'arrlinks': url},
+ decode=True)
+
+ yield parseFileInfo(FshareVn, url, html)
+
+
+def doubleDecode(m):
+ return m.group(1).decode('raw_unicode_escape')
+
+
+class FshareVn(SimpleHoster):
+ __name = "FshareVn"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?fshare\.vn/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FshareVn hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)<\\/p>'
+ OFFLINE_PATTERN = r'<div class=\\"f_left file_w\\"|<\\/p>\\t\\t\\t\\t\\r\\n\\t\\t<p><\\/p>\\t\\t\\r\\n\\t\\t<p>0 KB<\\/p>'
+
+ NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
+
+ LINK_FREE_PATTERN = r'action="(http://download.*?)[#"]'
+ WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
+
+
+ def preload(self):
+ self.html = self.load("http://www.fshare.vn/check_link.php",
+ post={'action': "check_link", 'arrlinks': pyfile.url},
+ decode=True)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('frm_download')
+ url = urlparse.urljoin(pyfile.url, action)
+
+ if not inputs:
+ self.error(_("No FORM"))
+
+ elif 'link_file_pwd_dl' in inputs:
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + password)
+ inputs['link_file_pwd_dl'] = password
+ self.html = self.load(url, post=inputs, decode=True)
+
+ if 'name="link_file_pwd_dl"' in self.html:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ else:
+ self.html = self.load(url, post=inputs, decode=True)
+
+ self.checkErrors()
+
+ m = re.search(r'var count = (\d+)', self.html)
+ self.setWait(int(m.group(1)) if m else 30)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
+ self.wait()
+
+
+ def checkErrors(self):
+ if '/error.php?' in self.req.lastEffectiveURL or u"Liên kết bạn chọn khÃŽng tồn" in self.html:
+ self.offline()
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logInfo(_("Wait until %s ICT") % m.group(1))
+ wait_until = time.mktime.time(time.strptime.time(m.group(1), "%d/%m/%Y %H:%M"))
+ self.wait(wait_until - time.mktime.time(time.gmtime.time()) - 7 * 60 * 60, True)
+ self.retry()
+ elif '<ul class="message-error">' in self.html:
+ msg = "Unknown error occured or wait time not parsed"
+ self.logError(msg)
+ self.retry(30, 2 * 60, msg)
+
+ self.info.pop('error', None)
diff --git a/pyload/plugin/hoster/Ftp.py b/pyload/plugin/hoster/Ftp.py
new file mode 100644
index 000000000..d26e3ad0b
--- /dev/null
+++ b/pyload/plugin/hoster/Ftp.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import urllib
+import urlparse
+
+from pyload.plugin.Hoster import Hoster
+
+
+class Ftp(Hoster):
+ __name = "Ftp"
+ __type = "hoster"
+ __version = "0.51"
+
+ __pattern = r'(?:ftps?|sftp)://([\w.-]+(:[\w.-]+)?@)?[\w.-]+(:\d+)?/.+'
+
+ __description = """Download from ftp directory"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.com"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+ def process(self, pyfile):
+ parsed_url = urlparse.urlparse(pyfile.url)
+ netloc = parsed_url.netloc
+
+ pyfile.name = parsed_url.path.rpartition('/')[2]
+ try:
+ pyfile.name = urllib.unquote(str(pyfile.name)).decode('utf8')
+ except Exception:
+ pass
+
+ if not "@" in netloc:
+ servers = [x['login'] for x in self.account.getAllAccounts()] if self.account else []
+
+ if netloc in servers:
+ self.logDebug("Logging on to %s" % netloc)
+ self.req.addAuth(self.account.getAccountInfo(netloc)['password'])
+ else:
+ pwd = self.getPassword()
+ if ':' in pwd:
+ self.req.addAuth(pwd)
+
+ self.req.http.c.setopt(pycurl.NOBODY, 1)
+
+ try:
+ res = self.load(pyfile.url)
+ except pycurl.error, e:
+ self.fail(_("Error %d: %s") % e.args)
+
+ self.req.http.c.setopt(pycurl.NOBODY, 0)
+ self.logDebug(self.req.http.header)
+
+ m = re.search(r"Content-Length:\s*(\d+)", res)
+ if m:
+ pyfile.size = int(m.group(1))
+ self.download(pyfile.url)
+ else:
+ # Naive ftp directory listing
+ if re.search(r'^25\d.*?"', self.req.http.header, re.M):
+ pyfile.url = pyfile.url.rstrip('/')
+ pkgname = "/".join(pyfile.package().name, urlparse.urlparse(pyfile.url).path.rpartition('/')[2])
+ pyfile.url += '/'
+ self.req.http.c.setopt(48, 1) #: CURLOPT_DIRLISTONLY
+ res = self.load(pyfile.url, decode=False)
+ links = [pyfile.url + urllib.quote(x) for x in res.splitlines()]
+ self.logDebug("LINKS", links)
+ self.core.api.addPackage(pkgname, links)
+ else:
+ self.fail(_("Unexpected server response"))
diff --git a/pyload/plugin/hoster/GamefrontCom.py b/pyload/plugin/hoster/GamefrontCom.py
new file mode 100644
index 000000000..81568e376
--- /dev/null
+++ b/pyload/plugin/hoster/GamefrontCom.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class GamefrontCom(Hoster):
+ __name = "GamefrontCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?gamefront\.com/files/\w+'
+
+ __description = """Gamefront.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fwannmacher", "felipe@warhammerproject.com")]
+
+
+ PATTERN_FILENAME = r'<title>(.*?) | Game Front'
+ PATTERN_FILESIZE = r'<dt>File Size:</dt>[\n\s]*<dd>(.*?)</dd>'
+ PATTERN_OFFLINE = r'This file doesn\'t exist, or has been removed.'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.html = self.load(pyfile.url, decode=True)
+
+ if not self._checkOnline():
+ self.offline()
+
+ pyfile.name = self._getName()
+
+ link = self._getLink()
+
+ if not link.startswith('http://'):
+ link = "http://www.gamefront.com/" + link
+
+ self.download(link)
+
+
+ def _checkOnline(self):
+ if re.search(self.PATTERN_OFFLINE, self.html):
+ return False
+ else:
+ return True
+
+
+ def _getName(self):
+ name = re.search(self.PATTERN_FILENAME, self.html)
+ if name is None:
+ self.fail(_("Plugin broken")
+
+ return name.group(1)
+
+
+ def _getLink(self):
+ self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=\w+)",
+ self.html).group(1))
+ return re.search("<a href=\"(http://media\d+\.gamefront.com/.*)\">click here</a>", self.html2).group(1).replace("&amp;", "&")
+
+
+def getInfo(urls):
+ result = []
+
+ for url in urls:
+ html = getURL(url)
+
+ if re.search(GamefrontCom.PATTERN_OFFLINE, html):
+ result.append((url, 0, 1, url))
+ else:
+ name = re.search(GamefrontCom.PATTERN_FILENAME, html)
+ if name is None:
+ result.append((url, 0, 1, url))
+ else:
+ name = name.group(1)
+ size = re.search(GamefrontCom.PATTERN_FILESIZE, html)
+ size = parseFileSize(size.group(1))
+
+ result.append((name, size, 3, url))
+
+ yield result
diff --git a/pyload/plugin/hoster/GigapetaCom.py b/pyload/plugin/hoster/GigapetaCom.py
new file mode 100644
index 000000000..a85074e79
--- /dev/null
+++ b/pyload/plugin/hoster/GigapetaCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class GigapetaCom(SimpleHoster):
+ __name = "GigapetaCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """GigaPeta.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
+ SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
+ OFFLINE_PATTERN = r'<div id="page_error">'
+
+ COOKIES = [("gigapeta.com", "lang", "us")]
+
+
+ def handleFree(self, pyfile):
+ captcha_key = str(random.randint(1, 100000000))
+ captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
+
+ for _i in xrange(5):
+ self.checkErrors()
+
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(pyfile.url,
+ post={'captcha_key': captcha_key,
+ 'captcha' : captcha,
+ 'download' : "Download"},
+ follow_location=False)
+
+ m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
+ if m:
+ self.link = m.group(1)
+ break
+ elif "Entered figures don&#96;t coincide with the picture" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("No valid captcha code entered"))
+
+
+ def checkErrors(self):
+ if "All threads for IP" in self.html:
+ self.logDebug("Your IP is already downloading a file")
+ self.wait(5 * 60, True)
+ self.retry()
+
+ self.info.pop('error', None)
diff --git a/pyload/plugin/hoster/GooIm.py b/pyload/plugin/hoster/GooIm.py
new file mode 100644
index 000000000..322dd6101
--- /dev/null
+++ b/pyload/plugin/hoster/GooIm.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class GooIm(SimpleHoster):
+ __name = "GooIm"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?goo\.im/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Goo.im hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'You will be redirected to .*(?P<N>[^/ ]+) in'
+ OFFLINE_PATTERN = r'The file you requested was not found'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ self.wait(10)
+ self.link = pyfile.url
diff --git a/pyload/plugin/hoster/GoogledriveCom.py b/pyload/plugin/hoster/GoogledriveCom.py
new file mode 100644
index 000000000..6e38db427
--- /dev/null
+++ b/pyload/plugin/hoster/GoogledriveCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://drive.google.com/file/d/0B6RNTe4ygItBQm15RnJiTmMyckU/view?pli=1
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import html_unescape
+
+
+class GoogledriveCom(SimpleHoster):
+ __name = "GoogledriveCom"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'https?://(?:www\.)?drive\.google\.com/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Drive.google.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'"og:title" content="(?P<N>.*?)">'
+ OFFLINE_PATTERN = r'align="center"><p class="errorMessage"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ try:
+ link1 = re.search(r'"(https://docs.google.com/uc\?id.*?export=download)",',
+ self.html.decode('unicode-escape')).group(1)
+
+ except AttributeError:
+ self.error(_("Hop #1 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link1)
+
+ self.html = self.load(link1).decode('unicode-escape')
+
+ try:
+ link2 = html_unescape(re.search(r'href="(/uc\?export=download.*?)">',
+ self.html).group(1))
+
+ except AttributeError:
+ self.error(_("Hop #2 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link2)
+
+ link3 = self.load("https://docs.google.com" + link2, just_header=True)
+ self.logDebug("DL-Link: %s" % link3['location'])
+
+ self.link = link3['location']
diff --git a/pyload/plugin/hoster/HellshareCz.py b/pyload/plugin/hoster/HellshareCz.py
new file mode 100644
index 000000000..735ed1f19
--- /dev/null
+++ b/pyload/plugin/hoster/HellshareCz.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class HellshareCz(SimpleHoster):
+ __name = "HellshareCz"
+ __type = "hoster"
+ __version = "0.85"
+
+ __pattern = r'http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Hellshare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ CHECK_TRAFFIC = True
+ LOGIN_ACCOUNT = True
+
+ NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
+ SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
+ OFFLINE_PATTERN = r'<h1>File not found.</h1>'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = bool(self.account)
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/HellspyCz.py b/pyload/plugin/hoster/HellspyCz.py
new file mode 100644
index 000000000..b64becf9c
--- /dev/null
+++ b/pyload/plugin/hoster/HellspyCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class HellspyCz(DeadHoster):
+ __name = "HellspyCz"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj\.pl)(/\S+/\d+)'
+ __config = []
+
+ __description = """HellSpy.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/HostujeNet.py b/pyload/plugin/hoster/HostujeNet.py
new file mode 100644
index 000000000..4dd5bb1c3
--- /dev/null
+++ b/pyload/plugin/hoster/HostujeNet.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class HostujeNet(SimpleHoster):
+ __name__ = "HostujeNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?hostuje\.net/\w+'
+
+ __description__ = """Hostuje.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("GammaC0de", None)]
+
+
+ NAME_PATTERN = r'<input type="hidden" name="name" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'<b>Rozmiar:</b> (?P<S>[\d.,]+) (?P<U>[\w^_]+)<br>'
+ OFFLINE_PATTERN = ur'Podany plik nie został odnaleziony\.\.\.'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ m = re.search(r'<script src="([\w^_]+.php)"></script>', self.html)
+ if m:
+ jscript = self.load("http://hostuje.net/" + m.group(1))
+ m = re.search(r"\('(\w+\.php\?i=\w+)'\);", jscript)
+ if m:
+ self.load("http://hostuje.net/" + m.group(1))
+ else:
+ self.error(_("unexpected javascript format"))
+ else:
+ self.error(_("script not found"))
+
+ action, inputs = self.parseHtmlForm(pyfile.url.replace(".", "\.").replace( "?", "\?"))
+ if not action:
+ self.error(_("form not found"))
+
+ self.download(action, post=inputs)
diff --git a/pyload/plugin/hoster/HotfileCom.py b/pyload/plugin/hoster/HotfileCom.py
new file mode 100644
index 000000000..743c24e23
--- /dev/null
+++ b/pyload/plugin/hoster/HotfileCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class HotfileCom(DeadHoster):
+ __name = "HotfileCom"
+ __type = "hoster"
+ __version = "0.37"
+
+ __pattern = r'https?://(?:www\.)?hotfile\.com/dl/\d+/\w+'
+ __config = []
+
+ __description = """Hotfile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sitacuisses", "sitacuisses@yhoo.de"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("JoKoT3", "jokot3@gmail.com")]
diff --git a/pyload/plugin/hoster/HugefilesNet.py b/pyload/plugin/hoster/HugefilesNet.py
new file mode 100644
index 000000000..25a337739
--- /dev/null
+++ b/pyload/plugin/hoster/HugefilesNet.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class HugefilesNet(XFSHoster):
+ __name = "HugefilesNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?hugefiles\.net/\w{12}'
+
+ __description = """Hugefiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ SIZE_PATTERN = r'File Size:</span>\s*<span.*?>(?P<S>[^<]+)</span></div>'
+
+ FORM_INPUTS_MAP = {'ctype': re.compile(r'\d+')}
diff --git a/pyload/plugin/hoster/HundredEightyUploadCom.py b/pyload/plugin/hoster/HundredEightyUploadCom.py
new file mode 100644
index 000000000..de312245e
--- /dev/null
+++ b/pyload/plugin/hoster/HundredEightyUploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class HundredEightyUploadCom(XFSHoster):
+ __name = "HundredEightyUploadCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?180upload\.com/\w{12}'
+
+ __description = """180upload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ OFFLINE_PATTERN = r'>File Not Found'
diff --git a/pyload/plugin/hoster/IFileWs.py b/pyload/plugin/hoster/IFileWs.py
new file mode 100644
index 000000000..57c86d490
--- /dev/null
+++ b/pyload/plugin/hoster/IFileWs.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IFileWs(DeadHoster):
+ __name = "IFileWs"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?ifile\.ws/\w{12}'
+ __config = []
+
+ __description = """Ifile.ws hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
diff --git a/pyload/plugin/hoster/IcyFilesCom.py b/pyload/plugin/hoster/IcyFilesCom.py
new file mode 100644
index 000000000..88ec57a98
--- /dev/null
+++ b/pyload/plugin/hoster/IcyFilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IcyFilesCom(DeadHoster):
+ __name = "IcyFilesCom"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?icyfiles\.com/(.+)'
+ __config = []
+
+ __description = """IcyFiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/hoster/IfileIt.py b/pyload/plugin/hoster/IfileIt.py
new file mode 100644
index 000000000..647e06c01
--- /dev/null
+++ b/pyload/plugin/hoster/IfileIt.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IfileIt(DeadHoster):
+ __name = "IfileIt"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'^unmatchable$'
+ __config = []
+
+ __description = """Ifile.it"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/IfolderRu.py b/pyload/plugin/hoster/IfolderRu.py
new file mode 100644
index 000000000..d16dc695c
--- /dev/null
+++ b/pyload/plugin/hoster/IfolderRu.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class IfolderRu(SimpleHoster):
+ __name = "IfolderRu"
+ __type = "hoster"
+ __version = "0.39"
+
+ __pattern = r'http://(?:www)?(files\.)?(ifolder\.ru|metalarea\.org|rusfolder\.(com|net|ru))/(files/)?(?P<ID>\d+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Ifolder.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
+
+ NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
+ SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
+ OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>.*?</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
+
+ SESSION_ID_PATTERN = r'<input type="hidden" name="session" value="(.+?)"'
+ INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "(.+?)";\}'
+ HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\''
+
+ LINK_FREE_PATTERN = r'<a href="(.+?)" class="downloadbutton_files"'
+
+ WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = bool(self.account)
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ url = "http://rusfolder.com/%s" % self.info['pattern']['ID']
+ self.html = self.load("http://rusfolder.com/%s" % self.info['pattern']['ID'], decode=True)
+ self.getFileInfo()
+
+ session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
+
+ captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
+ for _i in xrange(5):
+ action, inputs = self.parseHtmlForm('id="download-step-one-form"')
+ inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
+ inputs['action'] = '1'
+ self.logDebug(inputs)
+
+ self.html = self.load(url, decode=True, post=inputs)
+ if self.WRONG_CAPTCHA_PATTERN in self.html:
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("Invalid captcha"))
+
+ self.link = re.search(self.LINK_FREE_PATTERN, self.html).group(1)
diff --git a/pyload/plugin/hoster/JumbofilesCom.py b/pyload/plugin/hoster/JumbofilesCom.py
new file mode 100644
index 000000000..69e2c645a
--- /dev/null
+++ b/pyload/plugin/hoster/JumbofilesCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class JumbofilesCom(SimpleHoster):
+ __name = "JumbofilesCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?jumbofiles\.com/(?P<ID>\w{12})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """JumboFiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
+
+
+ INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
+ LINK_FREE_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ post_data = {"id": self.info['pattern']['ID'], "op": "download3", "rand": ""}
+ html = self.load(self.pyfile.url, post=post_data, decode=True)
+ self.link = re.search(self.LINK_FREE_PATTERN, html).group(1)
diff --git a/pyload/plugin/hoster/JunocloudMe.py b/pyload/plugin/hoster/JunocloudMe.py
new file mode 100644
index 000000000..9054734fb
--- /dev/null
+++ b/pyload/plugin/hoster/JunocloudMe.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class JunocloudMe(XFSHoster):
+ __name = "JunocloudMe"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:\w+\.)?junocloud\.me/\w{12}'
+
+ __description = """Junocloud.me hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")]
+
+ OFFLINE_PATTERN = r'>No such file with this filename<'
+ TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
diff --git a/pyload/plugin/hoster/Keep2ShareCc.py b/pyload/plugin/hoster/Keep2ShareCc.py
new file mode 100644
index 000000000..05dafffa8
--- /dev/null
+++ b/pyload/plugin/hoster/Keep2ShareCc.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class Keep2ShareCc(SimpleHoster):
+ __name = "Keep2ShareCc"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Keep2Share.cc hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://keep2s.cc/file/\g<ID>")]
+
+ NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
+ SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
+
+ OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
+ TEMP_OFFLINE_PATTERN = r'Downloading blocked due to'
+
+ LINK_FREE_PATTERN = r'"(.+?url.html\?file=.+?)"|window\.location\.href = \'(.+?)\';'
+ LINK_PREMIUM_PATTERN = r'window\.location\.href = \'(.+?)\';'
+
+ CAPTCHA_PATTERN = r'src="(/file/captcha\.html.+?)"'
+
+ WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
+ TEMP_ERROR_PATTERN = r'>\s*(Download count files exceed|Traffic limit exceed|Free account does not allow to download more than one file at the same time)'
+ ERROR_PATTERN = r'>\s*(Free user can\'t download large files|You no can access to this file|This download available only for premium users|This is private file)'
+
+
+ def checkErrors(self):
+ m = re.search(self.TEMP_ERROR_PATTERN, self.html)
+ if m:
+ self.info['error'] = m.group(1)
+ self.wantReconnect = True
+ self.retry(wait_time=30 * 60, reason=m.group(0))
+
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = m.group(1)
+ self.error(errmsg)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logDebug("Hoster told us to wait for %s" % m.group(1))
+
+ # string to time convert courtesy of https://stackoverflow.com/questions/10663720
+ ftr = [3600, 60, 1]
+ wait_time = sum(a * b for a, b in zip(ftr, map(int, m.group(1).split(':'))))
+
+ self.wantReconnect = True
+ self.retry(wait_time=wait_time, reason="Please wait to download this file")
+
+ self.info.pop('error', None)
+
+
+ def handleFree(self, pyfile):
+ self.fid = re.search(r'<input type="hidden" name="slow_id" value="(.+?)">', self.html).group(1)
+ self.html = self.load(pyfile.url, post={'yt0': '', 'slow_id': self.fid})
+
+ # self.logDebug(self.fid)
+ # self.logDebug(pyfile.url)
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.handleCaptcha()
+ self.wait(31)
+ self.html = self.load(pyfile.url)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
+
+
+ def handleCaptcha(self):
+ post_data = {'free' : 1,
+ 'freeDownloadRequest': 1,
+ 'uniqueId' : self.fid,
+ 'yt0' : ''}
+
+ m = re.search(r'id="(captcha\-form)"', self.html)
+ self.logDebug("captcha-form found %s" % m)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ self.logDebug("CAPTCHA_PATTERN found %s" % m)
+ if m:
+ captcha_url = urlparse.urljoin("http://keep2s.cc/", m.group(1))
+ post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url)
+ else:
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(self.pyfile.url, post=post_data)
+
+ if 'verification code is incorrect' not in self.html:
+ self.correctCaptcha()
+ else:
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/KickloadCom.py b/pyload/plugin/hoster/KickloadCom.py
new file mode 100644
index 000000000..11c79cef9
--- /dev/null
+++ b/pyload/plugin/hoster/KickloadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class KickloadCom(DeadHoster):
+ __name = "KickloadCom"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?kickload\.com/get/.+'
+ __config = []
+
+ __description = """Kickload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/KingfilesNet.py b/pyload/plugin/hoster/KingfilesNet.py
new file mode 100644
index 000000000..557f1f836
--- /dev/null
+++ b/pyload/plugin/hoster/KingfilesNet.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class KingfilesNet(SimpleHoster):
+ __name = "KingfilesNet"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Kingfiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'name="fname" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'>Size: .+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>(File Not Found</b><br><br>|File Not Found</h2>)'
+
+ RAND_ID_PATTERN = r'type=\"hidden\" name=\"rand\" value=\"(.+)\">'
+
+ LINK_FREE_PATTERN = r'var download_url = \'(.+)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ # Click the free user button
+ post_data = {'op' : "download1",
+ 'usr_login' : "",
+ 'id' : self.info['pattern']['ID'],
+ 'fname' : pyfile.name,
+ 'referer' : "",
+ 'method_free': "+"}
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ solvemedia = SolveMedia(self)
+ response, challenge = solvemedia.challenge()
+
+ # Make the downloadlink appear and load the file
+ m = re.search(self.RAND_ID_PATTERN, self.html)
+ if m is None:
+ self.error(_("Random key not found"))
+
+ rand = m.group(1)
+ self.logDebug("rand = ", rand)
+
+ post_data = {'op' : "download2",
+ 'id' : self.info['pattern']['ID'],
+ 'rand' : rand,
+ 'referer' : pyfile.url,
+ 'method_free' : "+",
+ 'method_premium' : "",
+ 'adcopy_response' : response,
+ 'adcopy_challenge': challenge,
+ 'down_direct' : "1"}
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/LemUploadsCom.py b/pyload/plugin/hoster/LemUploadsCom.py
new file mode 100644
index 000000000..c43867a99
--- /dev/null
+++ b/pyload/plugin/hoster/LemUploadsCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class LemUploadsCom(DeadHoster):
+ __name = "LemUploadsCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?lemuploads\.com/\w{12}'
+ __config = []
+
+ __description = """LemUploads.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/LetitbitNet.py b/pyload/plugin/hoster/LetitbitNet.py
new file mode 100644
index 000000000..85fd55b89
--- /dev/null
+++ b/pyload/plugin/hoster/LetitbitNet.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+#
+# API Documentation:
+# http://api.letitbit.net/reg/static/api.pdf
+#
+# Test links:
+# http://letitbit.net/download/07874.0b5709a7d3beee2408bb1f2eefce/random.bin.html
+
+import re
+import urlparse
+
+from pyload.utils import json_loads, json_dumps
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+def api_response(url):
+ json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
+ api_rep = getURL("http://api.letitbit.net/json",
+ post={'r': json_dumps(json_data)})
+ return json_loads(api_rep)
+
+
+def getInfo(urls):
+ for url in urls:
+ api_rep = api_response(url)
+ if api_rep['status'] == 'OK':
+ info = api_rep['data'][0]
+ yield (info['name'], info['size'], 2, url)
+ else:
+ yield (url, 0, 1, url)
+
+
+class LetitbitNet(SimpleHoster):
+ __name = "LetitbitNet"
+ __type = "hoster"
+ __version = "0.30"
+
+ __pattern = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Letitbit.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("z00nx", "z00nx0@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
+
+ SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
+ CAPTCHA_CONTROL_FIELD = r'recaptcha_control_field\s=\s\'(.+?)\''
+
+
+ def setup(self):
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ action, inputs = self.parseHtmlForm('id="ifree_form"')
+ if not action:
+ self.error(_("ifree_form"))
+
+ pyfile.size = float(inputs['sssize'])
+ self.logDebug(action, inputs)
+ inputs['desc'] = ""
+
+ self.html = self.load(urlparse.urljoin("http://letitbit.net/", action), post=inputs)
+
+ m = re.search(self.SECONDS_PATTERN, self.html)
+ seconds = int(m.group(1)) if m else 60
+
+ self.logDebug("Seconds found", seconds)
+
+ m = re.search(self.CAPTCHA_CONTROL_FIELD, self.html)
+ recaptcha_control_field = m.group(1)
+
+ self.logDebug("ReCaptcha control field found", recaptcha_control_field)
+
+ self.wait(seconds)
+
+ res = self.load("http://letitbit.net/ajax/download3.php", post=" ")
+ if res != '1':
+ self.error(_("Unknown response - ajax_check_url"))
+
+ self.logDebug(res)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ post_data = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field": response,
+ "recaptcha_control_field": recaptcha_control_field}
+
+ self.logDebug("Post data to send", post_data)
+
+ res = self.load("http://letitbit.net/ajax/check_recaptcha.php", post=post_data)
+
+ self.logDebug(res)
+
+ if not res:
+ self.invalidCaptcha()
+
+ if res == "error_free_download_blocked":
+ self.logWarning(_("Daily limit reached"))
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ if res == "error_wrong_captcha":
+ self.invalidCaptcha()
+ self.retry()
+
+ elif res.startswith('['):
+ urls = json_loads(res)
+
+ elif res.startswith('http://'):
+ urls = [res]
+
+ else:
+ self.error(_("Unknown response - captcha check"))
+
+ self.link = urls[0]
+
+
+ def handlePremium(self, pyfile):
+ api_key = self.user
+ premium_key = self.account.getAccountData(self.user)['password']
+
+ json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": pyfile.url}]]
+ api_rep = self.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
+ self.logDebug("API Data: " + api_rep)
+ api_rep = json_loads(api_rep)
+
+ if api_rep['status'] == 'FAIL':
+ self.fail(api_rep['data'])
+
+ self.link = api_rep['data'][0][0]
diff --git a/pyload/plugin/hoster/LinksnappyCom.py b/pyload/plugin/hoster/LinksnappyCom.py
new file mode 100644
index 000000000..186639a81
--- /dev/null
+++ b/pyload/plugin/hoster/LinksnappyCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class LinksnappyCom(MultiHoster):
+ __name = "LinksnappyCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:[^/]+\.)?linksnappy\.com'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Linksnappy.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ SINGLE_CHUNK_HOSTERS = ["easybytez.com"]
+
+
+ def handlePremium(self, pyfile):
+ host = self._get_host(pyfile.url)
+ json_params = json_dumps({'link' : pyfile.url,
+ 'type' : host,
+ 'username': self.user,
+ 'password': self.account.getAccountData(self.user)['password']})
+
+ r = self.load("http://gen.linksnappy.com/genAPI.php",
+ post={'genLinks': json_params})
+
+ self.logDebug("JSON data: " + r)
+
+ j = json_loads(r)['links'][0]
+
+ if j['error']:
+ self.error(_("Error converting the link"))
+
+ pyfile.name = j['filename']
+ self.link = j['generated']
+
+ if host in self.SINGLE_CHUNK_HOSTERS:
+ self.chunkLimit = 1
+ else:
+ self.setup()
+
+
+ @staticmethod
+ def _get_host(url):
+ host = urlparse.urlsplit(url).netloc
+ return re.search(r'[\w-]+\.\w+$', host).group(0)
diff --git a/pyload/plugin/hoster/LoadTo.py b/pyload/plugin/hoster/LoadTo.py
new file mode 100644
index 000000000..3a625dbe3
--- /dev/null
+++ b/pyload/plugin/hoster/LoadTo.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.load.to/JWydcofUY6/random.bin
+# http://www.load.to/oeSmrfkXE/random100.bin
+
+import re
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class LoadTo(SimpleHoster):
+ __name = "LoadTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?load\.to/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Load.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("halfman", "Pulpan3@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
+ SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>Can\'t find file'
+
+ LINK_FREE_PATTERN = r'<form method="post" action="(.+?)"'
+ WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
+
+ URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ # Search for Download URL
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
+
+ # Set Timer - may be obsolete
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+ # Load.to is using solvemedia captchas since ~july 2014:
+ solvemedia = SolveMedia(self)
+ captcha_key = solvemedia.detect_key()
+
+ if captcha_key:
+ response, challenge = solvemedia.challenge(captcha_key)
+ self.download(self.link,
+ post={'adcopy_challenge': challenge,
+ 'adcopy_response' : response,
+ 'returnUrl' : pyfile.url})
diff --git a/pyload/plugin/hoster/LolabitsEs.py b/pyload/plugin/hoster/LolabitsEs.py
new file mode 100644
index 000000000..890cb0239
--- /dev/null
+++ b/pyload/plugin/hoster/LolabitsEs.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*
+
+import HTMLParser
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class LolabitsEs(SimpleHoster):
+ __name = "LolabitsEs"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?lolabits\.es/.+'
+
+ __description = """Lolabits.es hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'Descargar: <b>(?P<N>.+?)<'
+ SIZE_PATTERN = r'class="fileSize">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'Un usuario con este nombre no existe'
+
+ FILEID_PATTERN = r'name="FileId" value="(\d+)"'
+ TOKEN_PATTERN = r'name="__RequestVerificationToken" type="hidden" value="(.+?)"'
+ LINK_PATTERN = r'"redirectUrl":"(.+?)"'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ fileid = re.search(self.FILEID_PATTERN, self.html).group(1)
+ self.logDebug("FileID: " + fileid)
+
+ token = re.search(self.TOKEN_PATTERN, self.html).group(1)
+ self.logDebug("Token: " + token)
+
+ self.html = self.load("http://lolabits.es/action/License/Download",
+ post={'fileId' : fileid,
+ '__RequestVerificationToken' : token}).decode('unicode-escape')
+
+ self.link = HTMLParser.HTMLParser().unescape(re.search(self.LINK_PATTERN, self.html).group(1))
diff --git a/pyload/plugin/hoster/LomafileCom.py b/pyload/plugin/hoster/LomafileCom.py
new file mode 100644
index 000000000..de56d2d58
--- /dev/null
+++ b/pyload/plugin/hoster/LomafileCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class LomafileCom(DeadHoster):
+ __name = "LomafileCom"
+ __type = "hoster"
+ __version = "0.52"
+
+ __pattern = r'http://lomafile\.com/\w{12}'
+ __config = []
+
+ __description = """Lomafile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"),
+ ("guidobelix", "guidobelix@hotmail.it")]
diff --git a/pyload/plugin/hoster/LuckyShareNet.py b/pyload/plugin/hoster/LuckyShareNet.py
new file mode 100644
index 000000000..6dff7d517
--- /dev/null
+++ b/pyload/plugin/hoster/LuckyShareNet.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from bottle import json_loads
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class LuckyShareNet(SimpleHoster):
+ __name = "LuckyShareNet"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """LuckyShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = r'<h1 class=\'file_name\'>(?P<N>\S+)</h1>\s*<span class=\'file_size\'>Filesize: (?P<S>[\d.,]+)(?P<U>[\w^_]+)</span>'
+ OFFLINE_PATTERN = r'There is no such file available'
+
+
+ def parseJson(self, rep):
+ if 'AJAX Error' in rep:
+ html = self.load(self.pyfile.url, decode=True)
+ m = re.search(r"waitingtime = (\d+);", html)
+ if m:
+ seconds = int(m.group(1))
+ self.logDebug("You have to wait %d seconds between free downloads" % seconds)
+ self.retry(wait_time=seconds)
+ else:
+ self.error(_("Unable to detect wait time between free downloads"))
+ elif 'Hash expired' in rep:
+ self.retry(reason=_("Hash expired"))
+ return json_loads(rep)
+
+
+ # TODO: There should be a filesize limit for free downloads
+ # TODO: Some files could not be downloaded in free mode
+
+
+ def handleFree(self, pyfile):
+ rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + self.info['pattern']['ID'], decode=True)
+
+ self.logDebug("JSON: " + rep)
+
+ json = self.parseJson(rep)
+ self.wait(json['time'])
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge()
+ rep = self.load(r"http://luckyshare.net/download/verify/challenge/%s/response/%s/hash/%s" %
+ (challenge, response, json['hash']), decode=True)
+ self.logDebug("JSON: " + rep)
+ if 'link' in rep:
+ json.update(self.parseJson(rep))
+ self.correctCaptcha()
+ break
+ elif 'Verification failed' in rep:
+ self.invalidCaptcha()
+ else:
+ self.error(_("Unable to get downlaod link"))
+
+ if not json['link']:
+ self.fail(_("No Download url retrieved/all captcha attempts failed"))
+
+ self.link = json['link']
diff --git a/pyload/plugin/hoster/MediafireCom.py b/pyload/plugin/hoster/MediafireCom.py
new file mode 100644
index 000000000..d05a51479
--- /dev/null
+++ b/pyload/plugin/hoster/MediafireCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MediafireCom(SimpleHoster):
+ __name = "MediafireCom"
+ __type = "hoster"
+ __version = "0.86"
+
+ __pattern = r'https?://(?:www\.)?mediafire\.com/(file/|view/\??|download(\.php\?|/)|\?)\w{15}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Mediafire.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>.+?)"/>'
+ SIZE_PATTERN = r'<li>File size: <span>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ INFO_PATTERN = r'oFileSharePopup\.ald\(\'.*?\',\'(?P<N>.+?)\',\'(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\',\'\',\'(?P<H>.+?)\'\)'
+ OFFLINE_PATTERN = r'class="error_msg_title"'
+
+ LINK_FREE_PATTERN = r'kNO = "(.+?)"'
+
+ PASSWORD_PATTERN = r'<form name="form_password"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ solvemedia = SolveMedia(self)
+ captcha_key = solvemedia.detect_key()
+
+ if captcha_key:
+ response, challenge = solvemedia.challenge(captcha_key)
+ self.html = self.load(pyfile.url,
+ post={'adcopy_challenge': challenge,
+ 'adcopy_response' : response},
+ decode=True)
+
+ if self.PASSWORD_PATTERN in self.html:
+ password = self.getPassword()
+
+ if not password:
+ self.fail(_("No password found"))
+ else:
+ self.logInfo(_("Password protected link, trying: ") + password)
+ self.html = self.load(self.link, post={'downloadp': password})
+
+ if self.PASSWORD_PATTERN in self.html:
+ self.fail(_("Incorrect password"))
+
+ return super(MediafireCom, self).handleFree(pyfile)
diff --git a/pyload/plugin/hoster/MegaCoNz.py b/pyload/plugin/hoster/MegaCoNz.py
new file mode 100644
index 000000000..2d7b40d98
--- /dev/null
+++ b/pyload/plugin/hoster/MegaCoNz.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+
+import array
+import os
+# import pycurl
+import random
+import re
+
+from base64 import standard_b64decode
+
+from Crypto.Cipher import AES
+from Crypto.Util import Counter
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import decode, fs_decode, fs_encode
+
+
+############################ General errors ###################################
+# EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred
+# EARGS (-2): You have passed invalid arguments to this command
+# EAGAIN (-3): (always at the request level) A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff
+# ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)
+#
+############################ Upload errors ####################################
+# EFAILED (-5): The upload failed. Please restart it from scratch
+# ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL
+# ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary
+# EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one
+#
+############################ Stream/System errors #############################
+# ENOENT (-9): Object (typically, node or user) not found
+# ECIRCULAR (-10): Circular linkage attempted
+# EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
+# EEXIST (-12): Trying to create an object that already exists
+# EINCOMPLETE (-13): Trying to access an incomplete resource
+# EKEY (-14): A decryption operation failed (never returned by the API)
+# ESID (-15): Invalid or expired user session, please relogin
+# EBLOCKED (-16): User blocked
+# EOVERQUOTA (-17): Request over quota
+# ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
+# ETOOMANYCONNECTIONS (-19): Too many connections on this resource
+# EWRITE (-20): Write failed
+# EREAD (-21): Read failed
+# EAPPKEY (-22): Invalid application key; request not processed
+
+
+class MegaCoNz(Hoster):
+ __name = "MegaCoNz"
+ __type = "hoster"
+ __version = "0.26"
+
+ __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#(?P<TYPE>N|)!(?P<ID>[\w^_]+)!(?P<KEY>[\w,-]+)'
+
+ __description = """Mega.co.nz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "ranan@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "https://eu.api.mega.co.nz/cs"
+ FILE_SUFFIX = ".crypted"
+
+
+ def b64_decode(self, data):
+ data = data.replace("-", "+").replace("_", "/")
+ return standard_b64decode(data + '=' * (-len(data) % 4))
+
+
+ def getCipherKey(self, key):
+ """ Construct the cipher key from the given data """
+ a = array.array("I", self.b64_decode(key))
+
+ k = array.array("I", (a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]))
+ iv = a[4:6] + array.array("I", (0, 0))
+ meta_mac = a[6:8]
+
+ return k, iv, meta_mac
+
+
+ def api_response(self, **kwargs):
+ """ Dispatch a call to the api, see https://mega.co.nz/#developers """
+
+ # generate a session id, no idea where to obtain elsewhere
+ uid = random.random.randint(10 << 9, 10 ** 10)
+
+ res = self.load(self.API_URL, get={'id': uid}, post=json_dumps([kwargs]))
+ self.logDebug("Api Response: " + res)
+ return json_loads(res)
+
+
+ def decryptAttr(self, data, key):
+ k, iv, meta_mac = self.getCipherKey(key)
+ cbc = AES.new(k, AES.MODE_CBC, "\0" * 16)
+ attr = decode(cbc.decrypt(self.b64_decode(data)))
+
+ self.logDebug("Decrypted Attr: %s" % attr)
+ if not attr.startswith("MEGA"):
+ self.fail(_("Decryption failed"))
+
+ # Data is padded, 0-bytes must be stripped
+ return json_loads(re.search(r'{.+?}', attr).group(0))
+
+
+ def decryptFile(self, key):
+ """ Decrypts the file at lastDownload` """
+
+ # upper 64 bit of counter start
+ n = self.b64_decode(key)[16:24]
+
+ # convert counter to long and shift bytes
+ k, iv, meta_mac = self.getCipherKey(key)
+ ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
+ cipher = AES.new(k, AES.MODE_CTR, counter=ctr)
+
+ self.pyfile.setStatus("decrypting")
+ self.pyfile.setProgress(0)
+
+ file_crypted = fs_encode(self.lastDownload)
+ file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
+
+ try:
+ f = open(file_crypted, "rb")
+ df = open(file_decrypted, "wb")
+
+ except IOError, e:
+ self.fail(e)
+
+ chunk_size = 2 ** 15 #: buffer size, 32k
+ # file_mac = [0, 0, 0, 0] #: calculate CBC-MAC for checksum
+
+ chunks = os.path.getsize(file_crypted) / chunk_size + 1
+ for i in xrange(chunks):
+ buf = f.read(chunk_size)
+ if not buf:
+ break
+
+ chunk = cipher.decrypt(buf)
+ df.write(chunk)
+
+ self.pyfile.setProgress(int((100.0 / chunks) * i))
+
+ # chunk_mac = [iv[0], iv[1], iv[0], iv[1]]
+ # for i in xrange(0, chunk_size, 16):
+ # block = chunk[i:i+16]
+ # if len(block) % 16:
+ # block += '=' * (16 - (len(block) % 16))
+ # block = array.array("I", block)
+
+ # chunk_mac = [chunk_mac[0] ^ a_[0], chunk_mac[1] ^ block[1], chunk_mac[2] ^ block[2], chunk_mac[3] ^ block[3]]
+ # chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k)
+
+ # file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1], file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]]
+ # file_mac = aes_cbc_encrypt_a32(file_mac, k)
+
+ self.pyfile.setProgress(100)
+
+ f.close()
+ df.close()
+
+ # if file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] != meta_mac:
+ # os.remove(file_decrypted)
+ # self.fail(_("Checksum mismatch"))
+
+ os.remove(file_crypted)
+ self.lastDownload = fs_decode(file_decrypted)
+
+
+ def checkError(self, code):
+ ecode = abs(code)
+
+ if ecode in (9, 16, 21):
+ self.offline()
+
+ elif ecode in (3, 13, 17, 18, 19):
+ self.tempOffline()
+
+ elif ecode in (1, 4, 6, 10, 15, 21):
+ self.retry(5, 30, _("Error code: [%s]") % -ecode)
+
+ else:
+ self.fail(_("Error code: [%s]") % -ecode)
+
+
+ def process(self, pyfile):
+ pattern = re.match(self.__pattern, pyfile.url).groupdict()
+ id = pattern['ID']
+ key = pattern['KEY']
+ public = pattern['TYPE'] == ''
+
+ self.logDebug("ID: %s" % id, "Key: %s" % key, "Type: %s" % ("public" if public else "node"))
+
+ # g is for requesting a download url
+ # this is similar to the calls in the mega js app, documentation is very bad
+ if public:
+ mega = self.api_response(a="g", g=1, p=id, ssl=1)[0]
+ else:
+ mega = self.api_response(a="g", g=1, n=id, ssl=1)[0]
+
+ if isinstance(mega, int):
+ self.checkError(mega)
+ elif "e" in mega:
+ self.checkError(mega['e'])
+
+ attr = self.decryptAttr(mega['at'], key)
+
+ pyfile.name = attr['n'] + self.FILE_SUFFIX
+ pyfile.size = mega['s']
+
+ # self.req.http.c.setopt(pycurl.SSL_CIPHER_LIST, "RC4-MD5:DEFAULT")
+
+ self.download(mega['g'])
+
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = attr['n']
diff --git a/pyload/plugin/hoster/MegaDebridEu.py b/pyload/plugin/hoster/MegaDebridEu.py
new file mode 100644
index 000000000..4b2604046
--- /dev/null
+++ b/pyload/plugin/hoster/MegaDebridEu.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class MegaDebridEu(MultiHoster):
+ __name = "MegaDebridEu"
+ __type = "hoster"
+ __version = "0.47"
+
+ __pattern = r'http://((?:www\d+\.|s\d+\.)?mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """mega-debrid.eu multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def api_load(self):
+ """
+ Connexion to the mega-debrid API
+ Return True if succeed
+ """
+ user, data = self.account.selectAccount()
+ jsonResponse = self.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ res = json_loads(jsonResponse)
+
+ if res['response_code'] == "ok":
+ self.token = res['token']
+ return True
+ else:
+ return False
+
+
+ def handlePremium(self, pyfile):
+ """
+ Debrid a link
+ Return The debrided link if succeed or original link if fail
+ """
+ if not self.api_load():
+ self.error("Unable to connect to remote API")
+
+ jsonResponse = self.load(self.API_URL,
+ get={'action': 'getLink', 'token': self.token},
+ post={'link': pyfile.url})
+
+ res = json_loads(jsonResponse)
+ if res['response_code'] == "ok":
+ self.link = res['debridLink'][1:-1]
diff --git a/pyload/plugin/hoster/MegaFilesSe.py b/pyload/plugin/hoster/MegaFilesSe.py
new file mode 100644
index 000000000..51667c786
--- /dev/null
+++ b/pyload/plugin/hoster/MegaFilesSe.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegaFilesSe(DeadHoster):
+ __name = "MegaFilesSe"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?megafiles\.se/\w{12}'
+ __config = []
+
+ __description = """MegaFiles.se hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/MegaRapidCz.py b/pyload/plugin/hoster/MegaRapidCz.py
new file mode 100644
index 000000000..d7ed78202
--- /dev/null
+++ b/pyload/plugin/hoster/MegaRapidCz.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ h = getRequest()
+ h.c.setopt(pycurl.HTTPHEADER,
+ ["Accept: text/html",
+ "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"])
+
+ for url in urls:
+ html = h.load(url, decode=True)
+ yield parseFileInfo(MegaRapidCz, url, html)
+
+
+class MegaRapidCz(SimpleHoster):
+ __name = "MegaRapidCz"
+ __type = "hoster"
+ __version = "0.56"
+
+ __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MegaRapid.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("MikyWoW", "mikywow@seznam.cz"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h1.*?><span.*?>(?:<a.*?>)?(?P<N>[^<]+)'
+ SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong></td>'
+ OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
+
+ CHECK_TRAFFIC = True
+
+ LINK_PREMIUM_PATTERN = r'<a href="(.+?)" title="Stahnout">([^<]+)</a>'
+
+ ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahování je přístupné pouze přihlášenÜm uÅŸivatelům'
+ ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahování zdarma je moÅŸné jen přes náš'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+
+
+ def handlePremium(self, pyfile):
+ m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ else:
+ if re.search(self.ERR_LOGIN_PATTERN, self.html):
+ self.relogin(self.user)
+ self.retry(wait_time=60, reason=_("User login failed"))
+ elif re.search(self.ERR_CREDIT_PATTERN, self.html):
+ self.fail(_("Not enough credit left"))
+ else:
+ self.fail(_("Download link not found"))
diff --git a/pyload/plugin/hoster/MegaRapidoNet.py b/pyload/plugin/hoster/MegaRapidoNet.py
new file mode 100644
index 000000000..1be61f235
--- /dev/null
+++ b/pyload/plugin/hoster/MegaRapidoNet.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import random
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+def random_with_N_digits(n):
+ rand = "0."
+ not_zero = 0
+ for _i in xrange(1, n + 1):
+ r = random.randint(0, 9)
+ if(r > 0):
+ not_zero += 1
+ rand += str(r)
+
+ if not_zero > 0:
+ return rand
+ else:
+ return random_with_N_digits(n)
+
+
+class MegaRapidoNet(MultiHoster):
+ __name = "MegaRapidoNet"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?\w+\.megarapido\.net/\?file=\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MegaRapido.net multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ LINK_PREMIUM_PATTERN = r'<\s*?a[^>]*?title\s*?=\s*?["\'].*?download["\'][^>]*?href=["\']([^"\']+)'
+
+ ERROR_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']?alert-message error.*?>([^<]*)'
+
+
+ def handlePremium(self, pyfile):
+ self.html = self.load("http://megarapido.net/gerar.php",
+ post={'rand' :random_with_N_digits(16),
+ 'urllist' : pyfile.url,
+ 'links' : pyfile.url,
+ 'exibir' : "normal",
+ 'usar' : "premium",
+ 'user' : self.account.getAccountInfo(self.user).get('sid', None),
+ 'autoreset': ""})
+
+ if "desloga e loga novamente para gerar seus links" in self.html.lower():
+ self.error("You have logged in at another place")
+
+ return super(MegaRapidoNet, self).handlePremium(pyfile)
diff --git a/pyload/plugin/hoster/MegacrypterCom.py b/pyload/plugin/hoster/MegacrypterCom.py
new file mode 100644
index 000000000..264ad958d
--- /dev/null
+++ b/pyload/plugin/hoster/MegacrypterCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads, json_dumps
+
+from pyload.plugin.hoster.MegaCoNz import MegaCoNz
+
+
+class MegacrypterCom(MegaCoNz):
+ __name = "MegacrypterCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'https?://\w{0,10}\.?megacrypter\.com/[\w!-]+'
+
+ __description = """Megacrypter.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("GonzaloSR", "gonzalo@gonzalosr.com")]
+
+
+ API_URL = "http://megacrypter.com/api"
+ FILE_SUFFIX = ".crypted"
+
+
+ def api_response(self, **kwargs):
+ """ Dispatch a call to the api, see megacrypter.com/api_doc """
+ self.logDebug("JSON request: " + json_dumps(kwargs))
+ res = self.load(self.API_URL, post=json_dumps(kwargs))
+ self.logDebug("API Response: " + res)
+ return json_loads(res)
+
+
+ def process(self, pyfile):
+ # match is guaranteed because plugin was chosen to handle url
+ node = re.match(self.__pattern, pyfile.url).group(0)
+
+ # get Mega.co.nz link info
+ info = self.api_response(link=node, m="info")
+
+ # get crypted file URL
+ dl = self.api_response(link=node, m="dl")
+
+ # TODO: map error codes, implement password protection
+ # if info['pass'] is True:
+ # crypted_file_key, md5_file_key = info['key'].split("#")
+
+ key = self.b64_decode(info['key'])
+
+ pyfile.name = info['name'] + self.FILE_SUFFIX
+
+ self.download(dl['url'])
+
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = info['name']
diff --git a/pyload/plugin/hoster/MegareleaseOrg.py b/pyload/plugin/hoster/MegareleaseOrg.py
new file mode 100644
index 000000000..65f1242a9
--- /dev/null
+++ b/pyload/plugin/hoster/MegareleaseOrg.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegareleaseOrg(DeadHoster):
+ __name = "MegareleaseOrg"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?megarelease\.org/\w{12}'
+ __config = []
+
+ __description = """Megarelease.org hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("derek3x", "derek3x@vmail.me"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/MegasharesCom.py b/pyload/plugin/hoster/MegasharesCom.py
new file mode 100644
index 000000000..ac7a81313
--- /dev/null
+++ b/pyload/plugin/hoster/MegasharesCom.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MegasharesCom(SimpleHoster):
+ __name = "MegasharesCom"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Megashares.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>.+?)">'
+ SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted|Invalid link)'
+
+ LINK_PATTERN = r'<div id="show_download_button_%d".*?>\s*<a href="(.+?)">'
+
+ PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <.*?>(\w+).*?You have.*?<.*?>.*?([\d.]+) (\w+)'
+ PASSPORT_RENEW_PATTERN = r'(\d+):<strong>(\d+)</strong>:<strong>(\d+)</strong>'
+ REACTIVATE_NUM_PATTERN = r'<input[^>]*id="random_num" value="(\d+)" />'
+ REACTIVATE_PASSPORT_PATTERN = r'<input[^>]*id="passport_num" value="(\w+)" />'
+ REQUEST_URI_PATTERN = r'var request_uri = "(.+?)";'
+ NO_SLOTS_PATTERN = r'<dd class="red">All download slots for this link are currently filled'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+
+ def handlePremium(self, pyfile):
+ self.handleDownload(True)
+
+
+ def handleFree(self, pyfile):
+ if self.NO_SLOTS_PATTERN in self.html:
+ self.retry(wait_time=5 * 60)
+
+ m = re.search(self.REACTIVATE_PASSPORT_PATTERN, self.html)
+ if m:
+ passport_num = m.group(1)
+ request_uri = re.search(self.REQUEST_URI_PATTERN, self.html).group(1)
+
+ for _i in xrange(5):
+ random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
+
+ verifyinput = self.decryptCaptcha("http://d01.megashares.com/index.php",
+ get={'secgfx': "gfx", 'random_num': random_num})
+
+ self.logInfo(_("Reactivating passport %s: %s %s") % (passport_num, random_num, verifyinput))
+
+ res = self.load("http://d01.megashares.com%s" % request_uri,
+ get={'rs' : "check_passport_renewal",
+ 'rsargs[]': verifyinput,
+ 'rsargs[]': random_num,
+ 'rsargs[]': passport_num,
+ 'rsargs[]': "replace_sec_pprenewal",
+ 'rsrnd[]' : str(int(time.time() * 1000))})
+
+ if 'Thank you for reactivating your passport.' in res:
+ self.correctCaptcha()
+ self.retry()
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("Failed to reactivate passport"))
+
+ m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
+ if m:
+ time = [int(x) for x in m.groups()]
+ renew = time[0] + (time[1] * 60) + (time[2] * 60)
+ self.logDebug("Waiting %d seconds for a new passport" % renew)
+ self.retry(wait_time=renew, reason=_("Passport renewal"))
+
+ # Check traffic left on passport
+ m = re.search(self.PASSPORT_LEFT_PATTERN, self.html, re.M | re.S)
+ if m is None:
+ self.fail(_("Passport not found"))
+
+ self.logInfo(_("Download passport: %s") % m.group(1))
+ data_left = float(m.group(2)) * (2 ** 20) ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[m.group(3)]
+ self.logInfo(_("Data left: %s %s (%d MB needed)") % (m.group(2), m.group(3), self.pyfile.size / 1048576))
+
+ if not data_left:
+ self.retry(wait_time=600, reason=_("Passport renewal"))
+
+ self.handleDownload(False)
+
+
+ def handleDownload(self, premium=False):
+ # Find download link;
+ m = re.search(self.LINK_PATTERN % (1 if premium else 2), self.html)
+ msg = _('%s download URL' % ('Premium' if premium else 'Free'))
+ if m is None:
+ self.error(msg)
+
+ self.link = m.group(1)
+ self.logDebug("%s: %s" % (msg, self.link))
diff --git a/pyload/plugin/hoster/MegauploadCom.py b/pyload/plugin/hoster/MegauploadCom.py
new file mode 100644
index 000000000..20acad9a6
--- /dev/null
+++ b/pyload/plugin/hoster/MegauploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegauploadCom(DeadHoster):
+ __name = "MegauploadCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?megaupload\.com/\?.*&?(d|v)=\w+'
+ __config = []
+
+ __description = """Megaupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
diff --git a/pyload/plugin/hoster/MegavideoCom.py b/pyload/plugin/hoster/MegavideoCom.py
new file mode 100644
index 000000000..f50ce4365
--- /dev/null
+++ b/pyload/plugin/hoster/MegavideoCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegavideoCom(DeadHoster):
+ __name = "MegavideoCom"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?megavideo\.com/\?.*&?(d|v)=\w+'
+ __config = []
+
+ __description = """Megavideo.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/MovReelCom.py b/pyload/plugin/hoster/MovReelCom.py
new file mode 100644
index 000000000..fb942c59f
--- /dev/null
+++ b/pyload/plugin/hoster/MovReelCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class MovReelCom(XFSHoster):
+ __name = "MovReelCom"
+ __type = "hoster"
+ __version = "1.24"
+
+ __pattern = r'http://(?:www\.)?movreel\.com/\w{12}'
+
+ __description = """MovReel.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("JorisV83", "jorisv83-pyload@yahoo.com")]
+
+
+ LINK_PATTERN = r'<a href="(.+?)">Download Link'
diff --git a/pyload/plugin/hoster/MultihostersCom.py b/pyload/plugin/hoster/MultihostersCom.py
new file mode 100644
index 000000000..1bb2452bb
--- /dev/null
+++ b/pyload/plugin/hoster/MultihostersCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)multihosters\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+
+ __description = """Multihosters.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
diff --git a/pyload/plugin/hoster/MultishareCz.py b/pyload/plugin/hoster/MultishareCz.py
new file mode 100644
index 000000000..d4546e5f9
--- /dev/null
+++ b/pyload/plugin/hoster/MultishareCz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MultishareCz(SimpleHoster):
+ __name = "MultishareCz"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MultiShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SIZE_REPLACEMENTS = [('&nbsp;', '')]
+
+ CHECK_TRAFFIC = True
+ MULTI_HOSTER = True
+
+ INFO_PATTERN = ur'(?:<li>Název|Soubor): <strong>(?P<N>[^<]+)</strong><(?:/li><li|br)>Velikost: <strong>(?P<S>[^<]+)</strong>'
+ OFFLINE_PATTERN = ur'<h1>Stáhnout soubor</h1><p><strong>PoşadovanÜ soubor neexistuje.</strong></p>'
+
+
+ def handleFree(self, pyfile):
+ self.download("http://www.multishare.cz/html/download_free.php", get={'ID': self.info['pattern']['ID']})
+
+
+ def handlePremium(self, pyfile):
+ self.download("http://www.multishare.cz/html/download_premium.php", get={'ID': self.info['pattern']['ID']})
+
+
+ def handleMulti(self, pyfile):
+ self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": pyfile.url}, decode=True)
+
+ self.checkInfo()
+
+ if not self.checkTrafficLeft():
+ self.fail(_("Not enough credit left to download file"))
+
+ self.download("http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random.random() * 10000 * random.random()),
+ get={'u_ID' : self.acc_info['u_ID'],
+ 'u_hash': self.acc_info['u_hash'],
+ 'link' : pyfile.url},
+ disposition=True)
diff --git a/pyload/plugin/hoster/MyfastfileCom.py b/pyload/plugin/hoster/MyfastfileCom.py
new file mode 100644
index 000000000..57041d6cd
--- /dev/null
+++ b/pyload/plugin/hoster/MyfastfileCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class MyfastfileCom(MultiHoster):
+ __name = "MyfastfileCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Myfastfile.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+
+
+ def handlePremium(self, pyfile):
+ self.html = self.load('http://myfastfile.com/api.php',
+ get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
+ 'link': pyfile.url})
+ self.logDebug("JSON data: " + self.html)
+
+ self.html = json_loads(self.html)
+ if self.html['status'] != 'ok':
+ self.fail(_("Unable to unrestrict link"))
+
+ self.link = self.html['link']
diff --git a/pyload/plugin/hoster/MystoreTo.py b/pyload/plugin/hoster/MystoreTo.py
new file mode 100644
index 000000000..581dec612
--- /dev/null
+++ b/pyload/plugin/hoster/MystoreTo.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# Test link:
+# http://mystore.to/dl/mxcA50jKfP
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MystoreTo(SimpleHoster):
+ __name = "MystoreTo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?mystore\.to/dl/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Mystore.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.+?)<'
+ SIZE_PATTERN = r'FILESIZE: (?P<S>[\d\.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>file not found<'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ try:
+ fid = re.search(r'wert="(.+?)"', self.html).group(1)
+
+ except AttributeError:
+ self.error(_("File-ID not found"))
+
+ self.link = self.load("http://mystore.to/api/download",
+ post={'FID': fid})
diff --git a/pyload/plugin/hoster/MyvideoDe.py b/pyload/plugin/hoster/MyvideoDe.py
new file mode 100644
index 000000000..08652cdc9
--- /dev/null
+++ b/pyload/plugin/hoster/MyvideoDe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class MyvideoDe(Hoster):
+ __name = "MyvideoDe"
+ __type = "hoster"
+ __version = "0.90"
+
+ __pattern = r'http://(?:www\.)?myvideo\.de/watch/'
+
+ __description = """Myvideo.de hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.download_html()
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ self.html = self.load(self.pyfile.url)
+
+
+ def get_file_url(self):
+ videoId = re.search(r"addVariable\('_videoid','(.*)'\);p.addParam\('quality'", self.html).group(1)
+ videoServer = re.search("rel='image_src' href='(.*)thumbs/.*' />", self.html).group(1)
+ file_url = videoServer + videoId + ".flv"
+ return file_url
+
+
+ def get_file_name(self):
+ file_name_pattern = r"<h1 class='globalHd'>(.*)</h1>"
+ return html_unescape(re.search(file_name_pattern, self.html).group(1).replace("/", "") + '.flv')
+
+
+ def file_exists(self):
+ self.download_html()
+ self.load(str(self.pyfile.url), cookies=False, just_header=True)
+ if self.req.lastEffectiveURL == "http://www.myvideo.de/":
+ return False
+ return True
diff --git a/pyload/plugin/hoster/NahrajCz.py b/pyload/plugin/hoster/NahrajCz.py
new file mode 100644
index 000000000..14f041e17
--- /dev/null
+++ b/pyload/plugin/hoster/NahrajCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class NahrajCz(DeadHoster):
+ __name = "NahrajCz"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?nahraj\.cz/content/download/.+'
+ __config = []
+
+ __description = """Nahraj.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/NarodRu.py b/pyload/plugin/hoster/NarodRu.py
new file mode 100644
index 000000000..456baefec
--- /dev/null
+++ b/pyload/plugin/hoster/NarodRu.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NarodRu(SimpleHoster):
+ __name = "NarodRu"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Narod.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
+ SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
+ OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
+
+ SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
+ URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
+ (r"/start/\d+\.\w+-narod\.yandex\.ru/(\d{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
+
+ CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
+ LINK_FREE_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
+
+
+ def handleFree(self, pyfile):
+ for _i in xrange(5):
+ self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random.random() * 777))
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha"))
+
+ post_data = {"action": "sendcapcha"}
+ captcha_url, post_data['key'] = m.groups()
+ post_data['rep'] = self.decryptCaptcha(captcha_url)
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = 'http://narod.ru' + m.group(1)
+ self.correctCaptcha()
+ break
+
+ elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
+ self.invalidCaptcha()
+
+ else:
+ self.error(_("Download link"))
+
+ else:
+ self.fail(_("No valid captcha code entered"))
diff --git a/pyload/plugin/hoster/NetloadIn.py b/pyload/plugin/hoster/NetloadIn.py
new file mode 100644
index 000000000..9e05b02c5
--- /dev/null
+++ b/pyload/plugin/hoster/NetloadIn.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+def getInfo(urls):
+ ## returns list of tupels (name, size (in bytes), status (see database.File), url)
+
+ apiurl = "http://api.netload.in/info.php"
+ id_regex = re.compile(NetloadIn.__pattern)
+ urls_per_query = 80
+
+ for chunk in chunks(urls, urls_per_query):
+ ids = ""
+ for url in chunk:
+ match = id_regex.search(url)
+ if match:
+ ids = ids + match.group('ID') + ";"
+
+ api = getURL(apiurl,
+ get={'auth' : "Zf9SnQh9WiReEsb18akjvQGqT0I830e8",
+ 'bz' : 1,
+ 'md5' : 1,
+ 'file_id': ids},
+ decode=True)
+
+ if api is None or len(api) < 10:
+ self.logDebug("Prefetch failed")
+ return
+
+ if api.find("unknown_auth") >= 0:
+ self.logDebug("Outdated auth code")
+ return
+
+ result = []
+
+ for i, r in enumerate(api.splitlines()):
+ try:
+ tmp = r.split(";")
+
+ try:
+ size = int(tmp[2])
+ except Exception:
+ size = 0
+
+ result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i] ))
+
+ except Exception:
+ self.logDebug("Error while processing response: %s" % r)
+
+ yield result
+
+
+class NetloadIn(Hoster):
+ __name = "NetloadIn"
+ __type = "hoster"
+ __version = "0.49"
+
+ __pattern = r'https?://(?:www\.)?netload\.in/(?P<PATH>datei|index\.php\?id=10&file_id=)(?P<ID>\w+)'
+
+ __description = """Netload.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("RaNaN", "ranan@pyload.org"),
+ ("Gregy", "gregy@gregy.cz")]
+
+
+ RECAPTCHA_KEY = "6LcLJMQSAAAAAJzquPUPKNovIhbK6LpSqCjYrsR1"
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+
+
+ def process(self, pyfile):
+ self.url = pyfile.url
+
+ self.prepare()
+
+ pyfile.setStatus("downloading")
+
+ self.proceed(self.url)
+
+
+ def prepare(self):
+ self.api_load()
+
+ if self.api_data and self.api_data['filename']:
+ self.pyfile.name = self.api_data['filename']
+
+ if self.premium:
+ self.logDebug("Use Premium Account")
+
+ settings = self.load("http://www.netload.in/index.php", get={'id': 2, 'lang': "en"})
+
+ if '<option value="2" selected="selected">Direkter Download' in settings:
+ self.logDebug("Using direct download")
+ return True
+ else:
+ self.logDebug("Direct downloads not enabled. Parsing html for a download URL")
+
+ if self.download_html():
+ return True
+ else:
+ self.fail(_("Failed"))
+ return False
+
+
+ def api_load(self, n=0):
+ url = self.url
+ id_regex = re.compile(self.__pattern)
+ match = id_regex.search(url)
+
+ if match:
+ # normalize url
+ self.url = 'http://www.netload.in/datei%s.htm' % match.group('ID')
+ self.logDebug("URL: %s" % self.url)
+ else:
+ self.api_data = False
+ return
+
+ apiurl = "http://api.netload.in/info.php"
+ html = self.load(apiurl, cookies=False,
+ get={"file_id": match.group('ID'), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
+ "md5": "1"}, decode=True).strip()
+ if not html and n <= 3:
+ self.setWait(2)
+ self.wait()
+ self.api_load(n + 1)
+ return
+
+ self.logDebug("APIDATA: " + html)
+
+ self.api_data = {}
+
+ if html and ";" in html and html not in ("unknown file_data", "unknown_server_data", "No input file specified."):
+ lines = html.split(";")
+ self.api_data['exists'] = True
+ self.api_data['fileid'] = lines[0]
+ self.api_data['filename'] = lines[1]
+ self.api_data['size'] = lines[2]
+ self.api_data['status'] = lines[3]
+
+ if self.api_data['status'] == "online":
+ self.api_data['checksum'] = lines[4].strip()
+ else:
+ self.api_data = False #: check manually since api data is useless sometimes
+
+ if lines[0] == lines[1] and lines[2] == "0": #: useless api data
+ self.api_data = False
+ else:
+ self.api_data = False
+
+
+ def final_wait(self, page):
+ wait_time = self.get_wait_time.time(page)
+
+ self.setWait(wait_time)
+
+ self.logDebug("Final wait %d seconds" % wait_time)
+
+ self.wait()
+
+ self.url = self.get_file_url(page)
+
+
+ def check_free_wait(self, page):
+ if ">An access request has been made from IP address <" in page:
+ self.wantReconnect = True
+ self.setWait(self.get_wait_time.time(page) or 30)
+ self.wait()
+ return True
+ else:
+ return False
+
+
+ def download_html(self):
+ page = self.load(self.url, decode=True)
+
+ if "/share/templates/download_hddcrash.tpl" in page:
+ self.logError(_("Netload HDD Crash"))
+ self.fail(_("File temporarily not available"))
+
+ if not self.api_data:
+ self.logDebug("API Data may be useless, get details from html page")
+
+ if "* The file was deleted" in page:
+ self.offline()
+
+ name = re.search(r'class="dl_first_filename">([^<]+)', page, re.M)
+ # the found filename is not truncated
+ if name:
+ name = name.group(1).strip()
+ if not name.endswith(".."):
+ self.pyfile.name = name
+
+ captchawaited = False
+
+ for i in xrange(5):
+ if not page:
+ page = self.load(self.url)
+ t = time.time() + 30
+
+ if "/share/templates/download_hddcrash.tpl" in page:
+ self.logError(_("Netload HDD Crash"))
+ self.fail(_("File temporarily not available"))
+
+ self.logDebug("Try number %d " % i)
+
+ if ">Your download is being prepared.<" in page:
+ self.logDebug("We will prepare your download")
+ self.final_wait(page)
+ return True
+
+ self.logDebug("Trying to find captcha")
+
+ try:
+ url_captcha_html = re.search(r'(index.php\?id=10&amp;.*&amp;captcha=1)', page).group(1).replace("amp;", "")
+
+ except Exception, e:
+ self.logDebug("Exception during Captcha regex: %s" % e.message)
+ page = None
+
+ else:
+ url_captcha_html = urlparse.urljoin("http://netload.in/", url_captcha_html)
+ break
+
+ self.html = self.load(url_captcha_html)
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ response_page = self.load("http://www.netload.in/index.php?id=10",
+ post={'captcha_check' : '1',
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response,
+ 'file_id' : self.api_data['fileid'],
+ 'Download_Next' : ''})
+ if "Orange_Link" in response_page:
+ break
+
+ if self.check_free_wait(response_page):
+ self.logDebug("Had to wait for next free slot, trying again")
+ return self.download_html()
+
+ else:
+ download_url = self.get_file_url(response_page)
+ self.logDebug("Download URL after get_file: " + download_url)
+ if not download_url.startswith("http://"):
+ self.error(_("Download url: %s") % download_url)
+ self.wait()
+
+ self.url = download_url
+ return True
+
+
+ def get_file_url(self, page):
+ try:
+ file_url_pattern = r'<a class="Orange_Link" href="(http://.+)".?>Or click here'
+ attempt = re.search(file_url_pattern, page)
+ if attempt:
+ return attempt.group(1)
+ else:
+ self.logDebug("Backup try for final link")
+ file_url_pattern = r'<a href="(.+)" class="Orange_Link">Click here'
+ attempt = re.search(file_url_pattern, page)
+ return "http://netload.in/" + attempt.group(1)
+
+ except Exception, e:
+ self.logDebug("Getting final link failed", e.message)
+ return None
+
+
+ def get_wait_time.time(self, page):
+ return int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
+
+
+ def proceed(self, url):
+ self.download(url, disposition=True)
+
+ check = self.checkDownload({'empty' : re.compile(r'^$'),
+ 'offline': re.compile("The file was deleted")})
+ if check == "empty":
+ self.logInfo(_("Downloaded File was empty"))
+ self.retry()
+
+ elif check == "offline":
+ self.offline()
diff --git a/pyload/plugin/hoster/NitroflareCom.py b/pyload/plugin/hoster/NitroflareCom.py
new file mode 100644
index 000000000..a93ff67ec
--- /dev/null
+++ b/pyload/plugin/hoster/NitroflareCom.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NitroflareCom(SimpleHoster):
+ __name = "NitroflareCom"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'https?://(?:www\.)?nitroflare\.com/view/(?P<ID>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Nitroflare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sahil", "sahilshekhawat01@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Stickell", "l.stickell@yahoo.it")]
+
+ # URL_REPLACEMENTS = [("http://", "https://")]
+
+ INFO_PATTERN = r'title="(?P<N>.+?)".+>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>File doesn\'t exist'
+
+ LINK_FREE_PATTERN = r'(https?://[\w\-]+\.nitroflare\.com/.+?)"'
+
+ RECAPTCHA_KEY = "6Lenx_USAAAAAF5L1pmTWvWcH73dipAEzNnmNLgy"
+
+ PREMIUM_ONLY_PATTERN = r'This file is available with Premium only'
+ WAIT_PATTERN = r'You have to wait .+?<'
+ ERROR_PATTERN = r'downloading is not possible'
+
+
+ def checkErrors(self):
+ if not self.html:
+ return
+
+ if not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
+ self.fail(_("Link require a premium account to be handled"))
+
+ elif hasattr(self, 'WAIT_PATTERN'):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec)', m.group(0), re.I))
+ self.wait(wait_time, wait_time > 300)
+ return
+
+ elif hasattr(self, 'ERROR_PATTERN'):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = m.group(1)
+ self.error(errmsg)
+
+ self.info.pop('error', None)
+
+
+ def handleFree(self, pyfile):
+ # used here to load the cookies which will be required later
+ self.load(pyfile.url, post={'goToFreePage': ""})
+
+ self.load("http://nitroflare.com/ajax/setCookie.php", post={'fileId': self.info['pattern']['ID']})
+ self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
+ post={'method': "startTimer", 'fileId': self.info['pattern']['ID']})
+
+ self.checkErrors()
+
+ try:
+ js_file = self.load("http://nitroflare.com/js/downloadFree.js?v=1.0.1")
+ var_time = re.search("var time = (\\d+);", js_file)
+ wait_time = int(var_time.groups()[0])
+
+ except Exception:
+ wait_time = 60
+
+ self.wait(wait_time)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
+ post={'method' : "fetchDownload",
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ if "The captcha wasn't entered correctly" in self.html:
+ self.logWarning("The captcha wasn't entered correctly")
+ return
+
+ if "You have to fill the captcha" in self.html:
+ self.logWarning("Captcha unfilled")
+ return
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ else:
+ self.logError("Unable to detect direct link")
diff --git a/pyload/plugin/hoster/NoPremiumPl.py b/pyload/plugin/hoster/NoPremiumPl.py
new file mode 100644
index 000000000..be1e4794e
--- /dev/null
+++ b/pyload/plugin/hoster/NoPremiumPl.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class NoPremiumPl(MultiHoster):
+ __name = "NoPremiumPl"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://direct\.nopremium\.pl.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NoPremium.pl multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+
+ API_URL = "http://crypt.nopremium.pl"
+
+ API_QUERY = {'site' : "nopremium",
+ 'output' : "json",
+ 'username': "",
+ 'password': "",
+ 'url' : ""}
+
+ ERROR_CODES = {0 : "[%s] Incorrect login credentials",
+ 1 : "[%s] Not enough transfer to download - top-up your account",
+ 2 : "[%s] Incorrect / dead link",
+ 3 : "[%s] Error connecting to hosting, try again later",
+ 9 : "[%s] Premium account has expired",
+ 15: "[%s] Hosting no longer supported",
+ 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
+
+
+ def prepare(self):
+ super(NoPremiumPl, self).prepare()
+
+ data = self.account.getAccountData(self.user)
+
+ self.usr = data['usr']
+ self.pwd = data['pwd']
+
+
+ def runFileQuery(self, url, mode=None):
+ query = self.API_QUERY.copy()
+
+ query['username'] = self.usr
+ query['password'] = self.pwd
+ query['url'] = url
+
+ if mode == "fileinfo":
+ query['check'] = 2
+ query['loc'] = 1
+
+ self.logDebug(query)
+
+ return self.load(self.API_URL, post=query)
+
+
+ def handleFree(self, pyfile):
+ try:
+ data = self.runFileQuery(pyfile.url, 'fileinfo')
+
+ except Exception:
+ self.logDebug("runFileQuery error")
+ self.tempOffline()
+
+ try:
+ parsed = json_loads(data)
+
+ except Exception:
+ self.logDebug("loads error")
+ self.tempOffline()
+
+ self.logDebug(parsed)
+
+ if "errno" in parsed.keys():
+ if parsed['errno'] in self.ERROR_CODES:
+ # error code in known
+ self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName())
+ else:
+ # error code isn't yet added to plugin
+ self.fail(
+ parsed['errstring']
+ or _("Unknown error (code: %s)") % parsed['errno']
+ )
+
+ if "sdownload" in parsed:
+ if parsed['sdownload'] == "1":
+ self.fail(
+ _("Download from %s is possible only using NoPremium.pl website \
+ directly") % parsed['hosting'])
+
+ pyfile.name = parsed['filename']
+ pyfile.size = parsed['filesize']
+
+ try:
+ self.link = self.runFileQuery(pyfile.url, 'filedownload')
+
+ except Exception:
+ self.logDebug("runFileQuery error #2")
+ self.tempOffline()
diff --git a/pyload/plugin/hoster/NosuploadCom.py b/pyload/plugin/hoster/NosuploadCom.py
new file mode 100644
index 000000000..af79da22b
--- /dev/null
+++ b/pyload/plugin/hoster/NosuploadCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class NosuploadCom(XFSHoster):
+ __name = "NosuploadCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
+
+ __description = """Nosupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)</p>'
+ LINK_PATTERN = r'<a class="select" href="(http://.+?)">Download</a>'
+
+ WAIT_PATTERN = r'Please wait.*?>(\d+)</span>'
+
+
+ def getDownloadLink(self):
+ # stage1: press the "Free Download" button
+ data = self.getPostParameters()
+ self.html = self.load(self.pyfile.url, post=data, decode=True)
+
+ # stage2: wait some time and press the "Download File" button
+ data = self.getPostParameters()
+ wait_time = re.search(self.WAIT_PATTERN, self.html, re.M | re.S).group(1)
+ self.logDebug("Hoster told us to wait %s seconds" % wait_time)
+ self.wait(wait_time)
+ self.html = self.load(self.pyfile.url, post=data, decode=True)
+
+ # stage3: get the download link
+ return re.search(self.LINK_PATTERN, self.html, re.S).group(1)
diff --git a/pyload/plugin/hoster/NovafileCom.py b/pyload/plugin/hoster/NovafileCom.py
new file mode 100644
index 000000000..f76d77269
--- /dev/null
+++ b/pyload/plugin/hoster/NovafileCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://novafile.com/vfun4z6o2cit
+# http://novafile.com/s6zrr5wemuz4
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class NovafileCom(XFSHoster):
+ __name = "NovafileCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?novafile\.com/\w{12}'
+
+ __description = """Novafile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ ERROR_PATTERN = r'class="alert.+?alert-separate".*?>\s*(?:<p>)?(.*?)\s*</'
+ WAIT_PATTERN = r'<p>Please wait <span id="count".*?>(\d+)</span> seconds</p>'
+
+ LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
diff --git a/pyload/plugin/hoster/NowDownloadSx.py b/pyload/plugin/hoster/NowDownloadSx.py
new file mode 100644
index 000000000..967c14e81
--- /dev/null
+++ b/pyload/plugin/hoster/NowDownloadSx.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import fixup
+
+
+class NowDownloadSx(SimpleHoster):
+ __name = "NowDownloadSx"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?(nowdownload\.[a-zA-Z]{2,}/(dl/|download\.php.+?id=|mobile/(#/files/|.+?id=))|likeupload\.org/)\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NowDownload.sx hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[\d.,]+) (?P<U>[\w^_]+) </h4>'
+ OFFLINE_PATTERN = r'>This file does not exist'
+
+ TOKEN_PATTERN = r'"(/api/token\.php\?token=\w+)"'
+ CONTINUE_PATTERN = r'"(/dl2/\w+/\w+)"'
+ WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
+ LINK_FREE_PATTERN = r'(http://s\d+\.coolcdn\.info/nowdownload/.+?)["\']'
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<.*?>', '')]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handleFree(self, pyfile):
+ tokenlink = re.search(self.TOKEN_PATTERN, self.html)
+ continuelink = re.search(self.CONTINUE_PATTERN, self.html)
+ if tokenlink is None or continuelink is None:
+ self.error()
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait = int(m.group(1))
+ else:
+ wait = 60
+
+ baseurl = "http://www.nowdownload.at"
+ self.html = self.load(baseurl + str(tokenlink.group(1)))
+ self.wait(wait)
+
+ self.html = self.load(baseurl + str(continuelink.group(1)))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/NowVideoSx.py b/pyload/plugin/hoster/NowVideoSx.py
new file mode 100644
index 000000000..bfa186e60
--- /dev/null
+++ b/pyload/plugin/hoster/NowVideoSx.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NowVideoSx(SimpleHoster):
+ __name = "NowVideoSx"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?nowvideo\.[a-zA-Z]{2,}/(video/|mobile/(#/videos/|.+?id=))(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NowVideo.sx hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.nowvideo.sx/video/\g<ID>')]
+
+ NAME_PATTERN = r'<h4>(?P<N>.+?)<'
+ OFFLINE_PATTERN = r'>This file no longer exists'
+
+ LINK_FREE_PATTERN = r'<source src="(.+?)"'
+ LINK_PREMIUM_PATTERN = r'<div id="content_player" >\s*<a href="(.+?)"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load("http://www.nowvideo.sx/mobile/video.php", get={'id': self.info['pattern']['ID']})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/OboomCom.py b/pyload/plugin/hoster/OboomCom.py
new file mode 100644
index 000000000..5b9b11485
--- /dev/null
+++ b/pyload/plugin/hoster/OboomCom.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://www.oboom.com/B7CYZIEB/10Mio.dat
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class OboomCom(Hoster):
+ __name = "OboomCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>\w{8})'
+
+ __description = """oboom.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stanley", "stanley.foerster@gmail.com")]
+
+
+ RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.multiDL = self.resumeDownload = self.premium
+
+
+ def process(self, pyfile):
+ self.pyfile.url.replace(".com/#id=", ".com/#")
+ self.pyfile.url.replace(".com/#/", ".com/#")
+ self.getFileId(self.pyfile.url)
+ self.getSessionToken()
+ self.getFileInfo(self.sessionToken, self.fileId)
+ self.pyfile.name = self.fileName
+ self.pyfile.size = self.fileSize
+ if not self.premium:
+ self.solveCaptcha()
+ self.getDownloadTicket()
+ self.download("https://%s/1.0/dlh" % self.downloadDomain, get={"ticket": self.downloadTicket, "http_errors": 0})
+
+
+ def loadUrl(self, url, get=None):
+ if get is None:
+ get = dict()
+ return json_loads(self.load(url, get, decode=True))
+
+
+ def getFileId(self, url):
+ self.fileId = re.match(OboomCom.__pattern, url).group('ID')
+
+
+ def getSessionToken(self):
+ if self.premium:
+ accountInfo = self.account.getAccountInfo(self.user, True)
+ if "session" in accountInfo:
+ self.sessionToken = accountInfo['session']
+ else:
+ self.fail(_("Could not retrieve premium session"))
+ else:
+ apiUrl = "https://www.oboom.com/1.0/guestsession"
+ result = self.loadUrl(apiUrl)
+ if result[0] == 200:
+ self.sessionToken = result[1]
+ else:
+ self.fail(_("Could not retrieve token for guest session. Error code: %s") % result[0])
+
+
+ def solveCaptcha(self):
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+ apiUrl = "https://www.oboom.com/1.0/download/ticket"
+ params = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field": response,
+ "download_id": self.fileId,
+ "token": self.sessionToken}
+ result = self.loadUrl(apiUrl, params)
+
+ if result[0] == 200:
+ self.downloadToken = result[1]
+ self.downloadAuth = result[2]
+ self.correctCaptcha()
+ self.setWait(30)
+ self.wait()
+ break
+
+ elif result[0] == 400:
+ if result[1] == "incorrect-captcha-sol":
+ self.invalidCaptcha()
+ elif result[1] == "captcha-timeout":
+ self.invalidCaptcha()
+ elif result[1] == "forbidden":
+ self.retry(5, 15 * 60, _("Service unavailable"))
+
+ elif result[0] == 403:
+ if result[1] == -1: #: another download is running
+ self.setWait(15 * 60)
+ else:
+ self.setWait(result[1], True)
+ self.wait()
+ self.retry(5)
+ else:
+ self.invalidCaptcha()
+ self.fail(_("Received invalid captcha 5 times"))
+
+
+ def getFileInfo(self, token, fileId):
+ apiUrl = "https://api.oboom.com/1.0/info"
+ params = {"token": token, "items": fileId, "http_errors": 0}
+
+ result = self.loadUrl(apiUrl, params)
+ if result[0] == 200:
+ item = result[1][0]
+ if item['state'] == "online":
+ self.fileSize = item['size']
+ self.fileName = item['name']
+ else:
+ self.offline()
+ else:
+ self.fail(_("Could not retrieve file info. Error code %s: %s") % (result[0], result[1]))
+
+
+ def getDownloadTicket(self):
+ apiUrl = "https://api.oboom.com/1/dl"
+ params = {"item": self.fileId, "http_errors": 0}
+ if self.premium:
+ params['token'] = self.sessionToken
+ else:
+ params['token'] = self.downloadToken
+ params['auth'] = self.downloadAuth
+
+ result = self.loadUrl(apiUrl, params)
+ if result[0] == 200:
+ self.downloadDomain = result[1]
+ self.downloadTicket = result[2]
+ elif result[0] == 421:
+ self.retry(wait_time=result[2] + 60, reason=_("Connection limit exceeded"))
+ else:
+ self.fail(_("Could not retrieve download ticket. Error code: %s") % result[0])
diff --git a/pyload/plugin/hoster/OneFichierCom.py b/pyload/plugin/hoster/OneFichierCom.py
new file mode 100644
index 000000000..74ac71fcb
--- /dev/null
+++ b/pyload/plugin/hoster/OneFichierCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class OneFichierCom(SimpleHoster):
+ __name = "OneFichierCom"
+ __type = "hoster"
+ __version = "0.83"
+
+ __pattern = r'https?://(?:www\.)?(?:(?P<ID1>\w+)\.)?(?P<HOST>1fichier\.com|alterupload\.com|cjoint\.net|d(es)?fichiers\.com|dl4free\.com|megadl\.fr|mesfichiers\.org|piecejointe\.net|pjointe\.com|tenvoi\.com)(?:/\?(?P<ID2>\w+))?'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """1fichier.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("the-razer", "daniel_ AT gmx DOT net"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("imclem", ""),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Ludovic Lehmann", "ludo.lehmann@gmail.com")]
+
+
+ NAME_PATTERN = r'>FileName :</td>\s*<td.*>(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Size :</td>\s*<td.*>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'File not found !\s*<'
+
+ COOKIES = [("1fichier.com", "LG", "en")]
+
+ WAIT_PATTERN = r'>You must wait \d+ minutes'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ id = self.info['pattern']['ID1'] or self.info['pattern']['ID2']
+ url, inputs = self.parseHtmlForm('action="https://1fichier.com/\?%s' % id)
+
+ if not url:
+ self.fail(_("Download link not found"))
+
+ if "pass" in inputs:
+ inputs['pass'] = self.getPassword()
+
+ inputs['submit'] = "Download"
+
+ self.download(url, post=inputs)
+
+
+ def handlePremium(self, pyfile):
+ self.download(pyfile.url, post={'dl': "Download", 'did': 0})
diff --git a/pyload/plugin/hoster/OronCom.py b/pyload/plugin/hoster/OronCom.py
new file mode 100644
index 000000000..29ca267a0
--- /dev/null
+++ b/pyload/plugin/hoster/OronCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class OronCom(DeadHoster):
+ __name = "OronCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?oron\.com/\w{12}'
+ __config = []
+
+ __description = """Oron.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("chrox", "chrox@pyload.org"),
+ ("DHMH", "DHMH@pyload.org")]
diff --git a/pyload/plugin/hoster/OverLoadMe.py b/pyload/plugin/hoster/OverLoadMe.py
new file mode 100644
index 000000000..85a0f0a1f
--- /dev/null
+++ b/pyload/plugin/hoster/OverLoadMe.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class OverLoadMe(MultiHoster):
+ __name = "OverLoadMe"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://.*overload\.me/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Over-Load.me multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def setup(self):
+ self.chunkLimit = 5
+
+
+ def handlePremium(self, pyfile):
+ https = "https" if self.getConfig('ssl') else "http"
+ data = self.account.getAccountData(self.user)
+ page = self.load(https + "://api.over-load.me/getdownload.php",
+ get={'auth': data['password'],
+ 'link': pyfile.url})
+
+ data = json_loads(page)
+
+ self.logDebug(data)
+
+ if data['error'] == 1:
+ self.logWarning(data['msg'])
+ self.tempOffline()
+ else:
+ if pyfile.name and pyfile.name.endswith('.tmp') and data['filename']:
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+
+ http_repl = ["http://", "https://"]
+ self.link = data['downloadlink'].replace(*http_repl if self.getConfig('ssl') else http_repl[::-1])
diff --git a/pyload/plugin/hoster/PandaplaNet.py b/pyload/plugin/hoster/PandaplaNet.py
new file mode 100644
index 000000000..dc23ef6a0
--- /dev/null
+++ b/pyload/plugin/hoster/PandaplaNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PandaplaNet(DeadHoster):
+ __name = "PandaplaNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?pandapla\.net/\w{12}'
+ __config = []
+
+ __description = """Pandapla.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/PornhostCom.py b/pyload/plugin/hoster/PornhostCom.py
new file mode 100644
index 000000000..103882166
--- /dev/null
+++ b/pyload/plugin/hoster/PornhostCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class PornhostCom(Hoster):
+ __name = "PornhostCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?pornhost\.com/(\d+/\d+\.html|\d+)'
+
+ __description = """Pornhost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de")]
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ # Old interface
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ url = re.search(r'download this file</label>.*?<a href="(.*?)"', self.html)
+ if url is None:
+ url = re.search(r'"(http://dl\d+\.pornhost\.com/files/.*?/.*?/.*?/.*?/.*?/.*?\..*?)"', self.html)
+ if url is None:
+ url = re.search(r'width: 894px; height: 675px">.*?<img src="(.*?)"', self.html)
+ if url is None:
+ url = re.search(r'"http://file\d+\.pornhost\.com/\d+/.*?"',
+ self.html) #: TODO: fix this one since it doesn't match
+
+ return url.group(1).strip()
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ name = re.search(r'<title>pornhost\.com - free file hosting with a twist - gallery(.*?)</title>', self.html)
+ if name is None:
+ name = re.search(r'id="url" value="http://www\.pornhost\.com/(.*?)/"', self.html)
+ if name is None:
+ name = re.search(r'<title>pornhost\.com - free file hosting with a twist -(.*?)</title>', self.html)
+ if name is None:
+ name = re.search(r'"http://file\d+\.pornhost\.com/.*?/(.*?)"', self.html)
+
+ name = name.group(1).strip() + ".flv"
+
+ return name
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r'gallery not found|You will be redirected to', self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/PornhubCom.py b/pyload/plugin/hoster/PornhubCom.py
new file mode 100644
index 000000000..c7bfa339e
--- /dev/null
+++ b/pyload/plugin/hoster/PornhubCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class PornhubCom(Hoster):
+ __name = "PornhubCom"
+ __type = "hoster"
+ __version = "0.50"
+
+ __pattern = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=\w+'
+
+ __description = """Pornhub.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de")]
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ url = "http://www.pornhub.com//gateway.php"
+ video_id = self.pyfile.url.split('=')[-1]
+ # thanks to jD team for this one v
+ post_data = "\x00\x03\x00\x00\x00\x01\x00\x0c\x70\x6c\x61\x79\x65\x72\x43\x6f\x6e\x66\x69\x67\x00\x02\x2f\x31\x00\x00\x00\x44\x0a\x00\x00\x00\x03\x02\x00"
+ post_data += chr(len(video_id))
+ post_data += video_id
+ post_data += "\x02\x00\x02\x2d\x31\x02\x00\x20"
+ post_data += "add299463d4410c6d1b1c418868225f7"
+
+ content = self.load(url, post=str(post_data))
+
+ new_content = ""
+ for x in content:
+ if ord(x) < 32 or ord(x) > 176:
+ new_content += '#'
+ else:
+ new_content += x
+
+ content = new_content
+
+ return re.search(r'flv_url.*(http.*?)##post_roll', content).group(1)
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<title.+?>([^<]+) - ', self.html)
+ if m:
+ name = m.group(1)
+ else:
+ matches = re.findall('<h1>(.*?)</h1>', self.html)
+ if len(matches) > 1:
+ name = matches[1]
+ else:
+ name = matches[0]
+
+ return name + '.flv'
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r'This video is no longer in our database or is in conversion', self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/PotloadCom.py b/pyload/plugin/hoster/PotloadCom.py
new file mode 100644
index 000000000..b6b86b689
--- /dev/null
+++ b/pyload/plugin/hoster/PotloadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PotloadCom(DeadHoster):
+ __name = "PotloadCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?potload\.com/\w{12}'
+ __config = []
+
+ __description = """Potload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/PremiumTo.py b/pyload/plugin/hoster/PremiumTo.py
new file mode 100644
index 000000000..59fd37459
--- /dev/null
+++ b/pyload/plugin/hoster/PremiumTo.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import fs_encode
+
+
+class PremiumTo(MultiHoster):
+ __name = "PremiumTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Premium.to multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CHECK_TRAFFIC = True
+
+
+ def handlePremium(self, pyfile):
+ # raise timeout to 2min
+ self.download("http://premium.to/api/getfile.php",
+ get={'username': self.account.username,
+ 'password': self.account.password,
+ 'link' : pyfile.url},
+ disposition=True)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'nopremium': "No premium account available"}):
+ self.retry(60, 5 * 60, "No premium account available")
+
+ err = ''
+ if self.req.http.code == '420':
+ # Custom error code send - fail
+ file = fs_encode(self.lastDownload)
+ with open(file, "rb") as f:
+ err = f.read(256).strip()
+ os.remove(file)
+
+ if err:
+ self.fail(err)
+
+ return super(PremiumTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/PremiumizeMe.py b/pyload/plugin/hoster/PremiumizeMe.py
new file mode 100644
index 000000000..f577da90e
--- /dev/null
+++ b/pyload/plugin/hoster/PremiumizeMe.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class PremiumizeMe(MultiHoster):
+ __name = "PremiumizeMe"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Premiumize.me multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def handlePremium(self, pyfile):
+ # In some cases hostsers do not supply us with a filename at download, so we
+ # are going to set a fall back filename (e.g. for freakshare or xfileshare)
+ pyfile.name = pyfile.name.split('/').pop() #: Remove everthing before last slash
+
+ # Correction for automatic assigned filename: Removing html at end if needed
+ suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
+ temp = pyfile.name.split('.')
+ if temp.pop() in suffix_to_remove:
+ pyfile.name = ".".join(temp)
+
+ # Get account data
+ user, data = self.account.selectAccount()
+
+ # Get rewritten link using the premiumize.me api v1 (see https://secure.premiumize.me/?show=api)
+ data = json_loads(self.load("https://api.premiumize.me/pm-api/v1.php",
+ get={'method' : "directdownloadlink",
+ 'params[login]': user,
+ 'params[pass]' : data['password'],
+ 'params[link]' : pyfile.url}))
+
+ # Check status and decide what to do
+ status = data['status']
+
+ if status == 200:
+ self.link = data['result']['location']
+ return
+
+ elif status == 400:
+ self.fail(_("Invalid link"))
+
+ elif status == 404:
+ self.offline()
+
+ elif status >= 500:
+ self.tempOffline()
+
+ else:
+ self.fail(data['statusmessage'])
diff --git a/pyload/plugin/hoster/PromptfileCom.py b/pyload/plugin/hoster/PromptfileCom.py
new file mode 100644
index 000000000..27b669382
--- /dev/null
+++ b/pyload/plugin/hoster/PromptfileCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class PromptfileCom(SimpleHoster):
+ __name = "PromptfileCom"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'https?://(?:www\.)?promptfile\.com/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Promptfile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ INFO_PATTERN = r'<span style=".+?" title=".+?">(?P<N>.*?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</span>'
+ OFFLINE_PATTERN = r'<span style=".+?" title="File Not Found">File Not Found</span>'
+
+ CHASH_PATTERN = r'<input type="hidden" name="chash" value="(.+?)" />'
+ LINK_FREE_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>'
+
+
+ def handleFree(self, pyfile):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.error(_("CHASH_PATTERN not found"))
+ chash = m.group(1)
+ self.logDebug("Read chash %s" % chash)
+ # continue to stage2
+ self.html = self.load(pyfile.url, decode=True, post={'chash': chash})
+
+ # STAGE 2: get the direct link
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/PrzeklejPl.py b/pyload/plugin/hoster/PrzeklejPl.py
new file mode 100644
index 000000000..f4781abb9
--- /dev/null
+++ b/pyload/plugin/hoster/PrzeklejPl.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PrzeklejPl(DeadHoster):
+ __name = "PrzeklejPl"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?przeklej\.pl/plik/.+'
+ __config = []
+
+ __description = """Przeklej.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/PutdriveCom.py b/pyload/plugin/hoster/PutdriveCom.py
new file mode 100644
index 000000000..051da9473
--- /dev/null
+++ b/pyload/plugin/hoster/PutdriveCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)putdrive\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+
+ __description = """Multihosters.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/QuickshareCz.py b/pyload/plugin/hoster/QuickshareCz.py
new file mode 100644
index 000000000..57b419688
--- /dev/null
+++ b/pyload/plugin/hoster/QuickshareCz.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class QuickshareCz(SimpleHoster):
+ __name = "QuickshareCz"
+ __type = "hoster"
+ __version = "0.56"
+
+ __pattern = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Quickshare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
+ SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</td>'
+ OFFLINE_PATTERN = r'<script type="text/javascript">location\.href=\'/chyba\';</script>'
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ # parse js variables
+ self.jsvars = dict((x, y.strip("'")) for x, y in re.findall(r"var (\w+) = ([\d.]+|'.+?')", self.html))
+ self.logDebug(self.jsvars)
+ pyfile.name = self.jsvars['ID3']
+
+ # determine download type - free or premium
+ if self.premium:
+ if 'UU_prihlasen' in self.jsvars:
+ if self.jsvars['UU_prihlasen'] == '0':
+ self.logWarning(_("User not logged in"))
+ self.relogin(self.user)
+ self.retry()
+ elif float(self.jsvars['UU_kredit']) < float(self.jsvars['kredit_odecet']):
+ self.logWarning(_("Not enough credit left"))
+ self.premium = False
+
+ if self.premium:
+ self.handlePremium(pyfile)
+ else:
+ self.handleFree(pyfile)
+
+ if self.checkDownload({"error": re.compile(r"\AChyba!")}, max_size=100):
+ self.fail(_("File not m or plugin defect"))
+
+
+ def handleFree(self, pyfile):
+ # get download url
+ download_url = '%s/download.php' % self.jsvars['server']
+ data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID3", "ID4"))
+ self.logDebug("FREE URL1:" + download_url, data)
+
+ self.load(download_url, post=data, follow_location=False)
+ self.header = self.req.http.header
+
+ m = re.search(r'Location\s*:\s*(.+)', self.header, re.I)
+ if m is None:
+ self.fail(_("File not found"))
+
+ self.link = m.group(1)
+ self.logDebug("FREE URL2:" + self.link)
+
+ # check errors
+ m = re.search(r'/chyba/(\d+)', self.link)
+ if m:
+ if m.group(1) == '1':
+ self.retry(60, 2 * 60, "This IP is already downloading")
+ elif m.group(1) == '2':
+ self.retry(60, 60, "No free slots available")
+ else:
+ self.fail(_("Error %d") % m.group(1))
+
+
+ def handlePremium(self, pyfile):
+ download_url = '%s/download_premium.php' % self.jsvars['server']
+ data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID4", "ID5"))
+ self.download(download_url, get=data)
diff --git a/pyload/plugin/hoster/RPNetBiz.py b/pyload/plugin/hoster/RPNetBiz.py
new file mode 100644
index 000000000..dc11eefb2
--- /dev/null
+++ b/pyload/plugin/hoster/RPNetBiz.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import json_loads
+
+
+class RPNetBiz(MultiHoster):
+ __name = "RPNetBiz"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'https?://.+rpnet\.biz'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """RPNet.biz multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+
+
+ def handlePremium(self, pyfile):
+ user, data = self.account.selectAccount()
+
+ # Get the download link
+ res = self.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user,
+ "password": data['password'],
+ "action" : "generate",
+ "links" : pyfile.url})
+
+ self.logDebug("JSON data: %s" % res)
+ link_status = json_loads(res)['links'][0] #: get the first link... since we only queried one
+
+ # Check if we only have an id as a HDD link
+ if 'id' in link_status:
+ self.logDebug("Need to wait at least 30 seconds before requery")
+ self.setWait(30) #: wait for 30 seconds
+ self.wait()
+ # Lets query the server again asking for the status on the link,
+ # we need to keep doing this until we reach 100
+ max_tries = 30
+ my_try = 0
+ while (my_try <= max_tries):
+ self.logDebug("Try: %d ; Max Tries: %d" % (my_try, max_tries))
+ res = self.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user,
+ "password": data['password'],
+ "action": "downloadInformation",
+ "id": link_status['id']})
+ self.logDebug("JSON data hdd query: %s" % res)
+ download_status = json_loads(res)['download']
+
+ if download_status['status'] == '100':
+ link_status['generated'] = download_status['rpnet_link']
+ self.logDebug("Successfully downloaded to rpnet HDD: %s" % link_status['generated'])
+ break
+ else:
+ self.logDebug("At %s%% for the file download" % download_status['status'])
+
+ self.setWait(30)
+ self.wait()
+ my_try += 1
+
+ if my_try > max_tries: #: We went over the limit!
+ self.fail(_("Waited for about 15 minutes for download to finish but failed"))
+
+ if 'generated' in link_status:
+ self.link = link_status['generated']
+ return
+ elif 'error' in link_status:
+ self.fail(link_status['error'])
+ else:
+ self.fail(_("Something went wrong, not supposed to enter here"))
diff --git a/pyload/plugin/hoster/RapideoPl.py b/pyload/plugin/hoster/RapideoPl.py
new file mode 100644
index 000000000..35d4da0ad
--- /dev/null
+++ b/pyload/plugin/hoster/RapideoPl.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class RapideoPl(MultiHoster):
+ __name = "RapideoPl"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapideo.pl multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+
+ API_URL = "http://enc.rapideo.pl"
+
+ API_QUERY = {'site' : "newrd",
+ 'output' : "json",
+ 'username': "",
+ 'password': "",
+ 'url' : ""}
+
+ ERROR_CODES = {0 : "[%s] Incorrect login credentials",
+ 1 : "[%s] Not enough transfer to download - top-up your account",
+ 2 : "[%s] Incorrect / dead link",
+ 3 : "[%s] Error connecting to hosting, try again later",
+ 9 : "[%s] Premium account has expired",
+ 15: "[%s] Hosting no longer supported",
+ 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
+
+
+ def prepare(self):
+ super(RapideoPl, self).prepare()
+
+ data = self.account.getAccountData(self.user)
+
+ self.usr = data['usr']
+ self.pwd = data['pwd']
+
+
+ def runFileQuery(self, url, mode=None):
+ query = self.API_QUERY.copy()
+
+ query['username'] = self.usr
+ query['password'] = self.pwd
+ query['url'] = url
+
+ if mode == "fileinfo":
+ query['check'] = 2
+ query['loc'] = 1
+
+ self.logDebug(query)
+
+ return self.load(self.API_URL, post=query)
+
+
+ def handleFree(self, pyfile):
+ try:
+ data = self.runFileQuery(pyfile.url, 'fileinfo')
+
+ except Exception:
+ self.logDebug("RunFileQuery error")
+ self.tempOffline()
+
+ try:
+ parsed = json_loads(data)
+
+ except Exception:
+ self.logDebug("Loads error")
+ self.tempOffline()
+
+ self.logDebug(parsed)
+
+ if "errno" in parsed.keys():
+ if parsed['errno'] in self.ERROR_CODES:
+ # error code in known
+ self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName())
+ else:
+ # error code isn't yet added to plugin
+ self.fail(
+ parsed['errstring']
+ or _("Unknown error (code: %s)") % parsed['errno']
+ )
+
+ if "sdownload" in parsed:
+ if parsed['sdownload'] == "1":
+ self.fail(
+ _("Download from %s is possible only using Rapideo.pl website \
+ directly") % parsed['hosting'])
+
+ pyfile.name = parsed['filename']
+ pyfile.size = parsed['filesize']
+
+ try:
+ self.link = self.runFileQuery(pyfile.url, 'filedownload')
+
+ except Exception:
+ self.logDebug("runFileQuery error #2")
+ self.tempOffline()
diff --git a/pyload/plugin/hoster/RapidfileshareNet.py b/pyload/plugin/hoster/RapidfileshareNet.py
new file mode 100644
index 000000000..8252b49e7
--- /dev/null
+++ b/pyload/plugin/hoster/RapidfileshareNet.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class RapidfileshareNet(XFSHoster):
+ __name = "RapidfileshareNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?rapidfileshare\.net/\w{12}'
+
+ __description = """Rapidfileshare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'>http://www.rapidfileshare.net/\w+?</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</font>'
+
+ OFFLINE_PATTERN = r'>No such file with this filename'
+ TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
diff --git a/pyload/plugin/hoster/RapidgatorNet.py b/pyload/plugin/hoster/RapidgatorNet.py
new file mode 100644
index 000000000..b05b0d5d0
--- /dev/null
+++ b/pyload/plugin/hoster/RapidgatorNet.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.captcha.AdsCaptcha import AdsCaptcha
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+class RapidgatorNet(SimpleHoster):
+ __name = "RapidgatorNet"
+ __type = "hoster"
+ __version = "0.33"
+
+ __pattern = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapidgator.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("chrox", ""),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "http://rapidgator.net/api/file"
+
+ COOKIES = [("rapidgator.net", "lang", "en")]
+
+ NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
+ SIZE_PATTERN = r'File size:\s*<strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong>'
+ OFFLINE_PATTERN = r'>(File not found|Error 404)'
+
+ JSVARS_PATTERN = r'\s+var\s*(startTimerUrl|getDownloadUrl|captchaUrl|fid|secs)\s*=\s*\'?(.*?)\'?;'
+
+ PREMIUM_ONLY_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
+ ERROR_PATTERN = r'You have reached your (?:daily|hourly) downloads limit'
+ WAIT_PATTERN = r'(Delay between downloads must be not less than|Try again in).+'
+
+ LINK_FREE_PATTERN = r'return \'(http://\w+.rapidgator.net/.*)\';'
+
+ RECAPTCHA_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
+ ADSCAPTCHA_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']+)'
+ SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
+
+
+ def setup(self):
+ if self.account:
+ self.sid = self.account.getAccountInfo(self.user).get('sid', None)
+ else:
+ self.sid = None
+
+ if self.sid:
+ self.premium = True
+
+ self.resumeDownload = self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def api_response(self, cmd):
+ try:
+ json = self.load('%s/%s' % (self.API_URL, cmd),
+ get={'sid': self.sid,
+ 'url': self.pyfile.url}, decode=True)
+ self.logDebug("API:%s" % cmd, json, "SID: %s" % self.sid)
+ json = json_loads(json)
+ status = json['response_status']
+ msg = json['response_details']
+
+ except BadHeader, e:
+ self.logError("API: %s" % cmd, e, "SID: %s" % self.sid)
+ status = e.code
+ msg = e
+
+ if status == 200:
+ return json['response']
+
+ elif status == 423:
+ self.account.empty(self.user)
+ self.retry()
+
+ else:
+ self.account.relogin(self.user)
+ self.retry(wait_time=60)
+
+
+ def handlePremium(self, pyfile):
+ self.api_data = self.api_response('info')
+ self.api_data['md5'] = self.api_data['hash']
+
+ pyfile.name = self.api_data['filename']
+ pyfile.size = self.api_data['size']
+
+ self.link = self.api_response('download')['url']
+
+
+ def handleFree(self, pyfile):
+ jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
+ self.logDebug(jsvars)
+
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ url = "http://rapidgator.net%s?fid=%s" % (
+ jsvars.get('startTimerUrl', '/download/AjaxStartTimer'), jsvars['fid'])
+ jsvars.update(self.getJsonResponse(url))
+
+ self.wait(jsvars.get('secs', 45), False)
+
+ url = "http://rapidgator.net%s?sid=%s" % (
+ jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
+ jsvars.update(self.getJsonResponse(url))
+
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
+
+ url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
+ self.html = self.load(url)
+
+ for _i in xrange(5):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ break
+ else:
+ captcha = self.handleCaptcha()
+
+ if not captcha:
+ self.error(_("Captcha pattern not found"))
+
+ response, challenge = captcha.challenge()
+
+ self.html = self.load(url, post={'DownloadCaptchaForm[captcha]': "",
+ 'adcopy_challenge' : challenge,
+ 'adcopy_response' : response})
+
+ if "The verification code is incorrect" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ else:
+ self.error(_("Download link"))
+
+
+ def handleCaptcha(self):
+ for klass in (AdsCaptcha, ReCaptcha, SolveMedia):
+ inst = klass(self)
+ if inst.detect_key():
+ return inst
+
+
+ def getJsonResponse(self, url):
+ res = self.load(url, decode=True)
+ if not res.startswith('{'):
+ self.retry()
+ self.logDebug(url, res)
+ return json_loads(res)
diff --git a/pyload/plugin/hoster/RapiduNet.py b/pyload/plugin/hoster/RapiduNet.py
new file mode 100644
index 000000000..6bbfc171a
--- /dev/null
+++ b/pyload/plugin/hoster/RapiduNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RapiduNet(SimpleHoster):
+ __name = "RapiduNet"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapidu.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", "")]
+
+
+ COOKIES = [("rapidu.net", "rapidu_lang", "en")]
+
+ INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>'
+ OFFLINE_PATTERN = r'<h1>404'
+
+ ERROR_PATTERN = r'<div class="error">'
+
+ RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+
+ def handleFree(self, pyfile):
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
+ get={'a': "getLoadTimeToDownload"},
+ post={'_go': ""},
+ decode=True)
+
+ if str(jsvars['timeToDownload']) is "stop":
+ t = (24 * 60 * 60) - (int(time.time()) % (24 * 60 * 60)) + time.altzone
+
+ self.logInfo("You've reach your daily download transfer")
+
+ self.retry(10, 10 if t < 1 else None, _("Try tomorrow again")) #@NOTE: check t in case of not synchronised clock
+
+ else:
+ self.wait(int(jsvars['timeToDownload']) - int(time.time()))
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
+ get={'a': "getCheckCaptcha"},
+ post={'_go' : "",
+ 'captcha1': challenge,
+ 'captcha2': response,
+ 'fileId' : self.info['pattern']['ID']},
+ decode=True)
+
+ if jsvars['message'] == 'success':
+ self.link = jsvars['url']
+
+
+ def getJsonResponse(self, *args, **kwargs):
+ res = self.load(*args, **kwargs)
+ if not res.startswith('{'):
+ self.retry()
+
+ self.logDebug(res)
+
+ return json_loads(res)
diff --git a/pyload/plugin/hoster/RarefileNet.py b/pyload/plugin/hoster/RarefileNet.py
new file mode 100644
index 000000000..f89708282
--- /dev/null
+++ b/pyload/plugin/hoster/RarefileNet.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class RarefileNet(XFSHoster):
+ __name = "RarefileNet"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?rarefile\.net/\w{12}'
+
+ __description = """Rarefile.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<a href="(.+?)">\1</a>'
diff --git a/pyload/plugin/hoster/RealdebridCom.py b/pyload/plugin/hoster/RealdebridCom.py
new file mode 100644
index 000000000..e9c55fae3
--- /dev/null
+++ b/pyload/plugin/hoster/RealdebridCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class RealdebridCom(MultiHoster):
+ __name = "RealdebridCom"
+ __type = "hoster"
+ __version = "0.67"
+
+ __pattern = r'https?://((?:www\.|s\d+\.)?real-debrid\.com/dl/|[\w^_]\.rdb\.so/d/)[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Real-Debrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 3
+
+
+ def handlePremium(self, pyfile):
+ data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php",
+ get={'lang' : "en",
+ 'link' : pyfile.url,
+ 'password': self.getPassword(),
+ 'time' : int(time.time() * 1000)}))
+
+ self.logDebug("Returned Data: %s" % data)
+
+ if data['error'] != 0:
+ if data['message'] == "Your file is unavailable on the hoster.":
+ self.offline()
+ else:
+ self.logWarning(data['message'])
+ self.tempOffline()
+ else:
+ if pyfile.name and pyfile.name.endswith('.tmp') and data['file_name']:
+ pyfile.name = data['file_name']
+ pyfile.size = parseFileSize(data['file_size'])
+ self.link = data['generated_links'][0][-1]
+
+ if self.getConfig('ssl'):
+ self.link = self.link.replace("http://", "https://")
+ else:
+ self.link = self.link.replace("https://", "http://")
diff --git a/pyload/plugin/hoster/RedtubeCom.py b/pyload/plugin/hoster/RedtubeCom.py
new file mode 100644
index 000000000..4b9afbc2b
--- /dev/null
+++ b/pyload/plugin/hoster/RedtubeCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class RedtubeCom(Hoster):
+ __name = "RedtubeCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?redtube\.com/\d+'
+
+ __description = """Redtube.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de")]
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ file_url = html_unescape(re.search(r'hashlink=(http.*?)"', self.html).group(1))
+
+ return file_url
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search('<title>(.*?)- RedTube - Free Porn Videos</title>', self.html).group(1).strip() + ".flv"
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r'This video has been removed.', self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/RehostTo.py b/pyload/plugin/hoster/RehostTo.py
new file mode 100644
index 000000000..c707a866f
--- /dev/null
+++ b/pyload/plugin/hoster/RehostTo.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class RehostTo(MultiHoster):
+ __name = "RehostTo"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://.*rehost\.to\..+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rehost.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def handlePremium(self, pyfile):
+ self.download("http://rehost.to/process_download.php",
+ get={'user': "cookie",
+ 'pass': self.account.getAccountInfo(self.user)['session'],
+ 'dl' : pyfile.url},
+ disposition=True)
diff --git a/pyload/plugin/hoster/RemixshareCom.py b/pyload/plugin/hoster/RemixshareCom.py
new file mode 100644
index 000000000..ba61887cd
--- /dev/null
+++ b/pyload/plugin/hoster/RemixshareCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://remixshare.com/download/z8uli
+#
+# Note:
+# The remixshare.com website is very very slow, so
+# if your download not starts because of pycurl timeouts:
+# Adjust timeouts in /usr/share/pyload/pyload/network/HTTPRequest.py
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RemixshareCom(SimpleHoster):
+ __name = "RemixshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://remixshare\.com/(download|dl)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Remixshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de" ),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("sraedler" , "simon.raedler@yahoo.de")]
+
+
+ INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>[\w^_]+)\)<'
+ HASHSUM_PATTERN = r'>(?P<T>MD5): (?P<H>\w+)'
+ OFFLINE_PATTERN = r'<h1>Ooops!'
+
+ LINK_PATTERN = r'var uri = "(.+?)"'
+ TOKEN_PATTERN = r'var acc = (\d+)'
+
+ WAIT_PATTERN = r'var XYZ = "(\d+)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ b = re.search(self.LINK_PATTERN, self.html)
+ if not b:
+ self.error(_("File url"))
+
+ c = re.search(self.TOKEN_PATTERN, self.html)
+ if not c:
+ self.error(_("File token"))
+
+ self.link = b.group(1) + "/zzz/" + c.group(1)
diff --git a/pyload/plugin/hoster/RgHostNet.py b/pyload/plugin/hoster/RgHostNet.py
new file mode 100644
index 000000000..14c92f9ab
--- /dev/null
+++ b/pyload/plugin/hoster/RgHostNet.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RgHostNet(SimpleHoster):
+ __name = "RgHostNet"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?rghost\.(net|ru)/[\d-]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """RgHost.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
+
+
+ INFO_PATTERN = r'data-share42-text="(?P<N>.+?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ HASHSUM_PATTERN = r'<dt>(?P<T>\w+)</dt>\s*<dd>(?P<H>\w+)'
+ OFFLINE_PATTERN = r'>(File is deleted|page not found)'
+
+ LINK_FREE_PATTERN = r'<a href="(.+?)" class="btn large'
diff --git a/pyload/plugin/hoster/SafesharingEu.py b/pyload/plugin/hoster/SafesharingEu.py
new file mode 100644
index 000000000..0676015a2
--- /dev/null
+++ b/pyload/plugin/hoster/SafesharingEu.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class SafesharingEu(XFSHoster):
+ __name = "SafesharingEu"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?safesharing\.eu/\w{12}'
+
+ __description = """Safesharing.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)'
diff --git a/pyload/plugin/hoster/SecureUploadEu.py b/pyload/plugin/hoster/SecureUploadEu.py
new file mode 100644
index 000000000..8cd310c2c
--- /dev/null
+++ b/pyload/plugin/hoster/SecureUploadEu.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class SecureUploadEu(XFSHoster):
+ __name = "SecureUploadEu"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?secureupload\.eu/\w{12}'
+
+ __description = """SecureUpload.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
+
+
+ INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
diff --git a/pyload/plugin/hoster/SendspaceCom.py b/pyload/plugin/hoster/SendspaceCom.py
new file mode 100644
index 000000000..740774313
--- /dev/null
+++ b/pyload/plugin/hoster/SendspaceCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SendspaceCom(SimpleHoster):
+ __name = "SendspaceCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'https?://(?:www\.)?sendspace\.com/file/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Sendspace.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
+ SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)\s*</div>'
+ OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
+
+ LINK_FREE_PATTERN = r'<a id="download_button" href="(.+?)"'
+
+ CAPTCHA_PATTERN = r'<td><img src="(/captchas/captcha\.php?captcha=(.+?))"></td>'
+ USER_CAPTCHA_PATTERN = r'<td><img src="/captchas/captcha\.php?user=(.+?))"></td>'
+
+
+ def handleFree(self, pyfile):
+ params = {}
+ for _i in xrange(3):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.correctCaptcha()
+ self.link = m.group(1)
+ break
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.invalidCaptcha()
+ captcha_url1 = "http://www.sendspace.com/" + m.group(1)
+ m = re.search(self.USER_CAPTCHA_PATTERN, self.html)
+ captcha_url2 = "http://www.sendspace.com/" + m.group(1)
+ params = {'captcha_hash': m.group(2),
+ 'captcha_submit': 'Verify',
+ 'captcha_answer': self.decryptCaptcha(captcha_url1) + " " + self.decryptCaptcha(captcha_url2)}
+ else:
+ params = {'download': "Regular Download"}
+
+ self.logDebug(params)
+ self.html = self.load(pyfile.url, post=params)
+ else:
+ self.fail(_("Download link not found"))
diff --git a/pyload/plugin/hoster/Share4WebCom.py b/pyload/plugin/hoster/Share4WebCom.py
new file mode 100644
index 000000000..72e3e4a02
--- /dev/null
+++ b/pyload/plugin/hoster/Share4WebCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.UnibytesCom import UnibytesCom
+
+
+class Share4WebCom(UnibytesCom):
+ __name = "Share4WebCom"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://(?:www\.)?share4web\.com/get/\w+'
+
+ __description = """Share4web.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "share4web.com"
diff --git a/pyload/plugin/hoster/Share76Com.py b/pyload/plugin/hoster/Share76Com.py
new file mode 100644
index 000000000..fda983401
--- /dev/null
+++ b/pyload/plugin/hoster/Share76Com.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class Share76Com(DeadHoster):
+ __name = "Share76Com"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?share76\.com/\w{12}'
+ __config = []
+
+ __description = """Share76.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
diff --git a/pyload/plugin/hoster/ShareFilesCo.py b/pyload/plugin/hoster/ShareFilesCo.py
new file mode 100644
index 000000000..37e386034
--- /dev/null
+++ b/pyload/plugin/hoster/ShareFilesCo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ShareFilesCo(DeadHoster):
+ __name = "ShareFilesCo"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?sharefiles\.co/\w{12}'
+ __config = []
+
+ __description = """Sharefiles.co hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/SharebeesCom.py b/pyload/plugin/hoster/SharebeesCom.py
new file mode 100644
index 000000000..5b5017fc2
--- /dev/null
+++ b/pyload/plugin/hoster/SharebeesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SharebeesCom(DeadHoster):
+ __name = "SharebeesCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?sharebees\.com/\w{12}'
+ __config = []
+
+ __description = """ShareBees hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/ShareonlineBiz.py b/pyload/plugin/hoster/ShareonlineBiz.py
new file mode 100644
index 000000000..0f5a8692a
--- /dev/null
+++ b/pyload/plugin/hoster/ShareonlineBiz.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urllib
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class ShareonlineBiz(SimpleHoster):
+ __name = "ShareonlineBiz"
+ __type = "hoster"
+ __version = "0.49"
+
+ __pattern = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Shareonline.biz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://www.share-online.biz/dl/\g<ID>")]
+
+ CHECK_TRAFFIC = True
+
+ RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX"
+
+ ERROR_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = {'name': urlparse.urlparse(urllib.unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': url}
+
+ if url:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict()
+
+ field = getURL("http://api.share-online.biz/linkcheck.php",
+ get={'md5': "1"},
+ post={'links': info['pattern']['ID']},
+ decode=True).split(";")
+
+ if field[1] == "OK":
+ info['fileid'] = field[0]
+ info['status'] = 2
+ info['name'] = field[2]
+ info['size'] = field[3] #: in bytes
+ info['md5'] = field[4].strip().lower().replace("\n\n", "") #: md5
+
+ elif field[1] in ("DELETED", "NOT FOUND"):
+ info['status'] = 1
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = self.premium
+ self.multiDL = False
+
+
+ def handleCaptcha(self):
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ m = re.search(r'var wait=(\d+);', self.html)
+ self.setWait(int(m.group(1)) if m else 30)
+
+ res = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time.time() * 1000)),
+ post={'dl_free' : "1",
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+ if not res == '0':
+ self.correctCaptcha()
+ return res
+ else:
+ self.invalidCaptcha()
+ else:
+ self.invalidCaptcha()
+ self.fail(_("No valid captcha solution received"))
+
+
+ def handleFree(self, pyfile):
+ self.wait(3)
+
+ self.html = self.load("%s/free/" % pyfile.url,
+ post={'dl_free': "1", 'choice': "free"},
+ decode=True)
+
+ self.checkErrors()
+
+ res = self.handleCaptcha()
+ self.link = res.decode('base64')
+
+ if not self.link.startswith("http://"):
+ self.error(_("Wrong download url"))
+
+ self.wait()
+
+
+ def checkFile(self, rules={}):
+ check = self.checkDownload({'cookie': re.compile(r'<div id="dl_failure"'),
+ 'fail' : re.compile(r"<title>Share-Online")})
+
+ if check == "cookie":
+ self.invalidCaptcha()
+ self.retry(5, 60, _("Cookie failure"))
+
+ elif check == "fail":
+ self.invalidCaptcha()
+ self.retry(5, 5 * 60, _("Download failed"))
+
+ return super(ShareonlineBiz, self).checkFile(rules)
+
+
+ def handlePremium(self, pyfile): #: should be working better loading (account) api internally
+ html = self.load("http://api.share-online.biz/account.php",
+ get={'username': self.user,
+ 'password': self.account.getAccountData(self.user)['password'],
+ 'act' : "download",
+ 'lid' : self.info['fileid']})
+
+ self.api_data = dlinfo = {}
+
+ for line in html.splitlines():
+ key, value = line.split(": ")
+ dlinfo[key.lower()] = value
+
+ self.logDebug(dlinfo)
+
+ if not dlinfo['status'] == "online":
+ self.offline()
+ else:
+ pyfile.name = dlinfo['name']
+ pyfile.size = int(dlinfo['size'])
+
+ self.link = dlinfo['url']
+
+ if self.link == "server_under_maintenance":
+ self.tempOffline()
+ else:
+ self.multiDL = True
+
+
+ def checkErrors(self):
+ m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
+ if m is None:
+ self.info.pop('error', None)
+ return
+
+ errmsg = m.group(1).lower()
+
+ try:
+ self.logError(errmsg, re.search(self.ERROR_PATTERN, self.html).group(1))
+ except Exception:
+ self.logError("Unknown error occurred", errmsg)
+
+ if errmsg is "invalid":
+ self.fail(_("File not available"))
+
+ elif errmsg in ("freelimit", "size", "proxy"):
+ self.fail(_("Premium account needed"))
+
+ elif errmsg in ("expired", "server"):
+ self.retry(wait_time=600, reason=errmsg)
+
+ elif 'slot' in errmsg:
+ self.wantReconnect = True
+ self.retry(24, 3600, errmsg)
+
+ else:
+ self.wantReconnect = True
+ self.retry(wait_time=60, reason=errmsg)
diff --git a/pyload/plugin/hoster/ShareplaceCom.py b/pyload/plugin/hoster/ShareplaceCom.py
new file mode 100644
index 000000000..b1361e859
--- /dev/null
+++ b/pyload/plugin/hoster/ShareplaceCom.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Hoster import Hoster
+
+
+class ShareplaceCom(Hoster):
+ __name = "ShareplaceCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?shareplace\.(com|org)/\?\w+'
+
+ __description = """Shareplace.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("ACCakut", "")]
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.prepare()
+ self.download(self.get_file_url())
+
+
+ def prepare(self):
+ if not self.file_exists():
+ self.offline()
+
+ self.pyfile.name = self.get_file_name()
+
+ wait_time = self.get_waiting_time()
+ self.setWait(wait_time)
+ self.wait()
+
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ # var zzipitime = 15;
+ m = re.search(r'var zzipitime = (\d+);', self.html)
+ if m:
+ sec = int(m.group(1))
+ else:
+ sec = 0
+
+ return sec
+
+
+ def download_html(self):
+ url = re.sub("shareplace.com\/\?", "shareplace.com//index1.php/?a=", self.pyfile.url)
+ self.html = self.load(url, decode=True)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ url = re.search(r"var beer = '(.*?)';", self.html)
+ if url:
+ url = url.group(1)
+ url = urllib.unquote(
+ url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
+ "teletubbies", ""))
+ self.logDebug("URL: %s" % url)
+ return url
+ else:
+ self.error(_("Absolute filepath not found"))
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search("<title>\s*(.*?)\s*</title>", self.html).group(1)
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r"HTTP Status 404", self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/SharingmatrixCom.py b/pyload/plugin/hoster/SharingmatrixCom.py
new file mode 100644
index 000000000..c4938f9bc
--- /dev/null
+++ b/pyload/plugin/hoster/SharingmatrixCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SharingmatrixCom(DeadHoster):
+ __name = "SharingmatrixCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sharingmatrix\.com/file/\w+'
+ __config = []
+
+ __description = """Sharingmatrix.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", "")]
diff --git a/pyload/plugin/hoster/ShragleCom.py b/pyload/plugin/hoster/ShragleCom.py
new file mode 100644
index 000000000..c435d01a7
--- /dev/null
+++ b/pyload/plugin/hoster/ShragleCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ShragleCom(DeadHoster):
+ __name = "ShragleCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?(cloudnator|shragle)\.com/files/(?P<ID>.+?)/'
+ __config = []
+
+ __description = """Cloudnator.com (Shragle.com) hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/SimplyPremiumCom.py b/pyload/plugin/hoster/SimplyPremiumCom.py
new file mode 100644
index 000000000..327bfdcc1
--- /dev/null
+++ b/pyload/plugin/hoster/SimplyPremiumCom.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+
+
+class SimplyPremiumCom(MultiHoster):
+ __name = "SimplyPremiumCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://.+simply-premium\.com'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Simply-Premium.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+
+
+ def checkErrors(self):
+ if '<valid>0</valid>' in self.html or (
+ "You are not allowed to download from this host" in self.html and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+
+ elif "NOTFOUND" in self.html:
+ self.offline()
+
+ elif "downloadlimit" in self.html:
+ self.logWarning(_("Reached maximum connctions"))
+ self.retry(5, 60, _("Reached maximum connctions"))
+
+ elif "trafficlimit" in self.html:
+ self.logWarning(_("Reached daily limit for this host"))
+ self.retry(wait_time=secondsToMidnight(gmt=2), reason="Daily limit for this host reached")
+
+ elif "hostererror" in self.html:
+ self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
+ self.retry(5, 60, _("Hoster is temporarily unavailable"))
+
+
+ def handlePremium(self, pyfile):
+ for _i in xrange(5):
+ self.html = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': self.pyfile.url})
+
+ if self.html:
+ self.logDebug("JSON data: " + self.html)
+ break
+ else:
+ self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
+ self.retry(5, 60, _("Unable to get API data"))
+
+ self.checkErrors()
+
+ try:
+ self.pyfile.name = re.search(r'<name>([^<]+)</name>', self.html).group(1)
+
+ except AttributeError:
+ self.pyfile.name = ""
+
+ try:
+ self.pyfile.size = re.search(r'<size>(\d+)</size>', self.html).group(1)
+
+ except AttributeError:
+ self.pyfile.size = 0
+
+ try:
+ self.link = re.search(r'<download>([^<]+)</download>', self.html).group(1)
+
+ except AttributeError:
+ self.link = 'http://www.simply-premium.com/premium.php?link=' + self.pyfile.url
diff --git a/pyload/plugin/hoster/SimplydebridCom.py b/pyload/plugin/hoster/SimplydebridCom.py
new file mode 100644
index 000000000..d703c3e52
--- /dev/null
+++ b/pyload/plugin/hoster/SimplydebridCom.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster, replace_patterns
+
+
+class SimplydebridCom(MultiHoster):
+ __name = "SimplydebridCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Simply-debrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def handlePremium(self, pyfile):
+ # fix the links for simply-debrid.com!
+ self.link = replace_patterns(pyfile.url, [("clz.to", "cloudzer.net/file")
+ ("http://share-online", "http://www.share-online")
+ ("ul.to", "uploaded.net/file")
+ ("uploaded.com", "uploaded.net")
+ ("filerio.com", "filerio.in")
+ ("lumfile.com", "lumfile.se")])
+
+ if 'fileparadox' in self.link:
+ self.link = self.link.replace("http://", "https://")
+
+ self.html = self.load("http://simply-debrid.com/api.php", get={'dl': self.link})
+ if 'tiger Link' in self.html or 'Invalid Link' in self.html or ('API' in self.html and 'ERROR' in self.html):
+ self.error(_("Unable to unrestrict link"))
+
+ self.link = self.html
+
+ self.wait(5)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"error": "No address associated with hostname"}):
+ self.retry(24, 3 * 60, _("Bad file downloaded"))
+
+ return super(SimplydebridCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/SmoozedCom.py b/pyload/plugin/hoster/SmoozedCom.py
new file mode 100644
index 000000000..f216a95bc
--- /dev/null
+++ b/pyload/plugin/hoster/SmoozedCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class SmoozedCom(MultiHoster):
+ __name = "SmoozedCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Smoozed.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def handlePremium(self, pyfile):
+ # In some cases hostsers do not supply us with a filename at download, so we
+ # are going to set a fall back filename (e.g. for freakshare or xfileshare)
+ pyfile.name = pyfile.name.split('/').pop() #: Remove everthing before last slash
+
+ # Correction for automatic assigned filename: Removing html at end if needed
+ suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
+ temp = pyfile.name.split('.')
+
+ if temp.pop() in suffix_to_remove:
+ pyfile.name = ".".join(temp)
+
+ # Check the link
+ get_data = {'session_key': self.account.getAccountInfo(self.user)['session'],
+ 'url' : pyfile.url}
+
+ data = json_loads(self.load("http://www2.smoozed.com/api/check", get=get_data))
+
+ if data['state'] != "ok":
+ self.fail(data['message'])
+
+ if data['data'].get("state", "ok") != "ok":
+ if data['data'] == "Offline":
+ self.offline()
+ else:
+ self.fail(data['data']['message'])
+
+ pyfile.name = data['data']['name']
+ pyfile.size = int(data['data']['size'])
+
+ # Start the download
+ header = self.load("http://www2.smoozed.com/api/download", get=get_data, just_header=True)
+
+ if not "location" in header:
+ self.fail(_("Unable to initialize download"))
+ else:
+ self.link = header['location'][-1] if isinstance(header['location'], list) else header['location']
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'error': '{"state":"error"}',
+ 'retry': '{"state":"retry"}'}):
+ self.fail(_("Error response received"))
+
+ return super(SmoozedCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/SockshareCom.py b/pyload/plugin/hoster/SockshareCom.py
new file mode 100644
index 000000000..f6cff5858
--- /dev/null
+++ b/pyload/plugin/hoster/SockshareCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SockshareCom(DeadHoster):
+ __name = "SockshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Sockshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/SolidfilesCom.py b/pyload/plugin/hoster/SolidfilesCom.py
new file mode 100644
index 000000000..9998f26ad
--- /dev/null
+++ b/pyload/plugin/hoster/SolidfilesCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.solidfiles.com/d/609cdb4b1b
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SolidfilesCom(SimpleHoster):
+ __name = "SolidfilesCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?solidfiles\.com\/d/\w+'
+
+ __description = """Solidfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sraedler", "simon.raedler@yahoo.de")]
+
+
+ NAME_PATTERN = r'<h1 title="(?P<N>.+?)"'
+ SIZE_PATTERN = r'<p class="meta">(?P<S>[\d.,]+) (?P<U>[\w_^]+)'
+ OFFLINE_PATTERN = r'<h1>404'
+
+ LINK_FREE_PATTERN = r'id="ddl-text" href="(.+?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/SoundcloudCom.py b/pyload/plugin/hoster/SoundcloudCom.py
new file mode 100644
index 000000000..7ce0e3787
--- /dev/null
+++ b/pyload/plugin/hoster/SoundcloudCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import json_loads
+
+
+class SoundcloudCom(SimpleHoster):
+ __name = "SoundcloudCom"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://(?:www\.)?soundcloud\.com/[\w-]+/[\w-]+'
+ __config = [("use_premium", "bool" , "Use premium account if available", True ),
+ ("quality" , "Lower;Higher", "Quality" , "Higher")]
+
+ __description = """SoundCloud.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'title" content="(?P<N>.+?)"'
+ OFFLINE_PATTERN = r'<title>"SoundCloud - Hear the world’s sounds"</title>'
+
+
+ def handleFree(self, pyfile):
+ try:
+ song_id = re.search(r'sounds:(\d+)"', self.html).group(1)
+
+ except Exception:
+ self.error(_("Could not find song id"))
+
+ try:
+ client_id = re.search(r'"clientID":"(.+?)"', self.html).group(1)
+
+ except Exception:
+ client_id = "b45b1aa10f1ac2941910a7f0d10f8e28"
+
+ # url to retrieve the actual song url
+ streams = json_loads(self.load("https://api.soundcloud.com/tracks/%s/streams" % song_id,
+ get={'client_id': client_id}))
+
+ regex = re.compile(r'[^\d]')
+ http_streams = sorted([(key, value) for key, value in streams.iteritems() if key.startswith('http_')],
+ key=lambda t: regex.sub(t[0], ''),
+ reverse=True)
+
+ self.logDebug("Streams found: %s" % (http_streams or "None"))
+
+ if http_streams:
+ stream_name, self.link = http_streams[0 if self.getConfig('quality') == "Higher" else -1]
+ pyfile.name += '.' + stream_name.split('_')[1].lower()
diff --git a/pyload/plugin/hoster/SpeedLoadOrg.py b/pyload/plugin/hoster/SpeedLoadOrg.py
new file mode 100644
index 000000000..383d60817
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedLoadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SpeedLoadOrg(DeadHoster):
+ __name = "SpeedLoadOrg"
+ __type = "hoster"
+ __version = "1.02"
+
+ __pattern = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Speedload.org hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/SpeedfileCz.py b/pyload/plugin/hoster/SpeedfileCz.py
new file mode 100644
index 000000000..bb1b2c527
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedfileCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SpeedfileCz(DeadHoster):
+ __name = "SpeedFileCz"
+ __type = "hoster"
+ __version = "0.32"
+
+ __pattern = r'http://(?:www\.)?speedfile\.cz/.+'
+ __config = []
+
+ __description = """Speedfile.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/SpeedyshareCom.py b/pyload/plugin/hoster/SpeedyshareCom.py
new file mode 100644
index 000000000..5133725fd
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedyshareCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SpeedyshareCom(SimpleHoster):
+ __name = "SpeedyshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Speedyshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
+ SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
+
+ OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
+
+ LINK_FREE_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload\.png alt=\'Slow Download\' border=0'
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/StorageTo.py b/pyload/plugin/hoster/StorageTo.py
new file mode 100644
index 000000000..8c0cc046e
--- /dev/null
+++ b/pyload/plugin/hoster/StorageTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class StorageTo(DeadHoster):
+ __name = "StorageTo"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?storage\.to/get/.+'
+ __config = []
+
+ __description = """Storage.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/StreamCz.py b/pyload/plugin/hoster/StreamCz.py
new file mode 100644
index 000000000..0500574ca
--- /dev/null
+++ b/pyload/plugin/hoster/StreamCz.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+
+
+def getInfo(urls):
+ result = []
+
+ for url in urls:
+
+ html = getURL(url)
+ if re.search(StreamCz.OFFLINE_PATTERN, html):
+ # File offline
+ result.append((url, 0, 1, url))
+ else:
+ result.append((url, 0, 2, url))
+ yield result
+
+
+class StreamCz(Hoster):
+ __name = "StreamCz"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+'
+
+ __description = """Stream.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<link rel="video_src" href="http://www\.stream\.cz/\w+/(\d+)-(.+?)" />'
+ OFFLINE_PATTERN = r'<h1 class="commonTitle">Str.nku nebylo mo.n. nal.zt \(404\)</h1>'
+
+ CDN_PATTERN = r'<param name="flashvars" value=".+?&id=(?P<ID>\d+)(?:&cdnLQ=(?P<cdnLQ>\d*))?(?:&cdnHQ=(?P<cdnHQ>\d*))?(?:&cdnHD=(?P<cdnHD>\d*))?&'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ if re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ m = re.search(self.CDN_PATTERN, self.html)
+ if m is None:
+ self.error(_("CDN_PATTERN not found"))
+ cdn = m.groupdict()
+ self.logDebug(cdn)
+ for cdnkey in ("cdnHD", "cdnHQ", "cdnLQ"):
+ if cdnkey in cdn and cdn[cdnkey] > '':
+ cdnid = cdn[cdnkey]
+ break
+ else:
+ self.fail(_("Stream URL not found"))
+
+ m = re.search(self.NAME_PATTERN, self.html)
+ if m is None:
+ self.error(_("NAME_PATTERN not found"))
+ pyfile.name = "%s-%s.%s.mp4" % (m.group(2), m.group(1), cdnkey[-2:])
+
+ download_url = "http://cdn-dispatcher.stream.cz/?id=" + cdnid
+ self.logInfo(_("STREAM: %s") % cdnkey[-2:], download_url)
+ self.download(download_url)
diff --git a/pyload/plugin/hoster/StreamcloudEu.py b/pyload/plugin/hoster/StreamcloudEu.py
new file mode 100644
index 000000000..cdf244539
--- /dev/null
+++ b/pyload/plugin/hoster/StreamcloudEu.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class StreamcloudEu(XFSHoster):
+ __name = "StreamcloudEu"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?streamcloud\.eu/\w{12}'
+
+ __description = """Streamcloud.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("seoester", "seoester@googlemail.com")]
+
+
+ WAIT_PATTERN = r'var count = (\d+)'
+
+ LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud\.eu:?\d*/.*/video\.(mp4|flv))",'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
diff --git a/pyload/plugin/hoster/TurbobitNet.py b/pyload/plugin/hoster/TurbobitNet.py
new file mode 100644
index 000000000..a55d824a3
--- /dev/null
+++ b/pyload/plugin/hoster/TurbobitNet.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import pycurl
+import random
+import re
+import time
+import urllib
+
+from Crypto.Cipher import ARC4
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp
+
+
+class TurbobitNet(SimpleHoster):
+ __name = "TurbobitNet"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Turbobit.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("prOq", "")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://turbobit.net/\g<ID>.html")]
+
+ COOKIES = [("turbobit.net", "user_lang", "en")]
+
+ NAME_PATTERN = r'id="file-title">(?P<N>.+?)<'
+ SIZE_PATTERN = r'class="file-size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'(/download/redirect/[^"\']+)'
+
+ LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<'
+ CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"'
+
+
+ def handleFree(self, pyfile):
+ self.html = self.load("http://turbobit.net/download/free/%s" % self.info['pattern']['ID'],
+ decode=True)
+
+ rtUpdate = self.getRtUpdate()
+
+ self.solveCaptcha()
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ self.html = self.load(self.getDownloadUrl(rtUpdate))
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+
+
+ def solveCaptcha(self):
+ for _i in xrange(5):
+ m = re.search(self.LIMIT_WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1))
+ self.wait(wait_time, wait_time > 60)
+ self.retry()
+
+ action, inputs = self.parseHtmlForm("action='#'")
+ if not inputs:
+ self.error(_("Captcha form not found"))
+ self.logDebug(inputs)
+
+ if inputs['captcha_type'] == 'recaptcha':
+ recaptcha = ReCaptcha(self)
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
+ else:
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("captcha"))
+ captcha_url = m.group(1)
+ inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
+
+ self.logDebug(inputs)
+ self.html = self.load(self.url, post=inputs)
+
+ if '<div class="captcha-error">Incorrect, try again!<' in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("Invalid captcha"))
+
+
+ def getRtUpdate(self):
+ rtUpdate = self.getStorage("rtUpdate")
+ if not rtUpdate:
+ if self.getStorage("version") != self.__version \
+ or int(self.getStorage("timestamp", 0)) + 86400000 < timestamp():
+ # that's right, we are even using jdownloader updates
+ rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js")
+ rtUpdate = self.decrypt(rtUpdate.splitlines()[1])
+ # but we still need to fix the syntax to work with other engines than rhino
+ rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{',
+ r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];', rtUpdate)
+ rtUpdate = re.sub(r"for\((\w+)=", r"for(var \1=", rtUpdate)
+
+ self.setStorage("rtUpdate", rtUpdate)
+ self.setStorage("timestamp", timestamp())
+ self.setStorage("version", self.__version)
+ else:
+ self.logError(_("Unable to download, wait for update..."))
+ self.tempOffline()
+
+ return rtUpdate
+
+
+ def getDownloadUrl(self, rtUpdate):
+ self.req.http.lastURL = self.url
+
+ m = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html)
+ if m:
+ url = "http://turbobit.net%s%s" % m.groups()
+ else:
+ url = "http://turbobit.net/files/timeout.js?ver=%s" % "".join(random.choice('0123456789ABCDEF') for _i in xrange(32))
+
+ fun = self.load(url)
+
+ self.setWait(65, False)
+
+ for b in [1, 3]:
+ self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % (
+ self.info['pattern']['ID'], b, urllib.quote(fun), rtUpdate)
+
+ try:
+ out = self.js.eval(self.jscode)
+ self.logDebug("URL", self.js.engine, out)
+ if out.startswith('/download/'):
+ return "http://turbobit.net%s" % out.strip()
+
+ except Exception, e:
+ self.logError(e)
+ else:
+ if self.retries >= 2:
+ # retry with updated js
+ self.delStorage("rtUpdate")
+ else:
+ self.retry()
+
+ self.wait()
+
+
+ def decrypt(self, data):
+ cipher = ARC4.new(binascii.hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
+ return binascii.unhexlify(cipher.encrypt(binascii.unhexlify(data)))
+
+
+ def getLocalTimeString(self):
+ lt = time.localtime()
+ tz = time.altzone if lt.tm_isdst else time.timezone
+ return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600)
diff --git a/pyload/plugin/hoster/TurbouploadCom.py b/pyload/plugin/hoster/TurbouploadCom.py
new file mode 100644
index 000000000..1452f05be
--- /dev/null
+++ b/pyload/plugin/hoster/TurbouploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class TurbouploadCom(DeadHoster):
+ __name = "TurbouploadCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?turboupload\.com/(\w+)'
+ __config = []
+
+ __description = """Turboupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/TusfilesNet.py b/pyload/plugin/hoster/TusfilesNet.py
new file mode 100644
index 000000000..068b8df58
--- /dev/null
+++ b/pyload/plugin/hoster/TusfilesNet.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class TusfilesNet(XFSHoster):
+ __name = "TusfilesNet"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:www\.)?tusfiles\.net/\w{12}'
+
+ __description = """Tusfiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("guidobelix", "guidobelix@hotmail.it")]
+
+
+ INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\['
+ OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!|The file you are trying to download is no longer available'
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def downloadLink(self, link, disposition=True):
+ try:
+ return super(TusfilesNet, self).downloadLink(link, disposition)
+
+ except BadHeader, e:
+ if e.code is 503:
+ self.multiDL = False
+ raise Retry("503")
diff --git a/pyload/plugin/hoster/TwoSharedCom.py b/pyload/plugin/hoster/TwoSharedCom.py
new file mode 100644
index 000000000..f116abfc1
--- /dev/null
+++ b/pyload/plugin/hoster/TwoSharedCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class TwoSharedCom(SimpleHoster):
+ __name = "TwoSharedCom"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """2Shared.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
+ SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
+
+ LINK_FREE_PATTERN = r'window.location =\'(.+?)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
diff --git a/pyload/plugin/hoster/UlozTo.py b/pyload/plugin/hoster/UlozTo.py
new file mode 100644
index 000000000..49d5d2ac1
--- /dev/null
+++ b/pyload/plugin/hoster/UlozTo.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+def convertDecimalPrefix(m):
+ # decimal prefixes used in filesize and traffic
+ return ("%%.%df" % {'k': 3, 'M': 6, 'G': 9}[m.group(2)] % float(m.group(1))).replace('.', '')
+
+
+class UlozTo(SimpleHoster):
+ __name = "UlozTo"
+ __type = "hoster"
+ __version = "1.08"
+
+ __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<ID>\w+/[^/?]*)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uloz.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
+ NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz\.to</title>'
+ SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[\d.,]+\s[kMG]?B)</span>'
+ OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
+
+ URL_REPLACEMENTS = [(r'(?<=http://)([^/]+)', "www.ulozto.net")]
+ SIZE_REPLACEMENTS = [(r'([\d.]+)\s([kMG])B', convertDecimalPrefix)]
+
+ CHECK_TRAFFIC = True
+
+ ADULT_PATTERN = r'<form action="(.+?)" method="post" id="frm-askAgeForm">'
+ PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
+ VIPLINK_PATTERN = r'<a href=".+?\?disclaimer=1" class="linkVip">'
+ TOKEN_PATTERN = r'<input type="hidden" name="_token_" .*?value="(.+?)"'
+
+
+ def setup(self):
+ self.chunkLimit = 16 if self.premium else 1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
+ if not action or not inputs:
+ self.error(_("Free download form not found"))
+
+ self.logDebug("inputs.keys = " + str(inputs.keys()))
+ # get and decrypt captcha
+ if all(key in inputs for key in ("captcha_value", "captcha_id", "captcha_key")):
+ # Old version - last seen 9.12.2013
+ self.logDebug('Using "old" version')
+
+ captcha_value = self.decryptCaptcha("http://img.uloz.to/captcha/%s.png" % inputs['captcha_id'])
+ self.logDebug("CAPTCHA ID: " + inputs['captcha_id'] + ", CAPTCHA VALUE: " + captcha_value)
+
+ inputs.update({'captcha_id': inputs['captcha_id'], 'captcha_key': inputs['captcha_key'], 'captcha_value': captcha_value})
+
+ elif all(key in inputs for key in ("captcha_value", "timestamp", "salt", "hash")):
+ # New version - better to get new parameters (like captcha reload) because of image url - since 6.12.2013
+ self.logDebug('Using "new" version')
+
+ xapca = self.load("http://www.ulozto.net/reloadXapca.php", get={'rnd': str(int(time.time()))})
+ self.logDebug("xapca = " + str(xapca))
+
+ data = json_loads(xapca)
+ captcha_value = self.decryptCaptcha(str(data['image']))
+ self.logDebug("CAPTCHA HASH: " + data['hash'], "CAPTCHA SALT: " + str(data['salt']), "CAPTCHA VALUE: " + captcha_value)
+
+ inputs.update({'timestamp': data['timestamp'], 'salt': data['salt'], 'hash': data['hash'], 'captcha_value': captcha_value})
+
+ else:
+ self.error(_("CAPTCHA form changed"))
+
+ self.download("http://www.ulozto.net" + action, post=inputs)
+
+
+ def handlePremium(self, pyfile):
+ self.download(pyfile.url, get={'do': "directDownload"})
+
+
+ def checkErrors(self):
+ if re.search(self.ADULT_PATTERN, self.html):
+ self.logInfo(_("Adult content confirmation needed"))
+
+ m = re.search(self.TOKEN_PATTERN, self.html)
+ if m is None:
+ self.error(_("TOKEN_PATTERN not found"))
+
+ self.html = self.load(pyfile.url,
+ get={'do': "askAgeForm-submit"},
+ post={"agree": "Confirm", "_token_": m.group(1)})
+
+ if self.PASSWD_PATTERN in self.html:
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + password)
+ self.html = self.load(pyfile.url,
+ get={'do': "passwordProtectedForm-submit"},
+ post={"password": password, "password_send": 'Send'})
+
+ if self.PASSWD_PATTERN in self.html:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ if re.search(self.VIPLINK_PATTERN, self.html):
+ self.html = self.load(pyfile.url, get={'disclaimer': "1"})
+
+ return super(UlozTo, self).checkErrors()
+
+
+ def checkFile(self, rules={}):
+ check = self.checkDownload({
+ "wrong_captcha": re.compile(r'<ul class="error">\s*<li>Error rewriting the text.</li>'),
+ "offline" : re.compile(self.OFFLINE_PATTERN),
+ "passwd" : self.PASSWD_PATTERN,
+ "server_error" : 'src="http://img.ulozto.cz/error403/vykricnik.jpg"', #: paralell dl, server overload etc.
+ "not_found" : "<title>UloÅŸ.to</title>"
+ })
+
+ if check == "wrong_captcha":
+ self.invalidCaptcha()
+ self.retry(reason=_("Wrong captcha code"))
+
+ elif check == "offline":
+ self.offline()
+
+ elif check == "passwd":
+ self.fail(_("Wrong password"))
+
+ elif check == "server_error":
+ self.logError(_("Server error, try downloading later"))
+ self.multiDL = False
+ self.wait(1 * 60 * 60, True)
+ self.retry()
+
+ elif check == "not_found":
+ self.fail(_("Server error, file not downloadable"))
+
+ return super(UlozTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UloziskoSk.py b/pyload/plugin/hoster/UloziskoSk.py
new file mode 100644
index 000000000..4a37d5ba9
--- /dev/null
+++ b/pyload/plugin/hoster/UloziskoSk.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UloziskoSk(SimpleHoster):
+ __name = "UloziskoSk"
+ __type = "hoster"
+ __version = "0.25"
+
+ __pattern = r'http://(?:www\.)?ulozisko\.sk/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Ulozisko.sk hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
+ SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong><br />'
+ OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
+
+ LINK_FREE_PATTERN = r'<form name = "formular" action = "(.+?)" method = "post">'
+ ID_PATTERN = r'<input type = "hidden" name = "id" value = "(.+?)" />'
+ CAPTCHA_PATTERN = r'<img src="(/obrazky/obrazky\.php\?fid=.+?)" alt="" />'
+ IMG_PATTERN = ur'<strong>PRE ZVÄČŠENIE KLIKNITE NA OBRÁZOK</strong><br /><a href = "(.+?)">'
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ m = re.search(self.IMG_PATTERN, self.html)
+ if m:
+ self.link = "http://ulozisko.sk" + m.group(1)
+ else:
+ self.handleFree(pyfile)
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+ parsed_url = 'http://www.ulozisko.sk' + m.group(1)
+
+ m = re.search(self.ID_PATTERN, self.html)
+ if m is None:
+ self.error(_("ID_PATTERN not found"))
+ id = m.group(1)
+
+ self.logDebug("URL:" + parsed_url + ' ID:' + id)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("CAPTCHA_PATTERN not found"))
+ captcha_url = 'http://www.ulozisko.sk' + m.group(1)
+
+ captcha = self.decryptCaptcha(captcha_url, cookies=True)
+
+ self.logDebug("CAPTCHA_URL:" + captcha_url + ' CAPTCHA:' + captcha)
+
+ self.download(parsed_url,
+ post={"antispam": captcha,
+ "id" : id,
+ "name" : pyfile.name,
+ "but" : "++++STIAHNI+S%DABOR++++"})
diff --git a/pyload/plugin/hoster/UnibytesCom.py b/pyload/plugin/hoster/UnibytesCom.py
new file mode 100644
index 000000000..d8092ae08
--- /dev/null
+++ b/pyload/plugin/hoster/UnibytesCom.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import urlparse
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UnibytesCom(SimpleHoster):
+ __name = "UnibytesCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """UniBytes.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "unibytes.com"
+
+ INFO_PATTERN = r'<span[^>]*?id="fileName".*?>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
+
+ WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
+ LINK_FREE_PATTERN = r'<a href="(.+?)">Download</a>'
+
+
+ def handleFree(self, pyfile):
+ domain = "http://www.%s/" % self.HOSTER_DOMAIN
+ action, post_data = self.parseHtmlForm('id="startForm"')
+
+
+ for _i in xrange(8):
+ self.logDebug(action, post_data)
+ self.html = self.load(urlparse.urljoin(domain, action), post=post_data, follow_location=False)
+
+ m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
+ if m:
+ self.link = m.group(1)
+ break
+
+ if '>Somebody else is already downloading using your IP-address<' in self.html:
+ self.wait(10 * 60, True)
+ self.retry()
+
+ if post_data['step'] == 'last':
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+
+ last_step = post_data['step']
+ action, post_data = self.parseHtmlForm('id="stepForm"')
+
+ if last_step == 'timer':
+ m = re.search(self.WAIT_PATTERN, self.html)
+ self.wait(m.group(1) if m else 60, False)
+
+ elif last_step in ("captcha", "last"):
+ post_data['captcha'] = self.decryptCaptcha(urlparse.urljoin(domain, "/captcha.jpg"))
+
+ else:
+ self.fail(_("No valid captcha code entered"))
diff --git a/pyload/plugin/hoster/UnrestrictLi.py b/pyload/plugin/hoster/UnrestrictLi.py
new file mode 100644
index 000000000..4f719eae4
--- /dev/null
+++ b/pyload/plugin/hoster/UnrestrictLi.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+
+
+class UnrestrictLi(MultiHoster):
+ __name = "UnrestrictLi"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'https?://(?:www\.)?(unrestrict|unr)\.li/dl/[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Unrestrict.li multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LOGIN_ACCOUNT = False
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ for _i in xrange(5):
+ self.html = self.load('https://unrestrict.li/unrestrict.php',
+ post={'link': pyfile.url, 'domain': 'long'})
+
+ self.logDebug("JSON data: " + self.html)
+
+ if self.html:
+ break
+ else:
+ self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
+ self.retry(5, 60, "Unable to get API data")
+
+ if 'Expired session' in self.html \
+ or ("You are not allowed to download from this host" in self.html and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+
+ elif "File offline" in self.html:
+ self.offline()
+
+ elif "You are not allowed to download from this host" in self.html:
+ self.fail(_("You are not allowed to download from this host"))
+
+ elif "You have reached your daily limit for this host" in self.html:
+ self.logWarning(_("Reached daily limit for this host"))
+ self.retry(5, secondsToMidnight(gmt=2), "Daily limit for this host reached")
+
+ elif "ERROR_HOSTER_TEMPORARILY_UNAVAILABLE" in self.html:
+ self.logInfo(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
+ self.retry(5, 60, "Hoster is temporarily unavailable")
+
+ self.html = json_loads(self.html)
+ self.link = self.html.keys()[0]
+ self.api_data = self.html[self.link]
+
+ if hasattr(self, 'api_data'):
+ self.setNameSize()
+
+
+ def checkFile(self, rules={}):
+ super(UnrestrictLi, self).checkFile(rules)
+
+ if self.getConfig('history'):
+ self.load("https://unrestrict.li/history/", get={'delete': "all"})
+ self.logInfo(_("Download history deleted"))
+
+
+ def setNameSize(self):
+ if 'name' in self.api_data:
+ self.pyfile.name = self.api_data['name']
+ if 'size' in self.api_data:
+ self.pyfile.size = self.api_data['size']
diff --git a/pyload/plugin/hoster/UpleaCom.py b/pyload/plugin/hoster/UpleaCom.py
new file mode 100644
index 000000000..0459a82ea
--- /dev/null
+++ b/pyload/plugin/hoster/UpleaCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class UpleaCom(XFSHoster):
+ __name = "UpleaCom"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:www\.)?uplea\.com/dl/\w{15}'
+
+ __description = """Uplea.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Redleon", None),
+ ("GammaC0de", None)]
+
+
+ DISPOSITION = False #@TODO: Remove in 0.4.10
+
+ HOSTER_DOMAIN = "uplea.com"
+
+ SIZE_REPLACEMENTS = [('ko','KB'), ('mo','MB'), ('go','GB'), ('Ko','KB'), ('Mo','MB'), ('Go','GB')]
+
+ NAME_PATTERN = r'<span class="gold-text">(?P<N>.+?)</span>'
+ SIZE_PATTERN = r'<span class="label label-info agmd">(?P<S>[\d.,]+) (?P<U>[\w^_]+?)</span>'
+ OFFLINE_PATTERN = r'>You followed an invalid or expired link'
+
+ LINK_PATTERN = r'"(https?://\w+\.uplea\.com/anonym/.*?)"'
+
+ PREMIUM_ONLY_PATTERN = r'You need to have a Premium subscription to download this file'
+ WAIT_PATTERN = r'timeText: ?([\d.]+),'
+ STEP_PATTERN = r'<a href="(/step/.+)">'
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.STEP_PATTERN, self.html)
+ if m is None:
+ self.error(_("STEP_PATTERN not found"))
+
+ self.html = self.load(urlparse.urljoin("http://uplea.com/", m.group(1)))
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logDebug(_("Waiting %s seconds") % m.group(1))
+ self.wait(m.group(1), True)
+ self.retry()
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ self.link = m.group(1)
+ self.wait(15)
diff --git a/pyload/plugin/hoster/UploadStationCom.py b/pyload/plugin/hoster/UploadStationCom.py
new file mode 100644
index 000000000..ab55c71d4
--- /dev/null
+++ b/pyload/plugin/hoster/UploadStationCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadStationCom(DeadHoster):
+ __name = "UploadStationCom"
+ __type = "hoster"
+ __version = "0.52"
+
+ __pattern = r'http://(?:www\.)?uploadstation\.com/file/(?P<ID>\w+)'
+ __config = []
+
+ __description = """UploadStation.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadableCh.py b/pyload/plugin/hoster/UploadableCh.py
new file mode 100644
index 000000000..c030c4178
--- /dev/null
+++ b/pyload/plugin/hoster/UploadableCh.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadableCh(SimpleHoster):
+ __name = "UploadableCh"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uploadable.ch hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.uploadable.ch/file/\g<ID>')]
+
+ INFO_PATTERN = r'div id=\"file_name\" title=.*>(?P<N>.+)<span class=\"filename_normal\">\((?P<S>[\d.]+) (?P<U>\w+)\)</span><'
+
+ OFFLINE_PATTERN = r'>(File not available|This file is no longer available)'
+ TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
+
+ WAIT_PATTERN = r'>Please wait.+?<'
+
+ RECAPTCHA_KEY = "6LdlJuwSAAAAAPJbPIoUhyqOJd7-yrah5Nhim5S3"
+
+
+ def handleFree(self, pyfile):
+ # Click the "free user" button and wait
+ a = self.load(pyfile.url, post={'downloadLink': "wait"}, decode=True)
+ self.logDebug(a)
+
+ self.wait(30)
+
+ # Make the recaptcha appear and show it the pyload interface
+ b = self.load(pyfile.url, post={'checkDownload': "check"}, decode=True)
+ self.logDebug(b) #: Expected output: {"success":"showCaptcha"}
+
+ recaptcha = ReCaptcha(self)
+
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ # Submit the captcha solution
+ self.load("http://www.uploadable.ch/checkReCaptcha.php",
+ post={'recaptcha_challenge_field' : challenge,
+ 'recaptcha_response_field' : response,
+ 'recaptcha_shortencode_field': self.info['pattern']['ID']},
+ decode=True)
+
+ self.wait(3)
+
+ # Get ready for downloading
+ self.load(pyfile.url, post={'downloadLink': "show"}, decode=True)
+
+ self.wait(3)
+
+ # Download the file
+ self.download(pyfile.url, post={'download': "normal"}, disposition=True)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'wait': re.compile("Please wait for")}):
+ self.logInfo("Downloadlimit reached, please wait or reconnect")
+ self.wait(60 * 60, True)
+ self.retry()
+
+ return super(UploadableCh, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UploadboxCom.py b/pyload/plugin/hoster/UploadboxCom.py
new file mode 100644
index 000000000..eb769919e
--- /dev/null
+++ b/pyload/plugin/hoster/UploadboxCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadboxCom(DeadHoster):
+ __name = "Uploadbox"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?uploadbox\.com/files/.+'
+ __config = []
+
+ __description = """UploadBox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadedTo.py b/pyload/plugin/hoster/UploadedTo.py
new file mode 100644
index 000000000..308b7c1ca
--- /dev/null
+++ b/pyload/plugin/hoster/UploadedTo.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadedTo(SimpleHoster):
+ __name = "UploadedTo"
+ __type = "hoster"
+ __version = "0.87"
+
+ __pattern = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uploaded.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ DISPOSITION = False
+
+ API_KEY = "lhF2IeeprweDfu9ccWlxXVVypA5nA3EL"
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://uploaded.net/file/\g<ID>')]
+
+ TEMP_OFFLINE_PATTERN = r'<title>uploaded\.net - Maintenance'
+
+ LINK_PREMIUM_PATTERN = r'<div class="tfree".*\s*<form method="post" action="(.+?)"'
+
+ WAIT_PATTERN = r'Current waiting period: <span>(\d+)'
+ DL_LIMIT_ERROR = r'You have reached the max. number of possible free downloads for this hour'
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ info = super(UploadedTo, cls).apiInfo(url)
+
+ for _i in xrange(5):
+ html = getURL("http://uploaded.net/api/filemultiple",
+ get={"apikey": cls.API_KEY, 'id_0': re.match(cls.__pattern, url).group('ID')},
+ decode=True)
+
+ if html != "can't find request":
+ api = html.split(",", 4)
+ if api[0] == "online":
+ info.update({'name': api[4].strip(), 'size': api[2], 'status': 2})
+ else:
+ info['status'] = 1
+ break
+ else:
+ time.sleep(3)
+
+ return info
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.chunkLimit = 1 #: critical problems with more chunks
+
+
+ def checkErrors(self):
+ if 'var free_enabled = false;' in self.html:
+ self.logError(_("Free-download capacities exhausted"))
+ self.retry(24, 5 * 60)
+
+ elif "limit-size" in self.html:
+ self.fail(_("File too big for free download"))
+
+ elif "limit-slot" in self.html: #: Temporary restriction so just wait a bit
+ self.wait(30 * 60, True)
+ self.retry()
+
+ elif "limit-parallel" in self.html:
+ self.fail(_("Cannot download in parallel"))
+
+ elif "limit-dl" in self.html or self.DL_LIMIT_ERROR in self.html: #: limit-dl
+ self.wait(3 * 60 * 60, True)
+ self.retry()
+
+ elif '"err":"captcha"' in self.html:
+ self.invalidCaptcha()
+
+ else:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+
+ def handleFree(self, pyfile):
+ self.load("http://uploaded.net/language/en", just_header=True)
+
+ self.html = self.load("http://uploaded.net/js/download.js", decode=True)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ self.html = self.load("http://uploaded.net/io/ticket/captcha/%s" % self.info['pattern']['ID'],
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ if "type:'download'" in self.html:
+ self.correctCaptcha()
+ try:
+ self.link = re.search("url:'(.+?)'", self.html).group(1)
+
+ except Exception:
+ pass
+
+ self.checkErrors()
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'limit-dl': self.DL_LIMIT_ERROR}):
+ self.wait(3 * 60 * 60, True)
+ self.retry()
+
+ return super(UploadedTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UploadhereCom.py b/pyload/plugin/hoster/UploadhereCom.py
new file mode 100644
index 000000000..789f5e9f7
--- /dev/null
+++ b/pyload/plugin/hoster/UploadhereCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadhereCom(DeadHoster):
+ __name = "UploadhereCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?uploadhere\.com/\w{10}'
+ __config = []
+
+ __description = """Uploadhere.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadheroCom.py b/pyload/plugin/hoster/UploadheroCom.py
new file mode 100644
index 000000000..4a01d5db0
--- /dev/null
+++ b/pyload/plugin/hoster/UploadheroCom.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://uploadhero.co/dl/wQBRAVSM
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadheroCom(SimpleHoster):
+ __name = "UploadheroCom"
+ __type = "hoster"
+ __version = "0.18"
+
+ __pattern = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """UploadHero.co plugin"""
+ __license = "GPLv3"
+ __authors = [("mcmyst", "mcmyst@hotmail.fr"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Filesize: </span><strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<p class="titre_dl_2">'
+
+ COOKIES = [("uploadhero.co", "lang", "en")]
+
+ IP_BLOCKED_PATTERN = r'href="(/lightbox_block_download\.php\?min=.+?)"'
+ IP_WAIT_PATTERN = r'<span id="minutes">(\d+)</span>.*\s*<span id="seconds">(\d+)</span>'
+
+ CAPTCHA_PATTERN = r'"(/captchadl\.php\?\w+)"'
+
+ LINK_FREE_PATTERN = r'var magicomfg = \'<a href="(.+?)"|"(http://storage\d+\.uploadhero\.co.+?)"'
+ LINK_PREMIUM_PATTERN = r'<a href="(.+?)" id="downloadnow"'
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha not found"))
+
+ captcha = self.decryptCaptcha(urlparse.urljoin("http://uploadhero.co", m.group(1)))
+
+ self.html = self.load(pyfile.url,
+ get={"code": captcha})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1) or m.group(2)
+ self.wait(50)
+
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.html = self.load(urlparse.urljoin("http://uploadhero.co", m.group(1)))
+
+ m = re.search(self.IP_WAIT_PATTERN, self.html)
+ wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 5 * 60
+ self.wait(wait_time, True)
+ self.retry()
+
+ return super(UploadheroCom, self).checkErrors()
diff --git a/pyload/plugin/hoster/UploadingCom.py b/pyload/plugin/hoster/UploadingCom.py
new file mode 100644
index 000000000..e6c4cb7de
--- /dev/null
+++ b/pyload/plugin/hoster/UploadingCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp
+
+
+class UploadingCom(SimpleHoster):
+ __name = "UploadingCom"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>\w+)'
+
+ __description = """Uploading.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
+ SIZE_PATTERN = r'size tip_container">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
+ OFFLINE_PATTERN = r'(Page|file) not found'
+
+ COOKIES = [("uploading.com", "lang", "1"),
+ (".uploading.com", "language", "1"),
+ (".uploading.com", "setlang", "en"),
+ (".uploading.com", "_lang", "en")]
+
+
+ def process(self, pyfile):
+ if not "/get/" in pyfile.url:
+ pyfile.url = pyfile.url.replace("/files", "/files/get")
+
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium(pyfile)
+ else:
+ self.handleFree(pyfile)
+
+
+ def handlePremium(self, pyfile):
+ postData = {'action': 'get_link',
+ 'code' : self.info['pattern']['ID'],
+ 'pass' : 'undefined'}
+
+ self.html = self.load('http://uploading.com/files/get/?JsHttpRequest=%d-xml' % timestamp(), post=postData)
+ url = re.search(r'"link"\s*:\s*"(.*?)"', self.html)
+ if url:
+ self.link = url.group(1).replace("\\/", "/")
+
+ raise Exception("Plugin defect")
+
+
+ def handleFree(self, pyfile):
+ m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
+ if m:
+ pyfile.error = m.group(1)
+ self.logWarning(pyfile.error)
+ self.retry(6, (6 * 60 if m.group(2) else 15) * 60, pyfile.error)
+
+ ajax_url = "http://uploading.com/files/get/?ajax"
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = pyfile.url
+
+ res = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.info['pattern']['ID']}))
+
+ if 'answer' in res and 'wait_time' in res['answer']:
+ wait_time = int(res['answer']['wait_time'])
+ self.logInfo(_("Waiting %d seconds") % wait_time)
+ self.wait(wait_time)
+ else:
+ self.error(_("No AJAX/WAIT"))
+
+ res = json_loads(self.load(ajax_url, post={'action': 'get_link', 'code': self.info['pattern']['ID'], 'pass': 'false'}))
+
+ if 'answer' in res and 'link' in res['answer']:
+ url = res['answer']['link']
+ else:
+ self.error(_("No AJAX/URL"))
+
+ self.html = self.load(url)
+ m = re.search(r'<form id="file_form" action="(.*?)"', self.html)
+ if m:
+ url = m.group(1)
+ else:
+ self.error(_("No URL"))
+
+ self.link = url
diff --git a/pyload/plugin/hoster/UploadkingCom.py b/pyload/plugin/hoster/UploadkingCom.py
new file mode 100644
index 000000000..31d8874d8
--- /dev/null
+++ b/pyload/plugin/hoster/UploadkingCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadkingCom(DeadHoster):
+ __name = "UploadkingCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?uploadking\.com/\w{10}'
+ __config = []
+
+ __description = """UploadKing.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UpstoreNet.py b/pyload/plugin/hoster/UpstoreNet.py
new file mode 100644
index 000000000..adf63e382
--- /dev/null
+++ b/pyload/plugin/hoster/UpstoreNet.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UpstoreNet(SimpleHoster):
+ __name = "UpstoreNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?upstore\.net/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Upstore.Net File Download Hoster"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ INFO_PATTERN = r'<div class="comment">.*?</div>\s*\n<h2 style="margin:0">(?P<N>.*?)</h2>\s*\n<div class="comment">\s*\n\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<span class="error">File not found</span>'
+
+ WAIT_PATTERN = r'var sec = (\d+)'
+ CHASH_PATTERN = r'<input type="hidden" name="hash" value="(.+?)">'
+ LINK_FREE_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
+
+
+ def handleFree(self, pyfile):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.error(_("CHASH_PATTERN not found"))
+ chash = m.group(1)
+ self.logDebug("Read hash " + chash)
+ # continue to stage2
+ post_data = {'hash': chash, 'free': 'Slow download'}
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ # STAGE 2: solv captcha and wait
+ # first get the infos we need: recaptcha key and wait time
+ recaptcha = ReCaptcha(self)
+
+ # try the captcha 5 times
+ for _i in xrange(5):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m is None:
+ self.error(_("Wait pattern not found"))
+ wait_time = int(m.group(1))
+
+ # then, do the waiting
+ self.wait(wait_time)
+
+ # then, handle the captcha
+ response, challenge = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ # STAGE 3: get direct link
+ m = re.search(self.LINK_FREE_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ if m is None:
+ self.error(_("Download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/UptoboxCom.py b/pyload/plugin/hoster/UptoboxCom.py
new file mode 100644
index 000000000..a2d1e8fcc
--- /dev/null
+++ b/pyload/plugin/hoster/UptoboxCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class UptoboxCom(XFSHoster):
+ __name = "UptoboxCom"
+ __type = "hoster"
+ __version = "0.18"
+
+ __pattern = r'https?://(?:www\.)?(uptobox|uptostream)\.com/\w{12}'
+
+ __description = """Uptobox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'"para_title">(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)'
+ OFFLINE_PATTERN = r'>(File not found|Access Denied|404 Not Found)'
+ TEMP_OFFLINE_PATTERN = r'>Service Unavailable'
+
+ LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
+
+ ERROR_PATTERN = r'>(You have to wait.+till next download.)<' #@TODO: Check XFSHoster ERROR_PATTERN
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = True
diff --git a/pyload/plugin/hoster/VeehdCom.py b/pyload/plugin/hoster/VeehdCom.py
new file mode 100644
index 000000000..6e28164de
--- /dev/null
+++ b/pyload/plugin/hoster/VeehdCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class VeehdCom(Hoster):
+ __name = "VeehdCom"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://veehd\.com/video/\d+_\S+'
+ __config = [("filename_spaces", "bool", "Allow spaces in filename", False),
+ ("replacement_char", "str", "Filename replacement character", "_")]
+
+ __description = """Veehd.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("cat", "cat@pyload")]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.req.canContinue = True
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.logDebug("Requesting page: %s" % url)
+ self.html = self.load(url)
+
+
+ def file_exists(self):
+ if not self.html:
+ self.download_html()
+
+ if '<title>Veehd</title>' in self.html:
+ return False
+ return True
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<title.*?>([^<]+) on Veehd</title>', self.html)
+ if m is None:
+ self.error(_("Video title not found"))
+
+ name = m.group(1)
+
+ # replace unwanted characters in filename
+ if self.getConfig('filename_spaces'):
+ pattern = '[^\w ]+'
+ else:
+ pattern = '[^\w.]+'
+
+ return re.sub(pattern, self.getConfig('replacement_char'), name) + '.avi'
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<embed type="video/divx" src="(http://([^/]*\.)?veehd\.com/dl/.+?)"',
+ self.html)
+ if m is None:
+ self.error(_("Embedded video url not found"))
+
+ return m.group(1)
diff --git a/pyload/plugin/hoster/VeohCom.py b/pyload/plugin/hoster/VeohCom.py
new file mode 100644
index 000000000..e1ee00ea1
--- /dev/null
+++ b/pyload/plugin/hoster/VeohCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class VeohCom(SimpleHoster):
+ __name = "VeohCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
+ __config = [("use_premium", "bool" , "Use premium account if available", True ),
+ ("quality" , "Low;High;Auto", "Quality" , "Auto")]
+
+ __description = """Veoh.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
+ OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.veoh.com/watch/\g<ID>')]
+
+ COOKIES = [("veoh.com", "lassieLocale", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handleFree(self, pyfile):
+ quality = self.getConfig('quality')
+ if quality == "Auto":
+ quality = ("High", "Low")
+
+ for q in quality:
+ pattern = r'"fullPreviewHash%sPath":"(.+?)"' % q
+ m = re.search(pattern, self.html)
+ if m:
+ pyfile.name += ".mp4"
+ self.link = m.group(1).replace("\\", "")
+ return
+ else:
+ self.logInfo(_("No %s quality video found") % q.upper())
+ else:
+ self.fail(_("No video found!"))
diff --git a/pyload/plugin/hoster/VidPlayNet.py b/pyload/plugin/hoster/VidPlayNet.py
new file mode 100644
index 000000000..5d98d2fb3
--- /dev/null
+++ b/pyload/plugin/hoster/VidPlayNet.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://vidplay.net/38lkev0h3jv0
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class VidPlayNet(XFSHoster):
+ __name = "VidPlayNet"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?vidplay\.net/\w{12}'
+
+ __description = """VidPlay.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+ NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
diff --git a/pyload/plugin/hoster/VimeoCom.py b/pyload/plugin/hoster/VimeoCom.py
new file mode 100644
index 000000000..f83e15815
--- /dev/null
+++ b/pyload/plugin/hoster/VimeoCom.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class VimeoCom(SimpleHoster):
+ __name = "VimeoCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
+ __config = [("use_premium", "bool" , "Use premium account if available" , True ),
+ ("quality" , "Lowest;Mobile;SD;HD;Highest", "Quality" , "Highest"),
+ ("original" , "bool" , "Try to download the original file", True )]
+
+ __description = """Vimeo.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<title>(?P<N>.+) on Vimeo<'
+ OFFLINE_PATTERN = r'class="exception_header"'
+ TEMP_OFFLINE_PATTERN = r'Please try again in a few minutes.<'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.vimeo.com/\g<ID>')]
+
+ COOKIES = [("vimeo.com", "language", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handleFree(self, pyfile):
+ password = self.getPassword()
+
+ if self.js and 'class="btn iconify_down_b"' in self.html:
+ html = self.js.eval(self.load(pyfile.url, get={'action': "download", 'password': password}, decode=True))
+ pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
+ else:
+ html = self.load("https://player.vimeo.com/video/" + self.info['pattern']['ID'], get={'password': password})
+ pattern = r'"(?P<QL>\w+)":{"profile".*?"(?P<URL>http://pdl\.vimeocdn\.com.+?)"'
+
+ link = dict((l.group('QL').lower(), l.group('URL')) for l in re.finditer(pattern, html))
+
+ if self.getConfig('original'):
+ if "original" in link:
+ self.link = link[q]
+ return
+ else:
+ self.logInfo(_("Original file not downloadable"))
+
+ quality = self.getConfig('quality')
+ if quality == "Highest":
+ qlevel = ("hd", "sd", "mobile")
+ elif quality == "Lowest":
+ qlevel = ("mobile", "sd", "hd")
+ else:
+ qlevel = quality.lower()
+
+ for q in qlevel:
+ if q in link:
+ self.link = link[q]
+ return
+ else:
+ self.logInfo(_("No %s quality video found") % q.upper())
+ else:
+ self.fail(_("No video found!"))
diff --git a/pyload/plugin/hoster/Vipleech4UCom.py b/pyload/plugin/hoster/Vipleech4UCom.py
new file mode 100644
index 000000000..cd68410f0
--- /dev/null
+++ b/pyload/plugin/hoster/Vipleech4UCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class Vipleech4UCom(DeadHoster):
+ __name = "Vipleech4UCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
+ __config = []
+
+ __description = """Vipleech4u.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
diff --git a/pyload/plugin/hoster/WarserverCz.py b/pyload/plugin/hoster/WarserverCz.py
new file mode 100644
index 000000000..2556a1393
--- /dev/null
+++ b/pyload/plugin/hoster/WarserverCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class WarserverCz(DeadHoster):
+ __name = "WarserverCz"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?warserver\.cz/stahnout/\d+'
+ __config = []
+
+ __description = """Warserver.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/WebshareCz.py b/pyload/plugin/hoster/WebshareCz.py
new file mode 100644
index 000000000..49a8da89f
--- /dev/null
+++ b/pyload/plugin/hoster/WebshareCz.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class WebshareCz(SimpleHoster):
+ __name = "WebshareCz"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """WebShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("rush", "radek.senfeld@gmail.com")]
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = super(WebshareCz, cls).getInfo(url, html)
+
+ if url:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict()
+
+ api_data = getURL("https://webshare.cz/api/file_info/",
+ post={'ident': info['pattern']['ID']},
+ decode=True)
+
+ if 'File not found' in api_data:
+ info['status'] = 1
+ else:
+ info['status'] = 2
+ info['name'] = re.search('<name>(.+)</name>', api_data).group(1) or info['name']
+ info['size'] = re.search('<size>(.+)</size>', api_data).group(1) or info['size']
+
+ return info
+
+
+ def handleFree(self, pyfile):
+ wst = self.account.infos['wst'] if self.account and 'wst' in self.account.infos else ""
+
+ api_data = getURL('https://webshare.cz/api/file_link/',
+ post={'ident': self.info['pattern']['ID'], 'wst': wst},
+ decode=True)
+
+ self.logDebug("API data: " + api_data)
+
+ m = re.search('<link>(.+)</link>', api_data)
+ if m is None:
+ self.error(_("Unable to detect direct link"))
+
+ self.link = m.group(1)
+
+
+ def handlePremium(self, pyfile):
+ return self.handleFree(pyfile)
diff --git a/pyload/plugin/hoster/WrzucTo.py b/pyload/plugin/hoster/WrzucTo.py
new file mode 100644
index 000000000..34306b75f
--- /dev/null
+++ b/pyload/plugin/hoster/WrzucTo.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class WrzucTo(SimpleHoster):
+ __name = "WrzucTo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Wrzuc.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
+ SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
+
+ COOKIES = [("wrzuc.to", "language", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+
+
+ def handleFree(self, pyfile):
+ data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
+ if len(data) != 2:
+ self.error(_("No file ID"))
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = pyfile.url
+ self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
+
+ self.req.http.lastURL = pyfile.url
+ self.html = self.load("http://www.wrzuc.to/ajax/server/download_link", post={"file": data['file']})
+
+ data.update(re.findall(r'"(download_link|server_id)":"(.*?)"', self.html))
+ if len(data) != 4:
+ self.error(_("No download URL"))
+
+ self.link = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
diff --git a/pyload/plugin/hoster/WuploadCom.py b/pyload/plugin/hoster/WuploadCom.py
new file mode 100644
index 000000000..2af68ced7
--- /dev/null
+++ b/pyload/plugin/hoster/WuploadCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class WuploadCom(DeadHoster):
+ __name = "WuploadCom"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?wupload\..+?/file/((\w+/)?\d+)(/.*)?'
+ __config = []
+
+ __description = """Wupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("Paul King", "")]
diff --git a/pyload/plugin/hoster/X7To.py b/pyload/plugin/hoster/X7To.py
new file mode 100644
index 000000000..865fc4b45
--- /dev/null
+++ b/pyload/plugin/hoster/X7To.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class X7To(DeadHoster):
+ __name = "X7To"
+ __type = "hoster"
+ __version = "0.41"
+
+ __pattern = r'http://(?:www\.)?x7\.to/'
+ __config = []
+
+ __description = """X7.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("ernieb", "ernieb")]
diff --git a/pyload/plugin/hoster/XFileSharingPro.py b/pyload/plugin/hoster/XFileSharingPro.py
new file mode 100644
index 000000000..140a69a72
--- /dev/null
+++ b/pyload/plugin/hoster/XFileSharingPro.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class XFileSharingPro(XFSHoster):
+ __name = "XFileSharingPro"
+ __type = "hoster"
+ __version = "0.45"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharingPro dummy hoster plugin for hook"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [("/embed-", "/")]
+
+
+ def _log(self, type, args):
+ msg = " | ".join(str(a).strip() for a in args if a)
+ logger = getattr(self.log, type)
+ logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
+
+
+ def init(self):
+ super(XFileSharingPro, self).init()
+
+ self.__pattern = self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern']
+
+ self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower()
+ self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
+
+ account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
+
+ if account and account.canUse():
+ self.account = account
+
+ elif self.account:
+ self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
+
+ else:
+ return
+
+ self.user, data = self.account.selectAccount()
+ self.req = self.account.getAccountRequest(self.user)
+ self.premium = self.account.isPremium(self.user)
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
+ self.multiDL = True
diff --git a/pyload/plugin/hoster/XHamsterCom.py b/pyload/plugin/hoster/XHamsterCom.py
new file mode 100644
index 000000000..4be06833b
--- /dev/null
+++ b/pyload/plugin/hoster/XHamsterCom.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.Hoster import Hoster
+
+
+def clean_json(json_expr):
+ json_expr = re.sub('[\n\r]', '', json_expr)
+ json_expr = re.sub(' +', '', json_expr)
+ json_expr = re.sub('\'', '"', json_expr)
+
+ return json_expr
+
+
+class XHamsterCom(Hoster):
+ __name = "XHamsterCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?xhamster\.com/movies/.+'
+ __config = [("type", ".mp4;.flv", "Preferred type", ".mp4")]
+
+ __description = """XHamster.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ if not self.file_exists():
+ self.offline()
+
+ if self.getConfig('type'):
+ self.desired_fmt = self.getConfig('type')
+
+ pyfile.name = self.get_file_name() + self.desired_fmt
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ flashvar_pattern = re.compile('flashvars = ({.*?});', re.S)
+ json_flashvar = flashvar_pattern.search(self.html)
+
+ if not json_flashvar:
+ self.error(_("flashvar not found"))
+
+ j = clean_json(json_flashvar.group(1))
+ flashvars = json_loads(j)
+
+ if flashvars['srv']:
+ srv_url = flashvars['srv'] + '/'
+ else:
+ self.error(_("srv_url not found"))
+
+ if flashvars['url_mode']:
+ url_mode = flashvars['url_mode']
+
+
+ else:
+ self.error(_("url_mode not found"))
+
+ if self.desired_fmt == ".mp4":
+ file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
+ if file_url is None:
+ self.error(_("file_url not found"))
+ file_url = file_url.group(1)
+ long_url = srv_url + file_url
+ self.logDebug("long_url = " + long_url)
+ else:
+ if flashvars['file']:
+ file_url = urllib.unquote(flashvars['file'])
+ else:
+ self.error(_("file_url not found"))
+
+ if url_mode == '3':
+ long_url = file_url
+ self.logDebug("long_url = " + long_url)
+ else:
+ long_url = srv_url + "key=" + file_url
+ self.logDebug("long_url = " + long_url)
+
+ return long_url
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ pattern = r'<title>(.*?) - xHamster\.com</title>'
+ name = re.search(pattern, self.html)
+ if name is None:
+ pattern = r'<h1 >(.*)</h1>'
+ name = re.search(pattern, self.html)
+ if name is None:
+ pattern = r'http://[www.]+xhamster\.com/movies/.*/(.*?)\.html?'
+ name = re.match(file_name_pattern, self.pyfile.url)
+ if name is None:
+ pattern = r'<div id="element_str_id" style="display:none;">(.*)</div>'
+ name = re.search(pattern, self.html)
+ if name is None:
+ return "Unknown"
+
+ return name.group(1)
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"(.*Video not found.*)", self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/XVideosCom.py b/pyload/plugin/hoster/XVideosCom.py
new file mode 100644
index 000000000..ae4d0637f
--- /dev/null
+++ b/pyload/plugin/hoster/XVideosCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugin.Hoster import Hoster
+
+
+class XVideosCom(Hoster):
+ __name = "XVideos.com"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?xvideos\.com/video(\d+)'
+
+ __description = """XVideos.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ def process(self, pyfile):
+ site = self.load(pyfile.url)
+ pyfile.name = "%s (%s).flv" % (
+ re.search(r"<h2>([^<]+)<span", site).group(1),
+ re.match(self.__pattern, pyfile.url).group(1),
+ )
+ self.download(unquote(re.search(r"flv_url=([^&]+)&", site).group(1)))
diff --git a/pyload/plugin/hoster/XdadevelopersCom.py b/pyload/plugin/hoster/XdadevelopersCom.py
new file mode 100644
index 000000000..0fe9197aa
--- /dev/null
+++ b/pyload/plugin/hoster/XdadevelopersCom.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# http://forum.xda-developers.com/devdb/project/dl/?id=10885
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class XdadevelopersCom(SimpleHoster):
+ __name = "XdadevelopersCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?forum\.xda-developers\.com/devdb/project/dl/\?id=\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Xda-developers.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<label>Filename:</label>\s*<div>\s*(?P<N>.*?)\n'
+ SIZE_PATTERN = r'<label>Size:</label>\s*<div>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'</i> Device Filter</h3>'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ self.download(pyfile.url,
+ get={'task': "get"})
diff --git a/pyload/plugin/hoster/Xdcc.py b/pyload/plugin/hoster/Xdcc.py
new file mode 100644
index 000000000..d7593a936
--- /dev/null
+++ b/pyload/plugin/hoster/Xdcc.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+
+import re
+import socket
+import struct
+import sys
+import time
+
+from select import select
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import fs_join
+
+
+class Xdcc(Hoster):
+ __name = "Xdcc"
+ __type = "hoster"
+ __version = "0.32"
+
+ __config = [("nick", "str", "Nickname", "pyload"),
+ ("ident", "str", "Ident", "pyloadident"),
+ ("realname", "str", "Realname", "pyloadreal")]
+
+ __description = """Download from IRC XDCC bot"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.com")]
+
+
+ def setup(self):
+ self.debug = 0 #: 0,1,2
+ self.timeout = 30
+ self.multiDL = False
+
+
+ def process(self, pyfile):
+ # change request type
+ self.req = pyfile.m.core.requestFactory.getRequest(self.getClassName(), type="XDCC")
+
+ self.pyfile = pyfile
+ for _i in xrange(0, 3):
+ try:
+ nmn = self.doDownload(pyfile.url)
+ self.logDebug("Download of %s finished." % nmn)
+ return
+ except socket.error, e:
+ if hasattr(e, "errno"):
+ errno = e.errno
+ else:
+ errno = e.args[0]
+
+ if errno == 10054:
+ self.logDebug("Server blocked our ip, retry in 5 min")
+ self.setWait(300)
+ self.wait()
+ continue
+
+ self.fail(_("Failed due to socket errors. Code: %d") % errno)
+
+ self.fail(_("Server blocked our ip, retry again later manually"))
+
+
+ def doDownload(self, url):
+ self.pyfile.setStatus("waiting") #: real link
+
+ m = re.match(r'xdcc://(.*?)/#?(.*?)/(.*?)/#?(\d+)/?', url)
+ server = m.group(1)
+ chan = m.group(2)
+ bot = m.group(3)
+ pack = m.group(4)
+ nick = self.getConfig('nick')
+ ident = self.getConfig('ident')
+ real = self.getConfig('realname')
+
+ temp = server.split(':')
+ ln = len(temp)
+ if ln == 2:
+ host, port = temp
+ elif ln == 1:
+ host, port = temp[0], 6667
+ else:
+ self.fail(_("Invalid hostname for IRC Server: %s") % server)
+
+ #######################
+ # CONNECT TO IRC AND IDLE FOR REAL LINK
+ dl_time = time.time()
+
+ sock = socket.socket()
+ sock.connect((host, int(port)))
+ if nick == "pyload":
+ nick = "pyload-%d" % (time.time() % 1000) #: last 3 digits
+ sock.send("NICK %s\r\n" % nick)
+ sock.send("USER %s %s bla :%s\r\n" % (ident, host, real))
+
+ self.setWait(3)
+ self.wait()
+
+ sock.send("JOIN #%s\r\n" % chan)
+ sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
+
+ # IRC recv loop
+ readbuffer = ""
+ done = False
+ retry = None
+ m = None
+ while True:
+
+ # done is set if we got our real link
+ if done:
+ break
+
+ if retry:
+ if time.time() > retry:
+ retry = None
+ dl_time = time.time()
+ sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
+
+ else:
+ if (dl_time + self.timeout) < time.time(): #@TODO: add in config
+ sock.send("QUIT :byebye\r\n")
+ sock.close()
+ self.fail(_("XDCC Bot did not answer"))
+
+ fdset = select([sock], [], [], 0)
+ if sock not in fdset[0]:
+ continue
+
+ readbuffer += sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ if self.debug is 2:
+ print "*> " + unicode(line, errors='ignore')
+ line = line.rstrip()
+ first = line.split()
+
+ if first[0] == "PING":
+ sock.send("PONG %s\r\n" % first[1])
+
+ if first[0] == "ERROR":
+ self.fail(_("IRC-Error: %s") % line)
+
+ msg = line.split(None, 3)
+ if len(msg) != 4:
+ continue
+
+ msg = {
+ "origin": msg[0][1:],
+ "action": msg[1],
+ "target": msg[2],
+ "text": msg[3][1:]
+ }
+
+ if nick == msg['target'][0:len(nick)] and "PRIVMSG" == msg['action']:
+ if msg['text'] == "\x01VERSION\x01":
+ self.logDebug("Sending CTCP VERSION")
+ sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
+ elif msg['text'] == "\x01TIME\x01":
+ self.logDebug("Sending CTCP TIME")
+ sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
+ elif msg['text'] == "\x01LAG\x01":
+ pass #: don't know how to answer
+
+ if not (bot == msg['origin'][0:len(bot)]
+ and nick == msg['target'][0:len(nick)]
+ and msg['action'] in ("PRIVMSG", "NOTICE")):
+ continue
+
+ if self.debug is 1:
+ print "%s: %s" % (msg['origin'], msg['text'])
+
+ if "You already requested that pack" in msg['text']:
+ retry = time.time() + 300
+
+ if "you must be on a known channel to request a pack" in msg['text']:
+ self.fail(_("Wrong channel"))
+
+ m = re.match('\x01DCC SEND (.*?) (\d+) (\d+)(?: (\d+))?\x01', msg['text'])
+ if m:
+ done = True
+
+ # get connection data
+ ip = socket.inet_ntoa(struct.pack('L', socket.ntohl(int(m.group(2)))))
+ port = int(m.group(3))
+ packname = m.group(1)
+
+ if len(m.groups()) > 3:
+ self.req.filesize = int(m.group(4))
+
+ self.pyfile.name = packname
+
+ download_folder = self.config.get("general", "download_folder")
+ filename = fs_join(download_folder, packname)
+
+ self.logInfo(_("Downloading %s from %s:%d") % (packname, ip, port))
+
+ self.pyfile.setStatus("downloading")
+ newname = self.req.download(ip, port, filename, sock, self.pyfile.setProgress)
+ if newname and newname != filename:
+ self.logInfo(_("%(name)s saved as %(newname)s") % {"name": self.pyfile.name, "newname": newname})
+ filename = newname
+
+ # kill IRC socket
+ # sock.send("QUIT :byebye\r\n")
+ sock.close()
+
+ self.lastDownload = filename
+ return self.lastDownload
diff --git a/pyload/plugin/hoster/YadiSk.py b/pyload/plugin/hoster/YadiSk.py
new file mode 100644
index 000000000..f68039e2e
--- /dev/null
+++ b/pyload/plugin/hoster/YadiSk.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import json_loads
+
+
+class YadiSk(SimpleHoster):
+ __name = "YadiSk"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://yadi\.sk/d/\w+'
+
+ __description = """Yadi.sk hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("GammaC0de", None)]
+
+
+ OFFLINE_PATTERN = r'Nothing found'
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = super(YadiSk, cls).getInfo(url, html)
+
+ if html:
+ if 'idclient' not in info:
+ info['idclient'] = ""
+ for _i in xrange(32):
+ info ['idclient'] += random.choice('0123456abcdef')
+
+ m = re.search(r'<script id="models-client" type="application/json">(.+?)</script>', html)
+ if m:
+ api_data = json_loads(m.group(1))
+ try:
+ for sect in api_data:
+ if 'model' in sect:
+ if sect['model'] == "config":
+ info['version'] = sect['data']['version']
+ info['sk'] = sect['data']['sk']
+
+ elif sect['model'] == "resource":
+ info['id'] = sect['data']['id']
+ info['size'] = sect['data']['meta']['size']
+ info['name'] = sect['data']['name']
+
+ except Exception, e:
+ info['status'] = 8
+ info['error'] = _("Unexpected server response: %s") % e.message
+
+ else:
+ info['status'] = 8
+ info['error'] = _("could not find required json data")
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handleFree(self, pyfile):
+ if any(True for _k in ['id', 'sk', 'version', 'idclient'] if _k not in self.info):
+ self.error(_("Missing JSON data"))
+
+
+ try:
+ self.html = self.load("https://yadi.sk/models/",
+ get={'_m': "do-get-resource-url"},
+ post={'idClient': self.info['idclient'],
+ 'version' : self.info['version'],
+ '_model.0': "do-get-resource-url",
+ 'sk' : self.info['sk'],
+ 'id.0' : self.info['id']})
+
+ self.link = json_loads(self.html)['models'][0]['data']['file']
+
+ except Exception:
+ pass
diff --git a/pyload/plugin/hoster/YibaishiwuCom.py b/pyload/plugin/hoster/YibaishiwuCom.py
new file mode 100644
index 000000000..6581f7534
--- /dev/null
+++ b/pyload/plugin/hoster/YibaishiwuCom.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class YibaishiwuCom(SimpleHoster):
+ __name = "YibaishiwuCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """115.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'file_name: \'(?P<N>.+?)\''
+ SIZE_PATTERN = r'file_size: \'(?P<S>.+?)\''
+ OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
+
+ LINK_FREE_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
+
+
+ def handleFree(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ url = m.group(1)
+
+ self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
+
+ res = json_loads(self.load("http://115.com" + url, decode=False))
+ if "urls" in res:
+ mirrors = res['urls']
+
+ elif "data" in res:
+ mirrors = res['data']
+
+ else:
+ mirrors = None
+
+ for mr in mirrors:
+ try:
+ self.link = mr['url'].replace("\\", "")
+ self.logDebug("Trying URL: " + self.link)
+ break
+ except Exception:
+ continue
+ else:
+ self.fail(_("No working link found"))
diff --git a/pyload/plugin/hoster/YoupornCom.py b/pyload/plugin/hoster/YoupornCom.py
new file mode 100644
index 000000000..980211864
--- /dev/null
+++ b/pyload/plugin/hoster/YoupornCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class YoupornCom(Hoster):
+ __name = "YoupornCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?youporn\.com/watch/.+'
+
+ __description = """Youporn.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("willnix", "willnix@pyload.org")]
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url, post={"user_choice": "Enter"}, cookies=False)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ return re.search(r'(http://download\.youporn\.com/download/\d+\?save=1)">', self.html).group(1)
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ file_name_pattern = r'<title>(.+) - '
+ return re.search(file_name_pattern, self.html).group(1).replace("&amp;", "&").replace("/", "") + '.flv'
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"(.*invalid video_id.*)", self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/YourfilesTo.py b/pyload/plugin/hoster/YourfilesTo.py
new file mode 100644
index 000000000..4ac49d357
--- /dev/null
+++ b/pyload/plugin/hoster/YourfilesTo.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import reimport urllib
+
+from pyload.plugin.Hoster import Hoster
+
+
+class YourfilesTo(Hoster):
+ __name = "YourfilesTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?yourfiles\.(to|biz)/\?d=\w+'
+
+ __description = """Youfiles.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("skydancer", "skydancer@hasnomail.de")]
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.prepare()
+ self.download(self.get_file_url())
+
+
+ def prepare(self):
+ if not self.file_exists():
+ self.offline()
+
+ self.pyfile.name = self.get_file_name()
+
+ wait_time = self.get_waiting_time()
+ self.setWait(wait_time)
+ self.wait()
+
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ # var zzipitime = 15;
+ m = re.search(r'var zzipitime = (\d+);', self.html)
+ if m:
+ sec = int(m.group(1))
+ else:
+ sec = 0
+
+ return sec
+
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ url = re.search(r"var bla = '(.*?)';", self.html)
+ if url:
+ url = url.group(1)
+ url = urllib.unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
+ return url
+ else:
+ self.error(_("Absolute filepath not found"))
+
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search("<title>(.*)</title>", self.html).group(1)
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r"HTTP Status 404", self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/YoutubeCom.py b/pyload/plugin/hoster/YoutubeCom.py
new file mode 100644
index 000000000..b6f91fdb0
--- /dev/null
+++ b/pyload/plugin/hoster/YoutubeCom.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocessimport urllib
+
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.internal.SimpleHoster import replace_patterns
+from pyload.utils import html_unescape
+
+
+def which(program):
+ """Works exactly like the unix command which
+ Courtesy of http://stackoverflow.com/a/377028/675646"""
+
+ isExe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK)
+
+ fpath, fname = os.path.split(program)
+
+ if fpath:
+ if isExe(program):
+ return program
+ else:
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if isExe(exe_file):
+ return exe_file
+
+
+class YoutubeCom(Hoster):
+ __name = "YoutubeCom"
+ __type = "hoster"
+ __version = "0.41"
+
+ __pattern = r'https?://(?:[^/]*\.)?(youtube\.com|youtu\.be)/watch\?(?:.*&)?v=.+'
+ __config = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting" , "hd" ),
+ ("fmt" , "int" , "FMT/ITAG Number (0 for auto)", 0 ),
+ (".mp4" , "bool" , "Allow .mp4" , True ),
+ (".flv" , "bool" , "Allow .flv" , True ),
+ (".webm" , "bool" , "Allow .webm" , False),
+ (".3gp" , "bool" , "Allow .3gp" , False),
+ ("3d" , "bool" , "Prefer 3D" , False)]
+
+ __description = """Youtube.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ URL_REPLACEMENTS = [(r'youtu\.be/', 'youtube.com/')]
+
+ # Invalid characters that must be removed from the file name
+ invalidChars = u'\u2605:?><"|\\'
+
+ # name, width, height, quality ranking, 3D
+ formats = {5 : (".flv" , 400 , 240 , 1 , False),
+ 6 : (".flv" , 640 , 400 , 4 , False),
+ 17 : (".3gp" , 176 , 144 , 0 , False),
+ 18 : (".mp4" , 480 , 360 , 2 , False),
+ 22 : (".mp4" , 1280, 720 , 8 , False),
+ 43 : (".webm", 640 , 360 , 3 , False),
+ 34 : (".flv" , 640 , 360 , 4 , False),
+ 35 : (".flv" , 854 , 480 , 6 , False),
+ 36 : (".3gp" , 400 , 240 , 1 , False),
+ 37 : (".mp4" , 1920, 1080, 9 , False),
+ 38 : (".mp4" , 4096, 3072, 10, False),
+ 44 : (".webm", 854 , 480 , 5 , False),
+ 45 : (".webm", 1280, 720 , 7 , False),
+ 46 : (".webm", 1920, 1080, 9 , False),
+ 82 : (".mp4" , 640 , 360 , 3 , True ),
+ 83 : (".mp4" , 400 , 240 , 1 , True ),
+ 84 : (".mp4" , 1280, 720 , 8 , True ),
+ 85 : (".mp4" , 1920, 1080, 9 , True ),
+ 100: (".webm", 640 , 360 , 3 , True ),
+ 101: (".webm", 640 , 360 , 4 , True ),
+ 102: (".webm", 1280, 720 , 8 , True )}
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
+ html = self.load(pyfile.url, decode=True)
+
+ if re.search(r'<div id="player-unavailable" class="\s*player-width player-height\s*">', html):
+ self.offline()
+
+ if "We have been receiving a large volume of requests from your network." in html:
+ self.tempOffline()
+
+ # get config
+ use3d = self.getConfig('3d')
+
+ if use3d:
+ quality = {"sd": 82, "hd": 84, "fullhd": 85, "240p": 83, "360p": 82,
+ "480p": 82, "720p": 84, "1080p": 85, "3072p": 85}
+ else:
+ quality = {"sd": 18, "hd": 22, "fullhd": 37, "240p": 5, "360p": 18,
+ "480p": 35, "720p": 22, "1080p": 37, "3072p": 38}
+
+ desired_fmt = self.getConfig('fmt')
+
+ if not desired_fmt:
+ desired_fmt = quality.get(self.getConfig('quality'), 18)
+
+ elif desired_fmt not in self.formats:
+ self.logWarning(_("FMT %d unknown, using default") % desired_fmt)
+ desired_fmt = 0
+
+ # parse available streams
+ streams = re.search(r'"url_encoded_fmt_stream_map":"(.+?)",', html).group(1)
+ streams = [x.split('\u0026') for x in streams.split(',')]
+ streams = [dict((y.split('=', 1)) for y in x) for x in streams]
+ streams = [(int(x['itag']), urllib.unquote(x['url'])) for x in streams]
+
+ # self.logDebug("Found links: %s" % streams)
+
+ self.logDebug("AVAILABLE STREAMS: %s" % [x[0] for x in streams])
+
+ # build dictionary of supported itags (3D/2D)
+ allowed = lambda x: self.getConfig(self.formats[x][0])
+ streams = [x for x in streams if x[0] in self.formats and allowed(x[0])]
+
+ if not streams:
+ self.fail(_("No available stream meets your preferences"))
+
+ fmt_dict = dict([x for x in streams if self.formats[x[0]][4] == use3d] or streams)
+
+ self.logDebug("DESIRED STREAM: ITAG:%d (%s) %sfound, %sallowed" %
+ (desired_fmt, "%s %dx%d Q:%d 3D:%s" % self.formats[desired_fmt],
+ "" if desired_fmt in fmt_dict else "NOT ", "" if allowed(desired_fmt) else "NOT "))
+
+ # return fmt nearest to quality index
+ if desired_fmt in fmt_dict and allowed(desired_fmt):
+ fmt = desired_fmt
+ else:
+ sel = lambda x: self.formats[x][3] #: select quality index
+ comp = lambda x, y: abs(sel(x) - sel(y))
+
+ self.logDebug("Choosing nearest fmt: %s" % [(x, allowed(x), comp(x, desired_fmt)) for x in fmt_dict.keys()])
+
+ fmt = reduce(lambda x, y: x if comp(x, desired_fmt) <= comp(y, desired_fmt) and
+ sel(x) > sel(y) else y, fmt_dict.keys())
+
+ self.logDebug("Chosen fmt: %s" % fmt)
+
+ url = fmt_dict[fmt]
+
+ self.logDebug("URL: %s" % url)
+
+ # set file name
+ file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv"
+ file_name_pattern = '<meta name="title" content="(.+?)">'
+ name = re.search(file_name_pattern, html).group(1).replace("/", "")
+
+ # Cleaning invalid characters from the file name
+ name = name.encode('ascii', 'replace')
+ for c in self.invalidChars:
+ name = name.replace(c, '_')
+
+ pyfile.name = html_unescape(name)
+
+ time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url)
+ ffmpeg = which("ffmpeg")
+ if ffmpeg and time:
+ m, s = time.groups()[1:]
+ if m is None:
+ m = "0"
+
+ pyfile.name += " (starting at %s:%s)" % (m, s)
+
+ pyfile.name += file_suffix
+ filename = self.download(url)
+
+ if ffmpeg and time:
+ inputfile = filename + "_"
+ os.rename(filename, inputfile)
+
+ subprocess.call([
+ ffmpeg,
+ "-ss", "00:%s:%s" % (m, s),
+ "-i", inputfile,
+ "-vcodec", "copy",
+ "-acodec", "copy",
+ filename])
+
+ os.remove(inputfile)
diff --git a/pyload/plugin/hoster/ZDF.py b/pyload/plugin/hoster/ZDF.py
new file mode 100644
index 000000000..c02eadc23
--- /dev/null
+++ b/pyload/plugin/hoster/ZDF.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from xml.etree.ElementTree import fromstring
+
+from pyload.plugin.Hoster import Hoster
+
+
+# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/)
+class ZDF(Hoster):
+ __name = "ZDF Mediathek"
+ __type = "hoster"
+ __version = "0.80"
+
+ __pattern = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*'
+
+ __description = """ZDF.de hoster 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 = fromstring(self.load(self.XML_API % self.get_id(pyfile.url)))
+
+ 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
+
+ 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)
diff --git a/pyload/plugin/hoster/ZShareNet.py b/pyload/plugin/hoster/ZShareNet.py
new file mode 100644
index 000000000..a2461fb6a
--- /dev/null
+++ b/pyload/plugin/hoster/ZShareNet.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ZShareNet(DeadHoster):
+ __name = "ZShareNet"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
+ __config = []
+
+ __description = """ZShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("espes", ""),
+ ("Cptn Sandwich", "")]
diff --git a/pyload/plugin/hoster/ZeveraCom.py b/pyload/plugin/hoster/ZeveraCom.py
new file mode 100644
index 000000000..c5defeff7
--- /dev/null
+++ b/pyload/plugin/hoster/ZeveraCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class ZeveraCom(MultiHoster):
+ __name = "ZeveraCom"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'https?://(?:www\.)zevera\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Zevera.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def handlePremium(self, pyfile):
+ self.link = "https://%s/getFiles.ashx?ourl=%s" % (self.account.HOSTER_DOMAIN, pyfile.url)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"error": 'action="ErrorDownload.aspx'}):
+ self.fail(_("Error response received"))
+
+ return super(ZeveraCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/ZippyshareCom.py b/pyload/plugin/hoster/ZippyshareCom.py
new file mode 100644
index 000000000..7f91c04e5
--- /dev/null
+++ b/pyload/plugin/hoster/ZippyshareCom.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class ZippyshareCom(SimpleHoster):
+ __name = "ZippyshareCom"
+ __type = "hoster"
+ __version = "0.78"
+
+ __pattern = r'http://www\d{0,2}\.zippyshare\.com/v(/|iew\.jsp.*key=)(?P<KEY>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Zippyshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("sebdelsol", "seb.morin@gmail.com")]
+
+
+ COOKIES = [("zippyshare.com", "ziplocale", "en")]
+
+ NAME_PATTERN = r'(<title>Zippyshare.com - |"/)(?P<N>[^/]+)(</title>|";)'
+ SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'does not exist (anymore )?on this server<'
+
+ LINK_PREMIUM_PATTERN = r"document.location = '(.+?)'"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def handleFree(self, pyfile):
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+
+ if captcha_key:
+ try:
+ self.link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ recaptcha.challenge()
+
+ except Exception, e:
+ self.error(e)
+
+ else:
+ self.link = self.get_link()
+
+ 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(self.html)
+ scripts = (s.getText().strip() for s in soup.body.findAll('script', type='text/javascript'))
+
+ # meant to be populated with the initialization of all the DOM elements found in the scripts
+ initScripts = set()
+
+
+ def replElementById(element):
+ id = element.group(1) #: id might be either 'x' (a real id) or x (a variable)
+ attr = element.group(4) #: attr might be None
+
+ varName = re.sub(r'-', '', 'GVAR[%s+"_%s"]' %(id, attr))
+
+ realid = id.strip('"\'')
+ if id != realid: #: id is not a variable, so look for realid.attr in the html
+ initValues = filter(None, [elt.get(attr, None) for elt in soup.findAll(id=realid)])
+ initValue = '"%s"' % initValues[-1] if initValues else 'null'
+ initScripts.add('%s = %s;' % (varName, initValue))
+
+ return varName
+
+ # handle all getElementById
+ reVar = r'document.getElementById\(([\'"\w-]+)\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?'
+ scripts = [re.sub(reVar, replElementById, script) for script in scripts if script]
+
+ # 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 = ['var GVAR = {}'] + list(initScripts) + scripts + ['GVAR['dlbutton_href']']
+ return self.js.eval('\n'.join(scripts))
diff --git a/pyload/plugin/hoster/__init__.py b/pyload/plugin/hoster/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/hoster/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/internal/BasePlugin.py b/pyload/plugin/internal/BasePlugin.py
new file mode 100644
index 000000000..6be9880fc
--- /dev/null
+++ b/pyload/plugin/internal/BasePlugin.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+import urlparse
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.internal.SimpleHoster import getFileURL
+from pyload.plugin.Hoster import Hoster
+
+
+class BasePlugin(Hoster):
+ __name = "BasePlugin"
+ __type = "hoster"
+ __version = "0.43"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Base plugin when any other didnt fit"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""): #@TODO: Move to hoster class in 0.4.10
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ return {'name' : (url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 'size' : 0,
+ 'status': 3 if url else 8,
+ 'url' : url}
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+ def process(self, pyfile):
+ """main function"""
+
+ pyfile.name = self.getInfo(pyfile.url)['name']
+
+ if not pyfile.url.startswith("http"):
+ self.fail(_("No plugin matched"))
+
+ for _i in xrange(5):
+ try:
+ link = getFileURL(self, urllib.unquote(pyfile.url))
+
+ if link:
+ self.download(link, ref=False, disposition=True)
+ else:
+ self.fail(_("File not found"))
+
+ except BadHeader, e:
+ if e.code is 404:
+ self.offline()
+
+ elif e.code in (401, 403):
+ self.logDebug("Auth required", "Received HTTP status code: %d" % e.code)
+
+ account = self.core.accountManager.getAccountPlugin('Http')
+ servers = [x['login'] for x in account.getAllAccounts()]
+ server = urlparse.urlparse(pyfile.url).netloc
+
+ if server in servers:
+ self.logDebug("Logging on to %s" % server)
+ self.req.addAuth(account.getAccountData(server)['password'])
+ else:
+ pwd = self.getPassword()
+ if ':' in pwd:
+ self.req.addAuth(pwd)
+ else:
+ self.fail(_("Authorization required"))
+ else:
+ self.fail(e)
+ else:
+ break
+ else:
+ self.fail(_("No file downloaded")) #@TODO: Move to hoster class in 0.4.10
+
+ errmsg = self.checkDownload({'Empty file' : re.compile(r'\A\s*\Z'),
+ 'Html error' : re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'),
+ 'Html file' : re.compile(r'\A\s*<!DOCTYPE html'),
+ 'Request error': re.compile(r'([Aa]n error occured while processing your request)')})
+ if not errmsg:
+ return
+
+ try:
+ errmsg += " | " + self.lastCheck.group(1).strip()
+ except Exception:
+ pass
+
+ self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
+ self.retry(3, 60, errmsg)
diff --git a/pyload/plugin/internal/DeadCrypter.py b/pyload/plugin/internal/DeadCrypter.py
new file mode 100644
index 000000000..daa7e1a0d
--- /dev/null
+++ b/pyload/plugin/internal/DeadCrypter.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter as _Crypter
+
+
+class DeadCrypter(_Crypter):
+ __name = "DeadCrypter"
+ __type = "crypter"
+ __version = "0.04"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Crypter is no longer available"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ api = super(DeadCrypter, self).apiInfo(url, get, post)
+ api['status'] = 1
+ return api
+
+
+ def setup(self):
+ self.pyfile.error = "Crypter is no longer available"
+ self.offline() #@TODO: self.offline("Crypter is no longer available")
diff --git a/pyload/plugin/internal/DeadHoster.py b/pyload/plugin/internal/DeadHoster.py
new file mode 100644
index 000000000..2e57decdb
--- /dev/null
+++ b/pyload/plugin/internal/DeadHoster.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Hoster import Hoster as _Hoster
+
+
+class DeadHoster(_Hoster):
+ __name = "DeadHoster"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Hoster is no longer available"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ api = super(DeadHoster, self).apiInfo(url, get, post)
+ api['status'] = 1
+ return api
+
+
+ def setup(self):
+ self.pyfile.error = "Hoster is no longer available"
+ self.offline() #@TODO: self.offline("Hoster is no longer available")
diff --git a/pyload/plugin/internal/MultiHook.py b/pyload/plugin/internal/MultiHook.py
new file mode 100644
index 000000000..dc97ffea9
--- /dev/null
+++ b/pyload/plugin/internal/MultiHook.py
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import traceback
+
+from pyload.plugin.Hook import Hook
+from pyload.utils import decode, remove_chars
+
+
+class MultiHook(Hook):
+ __name = "MultiHook"
+ __type = "hook"
+ __version = "0.44"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", ""),
+ ("reload" , "bool" , "Reload plugin list" , True),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12)]
+
+ __description = """Hook plugin for multi hoster/crypter"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team" , "admin@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ MIN_RELOAD_INTERVAL = 1 * 60 * 60 #: 1 hour
+
+ DOMAIN_REPLACEMENTS = [(r'180upload\.com' , "hundredeightyupload.com"),
+ (r'bayfiles\.net' , "bayfiles.com"),
+ (r'cloudnator\.com' , "shragle.com"),
+ (r'dfiles\.eu' , "depositfiles.com"),
+ (r'easy-share\.com' , "crocko.com"),
+ (r'freakshare\.net' , "freakshare.com"),
+ (r'hellshare\.com' , "hellshare.cz"),
+ (r'ifile\.it' , "filecloud.io"),
+ (r'nowdownload\.\w+', "nowdownload.sx"),
+ (r'nowvideo\.\w+' , "nowvideo.sx"),
+ (r'putlocker\.com' , "firedrive.com"),
+ (r'share-?rapid\.cz', "multishare.cz"),
+ (r'ul\.to' , "uploaded.to"),
+ (r'uploaded\.net' , "uploaded.to"),
+ (r'uploadhero\.co' , "uploadhero.com"),
+ (r'zshares\.net' , "zshare.net"),
+ (r'^1' , "one"),
+ (r'^2' , "two"),
+ (r'^3' , "three"),
+ (r'^4' , "four"),
+ (r'^5' , "five"),
+ (r'^6' , "six"),
+ (r'^7' , "seven"),
+ (r'^8' , "eight"),
+ (r'^9' , "nine"),
+ (r'^0' , "zero")]
+
+
+ def setup(self):
+ self.plugins = []
+ self.supported = []
+ self.new_supported = []
+
+ self.account = None
+ self.pluginclass = None
+ self.pluginmodule = None
+ self.pluginname = None
+ self.plugintype = None
+
+ self.initPlugin()
+
+
+ def initPlugin(self):
+ self.pluginname = self.getClassName()
+ plugin, self.plugintype = self.core.pluginManager.findPlugin(("hoster", "crypter", "container"), self.pluginname)
+
+ if plugin:
+ self.pluginmodule = self.core.pluginManager.loadModule(self.plugintype, self.pluginname)
+ self.pluginclass = getattr(self.pluginmodule, self.pluginname)
+ else:
+ self.logWarning("Hook plugin will be deactivated due missing plugin reference")
+ self.setConfig('activated', False)
+
+
+ def loadAccount(self):
+ self.account = self.core.accountManager.getAccountPlugin(self.pluginname)
+
+ if self.account and not self.account.canUse():
+ self.account = None
+
+ if not self.account and hasattr(self.pluginclass, "LOGIN_ACCOUNT") and self.pluginclass.LOGIN_ACCOUNT:
+ self.logWarning("Hook plugin will be deactivated due missing account reference")
+ self.setConfig('activated', False)
+
+
+ def activate(self):
+ self.initPeriodical(threaded=True)
+
+
+ def getURL(self, *args, **kwargs): #@TODO: Remove in 0.4.10
+ """ see HTTPRequest for argument list """
+ h = pyreq.getHTTPRequest(timeout=120)
+ try:
+ if not 'decode' in kwargs:
+ kwargs['decode'] = True
+ rep = h.load(*args, **kwargs)
+ finally:
+ h.close()
+
+ return rep
+
+
+ def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
+ """getConfig with default value - sublass may not implements all config options"""
+ try:
+ return self.getConf(option)
+
+ except KeyError:
+ return default
+
+
+ def pluginsCached(self):
+ if self.plugins:
+ return self.plugins
+
+ for _i in xrange(2):
+ try:
+ pluginset = self._pluginSet(self.getHosters() if self.plugintype == "hoster" else self.getCrypters())
+ break
+
+ except Exception, e:
+ self.logDebug(e, "Waiting 1 minute and retry")
+ time.sleep(60)
+ else:
+ self.logWarning(_("Fallback to default reload interval due plugin parse error"))
+ self.interval = self.MIN_RELOAD_INTERVAL
+ return list()
+
+ try:
+ configmode = self.getConfig('pluginmode', 'all')
+ if configmode in ("listed", "unlisted"):
+ pluginlist = self.getConfig('pluginlist', '').replace('|', ',').replace(';', ',').split(',')
+ configset = self._pluginSet(pluginlist)
+
+ if configmode == "listed":
+ pluginset &= configset
+ else:
+ pluginset -= configset
+
+ except Exception, e:
+ self.logError(e)
+
+ self.plugins = list(pluginset)
+
+ return self.plugins
+
+
+ def _pluginSet(self, plugins):
+ regexp = re.compile(r'^[\w\-.^_]{3,63}\.[a-zA-Z]{2,}$', re.U)
+ plugins = [decode(p.strip()).lower() for p in plugins if regexp.match(p.strip())]
+
+ for r in self.DOMAIN_REPLACEMENTS:
+ rf, rt = r
+ repr = re.compile(rf, re.I | re.U)
+ plugins = [re.sub(rf, rt, p) if repr.match(p) else p for p in plugins]
+
+ return set(plugins)
+
+
+ def getHosters(self):
+ """Load list of supported hoster
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+
+ def getCrypters(self):
+ """Load list of supported crypters
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+
+ def periodical(self):
+ """reload plugin list periodically"""
+ self.loadAccount()
+
+ if self.getConfig('reload', True):
+ self.interval = max(self.getConfig('reloadinterval', 12) * 60 * 60, self.MIN_RELOAD_INTERVAL)
+ else:
+ self.core.scheduler.removeJob(self.cb)
+ self.cb = None
+
+ self.logInfo(_("Reloading supported %s list") % self.plugintype)
+
+ old_supported = self.supported
+
+ self.supported = []
+ self.new_supported = []
+ self.plugins = []
+
+ self.overridePlugins()
+
+ old_supported = [plugin for plugin in old_supported if plugin not in self.supported]
+
+ if old_supported:
+ self.logDebug("Unload: %s" % ", ".join(old_supported))
+ for plugin in old_supported:
+ self.unloadPlugin(plugin)
+
+
+ def overridePlugins(self):
+ excludedList = []
+
+ if self.plugintype == "hoster":
+ pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.iterkeys())
+ accountList = [account.type.lower() for account in self.core.api.getAccounts(False) if account.valid and account.premium]
+ else:
+ pluginMap = {}
+ accountList = [name[::-1].replace("Folder"[::-1], "", 1).lower()[::-1] for name in self.core.pluginManager.crypterPlugins.iterkeys()]
+
+ for plugin in self.pluginsCached():
+ name = remove_chars(plugin, "-.")
+
+ if name in accountList:
+ excludedList.append(plugin)
+ else:
+ if name in pluginMap:
+ self.supported.append(pluginMap[name])
+ else:
+ self.new_supported.append(plugin)
+
+ if not self.supported and not self.new_supported:
+ self.logError(_("No %s loaded") % self.plugintype)
+ return
+
+ # inject plugin plugin
+ self.logDebug("Overwritten %ss: %s" % (self.plugintype, ", ".join(sorted(self.supported))))
+
+ for plugin in self.supported:
+ hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
+ hdict['new_module'] = self.pluginmodule
+ hdict['new_name'] = self.pluginname
+
+ if excludedList:
+ self.logInfo(_("%ss not overwritten: %s") % (self.plugintype.capitalize(), ", ".join(sorted(excludedList))))
+
+ if self.new_supported:
+ plugins = sorted(self.new_supported)
+
+ self.logDebug("New %ss: %s" % (self.plugintype, ", ".join(plugins)))
+
+ # create new regexp
+ regexp = r'.*(?P<DOMAIN>%s).*' % "|".join(x.replace('.', '\.') for x in plugins)
+ if hasattr(self.pluginclass, "__pattern") and isinstance(self.pluginclass.__pattern, basestring) and '://' in self.pluginclass.__pattern:
+ regexp = r'%s|%s' % (self.pluginclass.__pattern, regexp)
+
+ self.logDebug("Regexp: %s" % regexp)
+
+ hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
+ hdict['pattern'] = regexp
+ hdict['re'] = re.compile(regexp)
+
+
+ def unloadPlugin(self, plugin):
+ hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
+ if "module" in hdict:
+ hdict.pop('module', None)
+
+ if "new_module" in hdict:
+ hdict.pop('new_module', None)
+ hdict.pop('new_name', None)
+
+
+ def deactivate(self):
+ """Remove override for all plugins. Scheduler job is removed by hookmanager"""
+ for plugin in self.supported:
+ self.unloadPlugin(plugin)
+
+ # reset pattern
+ hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
+
+ hdict['pattern'] = getattr(self.pluginclass, "__pattern", r'^unmatchable$')
+ hdict['re'] = re.compile(hdict['pattern'])
diff --git a/pyload/plugin/internal/MultiHoster.py b/pyload/plugin/internal/MultiHoster.py
new file mode 100644
index 000000000..8dbcf4f30
--- /dev/null
+++ b/pyload/plugin/internal/MultiHoster.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Plugin import Fail, Retry
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies
+
+
+class MultiHoster(SimpleHoster):
+ __name = "MultiHoster"
+ __type = "hoster"
+ __version = "0.39"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("revertfailed", "bool", "Revert to standard download if fails", True)]
+
+ __description = """Multi hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LOGIN_ACCOUNT = True
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.multiDL = bool(self.account)
+ self.resumeDownload = self.premium
+
+
+ def prepare(self):
+ self.info = {}
+ self.html = ""
+ self.link = "" #@TODO: Move to hoster class in 0.4.10
+ self.directDL = False #@TODO: Move to hoster class in 0.4.10
+
+ if not self.getConfig('use_premium', True):
+ self.retryFree()
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ if self.DIRECT_LINK is None:
+ self.directDL = self.__pattern != r'^unmatchable$' and re.match(self.__pattern, self.pyfile.url)
+ else:
+ self.directDL = self.DIRECT_LINK
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def process(self, pyfile):
+ try:
+ self.prepare()
+
+ if self.directDL:
+ self.checkInfo()
+ self.logDebug("Looking for direct download link...")
+ self.handleDirect(pyfile)
+
+ if not self.link and not self.lastDownload:
+ self.preload()
+
+ self.checkErrors()
+ self.checkStatus(getinfo=False)
+
+ if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as premium download")
+ self.handlePremium(pyfile)
+
+ elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as free download")
+ self.handleFree(pyfile)
+
+ self.downloadLink(self.link, True)
+ self.checkFile()
+
+ except Fail, e: #@TODO: Move to PluginThread in 0.4.10
+ if self.premium:
+ self.logWarning(_("Premium download failed"))
+ self.retryFree()
+
+ elif self.getConfig('revertfailed', True) \
+ and "new_module" in self.core.pluginManager.hosterPlugins[self.getClassName()]:
+ hdict = self.core.pluginManager.hosterPlugins[self.getClassName()]
+
+ tmp_module = hdict['new_module']
+ tmp_name = hdict['new_name']
+ hdict.pop('new_module', None)
+ hdict.pop('new_name', None)
+
+ pyfile.initPlugin()
+
+ hdict['new_module'] = tmp_module
+ hdict['new_name'] = tmp_name
+
+ raise Retry(_("Revert to original hoster plugin"))
+
+ else:
+ raise Fail(e)
+
+
+ def handlePremium(self, pyfile):
+ return self.handleFree(pyfile)
+
+
+ def handleFree(self, pyfile):
+ if self.premium:
+ raise NotImplementedError
+ else:
+ self.fail(_("Required premium account not found"))
diff --git a/pyload/plugin/internal/SimpleCrypter.py b/pyload/plugin/internal/SimpleCrypter.py
new file mode 100644
index 000000000..e22a4df29
--- /dev/null
+++ b/pyload/plugin/internal/SimpleCrypter.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies
+from pyload.utils import fixup
+
+
+class SimpleCrypter(Crypter, SimpleHoster):
+ __name = "SimpleCrypter"
+ __type = "crypter"
+ __version = "0.43"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), #: Overrides core.config.get("general", "folder_per_package")
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Simple decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: Download link or regex to catch links in group(1)
+ example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ NAME_PATTERN: (optional) folder name or page title
+ example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>'
+
+ OFFLINE_PATTERN: (optional) Checks if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+
+ You can override the getLinks method if you need a more sophisticated way to extract the links.
+
+
+ If the links are splitted on multiple pages you can define the PAGES_PATTERN regex:
+
+ PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links
+ example: PAGES_PATTERN = r'Pages: (\d+)'
+
+ and its loadPage method:
+
+ def loadPage(self, page_n):
+ return the html of the page number page_n
+ """
+
+ LINK_PATTERN = None
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
+ URL_REPLACEMENTS = []
+
+ TEXT_ENCODING = False #: Set to True or encoding name if encoding in http header is not correct
+ COOKIES = True #: or False or list of tuples [(domain, name, value)]
+
+ LOGIN_ACCOUNT = False
+ LOGIN_PREMIUM = False
+
+
+ def prepare(self):
+ self.pyfile.error = "" #@TODO: Remove in 0.4.10
+
+ self.info = {}
+ self.html = ""
+ self.links = [] #@TODO: Move to hoster class in 0.4.10
+
+ if self.LOGIN_PREMIUM and not self.premium:
+ self.fail(_("Required premium account not found"))
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def decrypt(self, pyfile):
+ self.prepare()
+
+ self.preload()
+ self.checkInfo()
+
+ self.links = self.getLinks()
+
+ if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
+ self.handlePages(pyfile)
+
+ self.logDebug("Package has %d links" % len(self.links))
+
+ if self.links:
+ self.packages = [(self.info['name'], self.links, self.info['folder'])]
+
+ elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
+ self.fail(_("No link grabbed"))
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("File info (BEFORE): %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("File info (AFTER): %s" % self.info)
+
+ try:
+ url = self.info['url'].strip()
+ name = self.info['name'].strip()
+ if name and name != url:
+ self.pyfile.name = name
+
+ except Exception:
+ pass
+
+ try:
+ folder = self.info['folder'] = self.pyfile.name
+
+ except Exception:
+ pass
+
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File folder: %s" % self.pyfile.name)
+
+
+ def getLinks(self):
+ """
+ Returns the links extracted from self.html
+ You should override this only if it's impossible to extract links using only the LINK_PATTERN.
+ """
+ url_p = urlparse.urlparse(self.pyfile.url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+
+ return [urlparse.urljoin(baseurl, link) if not urlparse.urlparse(link).scheme else link \
+ for link in re.findall(self.LINK_PATTERN, self.html)]
+
+
+ def handlePages(self, pyfile):
+ try:
+ pages = int(re.search(self.PAGES_PATTERN, self.html).group(1))
+ except Exception:
+ pages = 1
+
+ for p in xrange(2, pages + 1):
+ self.html = self.loadPage(p)
+ self.links += self.getLinks()
diff --git a/pyload/plugin/internal/SimpleDereferer.py b/pyload/plugin/internal/SimpleDereferer.py
new file mode 100644
index 000000000..0ec947751
--- /dev/null
+++ b/pyload/plugin/internal/SimpleDereferer.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.internal.SimpleHoster import getFileURL, set_cookies
+
+
+class SimpleDereferer(Crypter):
+ __name = "SimpleDereferer"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Simple dereferer plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: Regex to catch the redirect url in group(1)
+ example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ OFFLINE_PATTERN: (optional) Checks if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+
+ You can override the getLinks method if you need a more sophisticated way to extract the redirect url.
+ """
+
+ LINK_PATTERN = None
+
+ TEXT_ENCODING = False
+ COOKIES = True
+
+
+ def decrypt(self, pyfile):
+ link = getFileURL(self, pyfile.url)
+
+ if not link:
+ try:
+ link = urllib.unquote(re.match(self.__pattern, pyfile.url).group('LINK'))
+
+ except Exception:
+ self.prepare()
+ self.preload()
+ self.checkStatus()
+
+ link = self.getLink()
+
+ if link.strip():
+ self.urls = [link]
+
+
+ def prepare(self):
+ self.info = {}
+ self.html = ""
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+
+ def preload(self):
+ self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def checkStatus(self):
+ if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html):
+ self.tempOffline()
+
+
+ def getLink(self):
+ try:
+ return re.search(self.LINK_PATTERN, self.html).group(1)
+
+ except Exception:
+ pass
diff --git a/pyload/plugin/internal/SimpleHoster.py b/pyload/plugin/internal/SimpleHoster.py
new file mode 100644
index 000000000..a87986330
--- /dev/null
+++ b/pyload/plugin/internal/SimpleHoster.py
@@ -0,0 +1,757 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import mimetypes
+import os
+import re
+import time
+import urllib
+import urlparse
+
+from pyload.datatype.File import statusMap as _statusMap
+from pyload.network.CookieJar import CookieJar
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import Fail, Retry
+from pyload.utils import fixup, fs_encode, parseFileSize
+
+
+#@TODO: Adapt and move to PyFile in 0.4.10
+statusMap = dict((v, k) for k, v in _statusMap.iteritems())
+
+
+#@TODO: Remove in 0.4.10 and redirect to self.error instead
+def _error(self, reason, type):
+ if not reason and not type:
+ type = "unknown"
+
+ msg = _("%s error") % type.strip().capitalize() if type else _("Error")
+ msg += (": %s" % reason.strip()) if reason else ""
+ msg += _(" | Plugin may be out of date")
+
+ raise Fail(msg)
+
+
+#@TODO: Remove in 0.4.10
+def _wait(self, seconds, reconnect):
+ if seconds:
+ self.setWait(int(seconds) + 1)
+
+ if reconnect is not None:
+ self.wantReconnect = reconnect
+
+ super(SimpleHoster, self).wait()
+
+
+def replace_patterns(string, ruleslist):
+ for r in ruleslist:
+ rf, rt = r
+ string = re.sub(rf, rt, string)
+ return string
+
+
+def set_cookies(cj, cookies):
+ for cookie in cookies:
+ if isinstance(cookie, tuple) and len(cookie) == 3:
+ domain, name, value = cookie
+ cj.setCookie(domain, name, value)
+
+
+def parseHtmlTagAttrValue(attr_name, tag):
+ m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I)
+ return m.group(2) if m else None
+
+
+def parseHtmlForm(attr_str, html, input_names={}):
+ for form in re.finditer(r"(?P<TAG><form[^>]*%s[^>]*>)(?P<CONTENT>.*?)</?(form|body|html)[^>]*>" % attr_str,
+ html, re.S | re.I):
+ inputs = {}
+ action = parseHtmlTagAttrValue("action", form.group('TAG'))
+
+ for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('CONTENT'), re.S | re.I):
+ name = parseHtmlTagAttrValue("name", inputtag.group(1))
+ if name:
+ value = parseHtmlTagAttrValue("value", inputtag.group(1))
+ if not value:
+ inputs[name] = inputtag.group(3) or ''
+ else:
+ inputs[name] = value
+
+ if input_names:
+ # check input attributes
+ for key, val in input_names.iteritems():
+ if key in inputs:
+ if isinstance(val, basestring) and inputs[key] == val:
+ continue
+ elif isinstance(val, tuple) and inputs[key] in val:
+ continue
+ elif hasattr(val, "search") and re.match(val, inputs[key]):
+ continue
+ break #: attibute value does not match
+ else:
+ break #: attibute name does not match
+ else:
+ return action, inputs #: passed attribute check
+ else:
+ # no attribute check
+ return action, inputs
+
+ return {}, None #: no matching form found
+
+
+#: Deprecated
+def parseFileInfo(plugin, url="", html=""):
+ if hasattr(plugin, "getInfo"):
+ info = plugin.getInfo(url, html)
+ res = info['name'], info['size'], info['status'], info['url']
+ else:
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ res = ((url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 0,
+ 3 if url else 8,
+ url)
+
+ return res
+
+
+#@TODO: Remove in 0.4.10
+#@NOTE: Every plugin must have own parseInfos classmethod to work with 0.4.10
+# def create_getInfo(plugin):
+
+ # def generator(list):
+ # for x in list:
+ # yield x
+
+ # if hasattr(plugin, "parseInfos"):
+ # fn = lambda urls: generator((info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfos(urls))
+ # else:
+ # fn = lambda urls: generator(parseFileInfo(url) for url in urls)
+
+ # return fn
+
+
+def timestamp():
+ return int(time.time() * 1000)
+
+
+#@TODO: Move to hoster class in 0.4.10
+def getFileURL(self, url, follow_location=None):
+ link = ""
+ redirect = 1
+
+ if type(follow_location) is int:
+ redirect = max(follow_location, 1)
+ else:
+ redirect = 10
+
+ for i in xrange(redirect):
+ try:
+ self.logDebug("Redirect #%d to: %s" % (i, url))
+ header = self.load(url, just_header=True, decode=True)
+
+ except Exception: #: Bad bad bad...
+ req = pyreq.getHTTPRequest()
+ res = req.load(url, just_header=True, decode=True)
+
+ req.close()
+
+ header = {"code": req.code}
+ for line in res.splitlines():
+ line = line.strip()
+ if not line or ":" not in line:
+ continue
+
+ key, none, value = line.partition(":")
+ key = key.lower().strip()
+ value = value.strip()
+
+ if key in header:
+ if type(header[key]) == list:
+ header[key].append(value)
+ else:
+ header[key] = [header[key], value]
+ else:
+ header[key] = value
+
+ if 'content-disposition' in header:
+ link = url
+
+ elif 'location' in header and header['location'].strip():
+ location = header['location']
+
+ if not urlparse.urlparse(location).scheme:
+ url_p = urlparse.urlparse(url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+ location = urlparse.urljoin(baseurl, location)
+
+ if 'code' in header and header['code'] == 302:
+ link = location
+
+ if follow_location:
+ url = location
+ continue
+
+ else:
+ extension = os.path.splitext(urlparse.urlparse(url).path.split('/')[-1])[-1]
+
+ if 'content-type' in header and header['content-type'].strip():
+ mimetype = header['content-type'].split(';')[0].strip()
+
+ elif extension:
+ mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream"
+
+ else:
+ mimetype = ""
+
+ if mimetype and (link or 'html' not in mimetype):
+ link = url
+ else:
+ link = ""
+
+ break
+
+ else:
+ try:
+ self.logError(_("Too many redirects"))
+ except Exception:
+ pass
+
+ return link
+
+
+def secondsToMidnight(gmt=0):
+ now = datetime.datetime.utcnow() + datetime.timedelta(hours=gmt)
+
+ if now.hour is 0 and now.minute < 10:
+ midnight = now
+ else:
+ midnight = now + datetime.timedelta(days=1)
+
+ td = midnight.replace(hour=0, minute=10, second=0, microsecond=0) - now
+
+ if hasattr(td, 'total_seconds'):
+ res = td.total_seconds()
+ else: #: work-around for python 2.5 and 2.6 missing datetime.timedelta.total_seconds
+ res = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6
+
+ return int(res)
+
+
+class SimpleHoster(Hoster):
+ __name = "SimpleHoster"
+ __type = "hoster"
+ __version = "1.40"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Simple hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ """
+ Info patterns should be defined by each hoster:
+
+ INFO_PATTERN: (optional) Name and Size of the file
+ example: INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
+ or
+ NAME_PATTERN: (optional) Name that will be set for the file
+ example: NAME_PATTERN = r'(?P<N>file_name)'
+ SIZE_PATTERN: (optional) Size that will be checked for the file
+ example: SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
+
+ HASHSUM_PATTERN: (optional) Hash code and type of the file
+ example: HASHSUM_PATTERN = r'(?P<H>hash_code) (?P<T>MD5)'
+
+ OFFLINE_PATTERN: (optional) Check if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Check if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)'
+
+
+ Error handling patterns are all optional:
+
+ WAIT_PATTERN: (optional) Detect waiting time
+ example: WAIT_PATTERN = r''
+
+ PREMIUM_ONLY_PATTERN: (optional) Check if the file can be downloaded only with a premium account
+ example: PREMIUM_ONLY_PATTERN = r'Premium account required'
+
+ ERROR_PATTERN: (optional) Detect any error preventing download
+ example: ERROR_PATTERN = r''
+
+
+ Instead overriding handleFree and handlePremium methods you can define the following patterns for direct download:
+
+ LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download
+ example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download
+ example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"'
+ """
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
+ SIZE_REPLACEMENTS = []
+ URL_REPLACEMENTS = []
+
+ TEXT_ENCODING = False #: Set to True or encoding name if encoding value in http header is not correct
+ COOKIES = True #: or False or list of tuples [(domain, name, value)]
+ CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account
+ DIRECT_LINK = None #: Set to True to looking for direct link (as defined in handleDirect method), set to None to do it if self.account is True else False
+ MULTI_HOSTER = False #: Set to True to leech other hoster link (as defined in handleMulti method)
+ LOGIN_ACCOUNT = False #: Set to True to require account login
+ DISPOSITION = True #: Set to True to use any content-disposition value in http header as file name
+
+ directLink = getFileURL #@TODO: Remove in 0.4.10
+
+
+ @classmethod
+ def parseInfos(cls, urls): #@TODO: Built-in in 0.4.10 core (remove from plugins)
+ for url in urls:
+ url = replace_patterns(url, cls.URL_REPLACEMENTS)
+ yield cls.getInfo(url)
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ return {'name': (url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 'size': 0,
+ 'status': 3 if url else 8,
+ 'url': url}
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = cls.apiInfo(url)
+ online = info['status'] == 2
+
+ try:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict() #: pattern groups will be saved here
+
+ except Exception:
+ info['pattern'] = {}
+
+ if not html and not online:
+ if not url:
+ info['error'] = "missing url"
+ info['status'] = 1
+
+ elif info['status'] is 3 and not getFileURL(None, url):
+ try:
+ html = getURL(url, cookies=cls.COOKIES, decode=not cls.TEXT_ENCODING)
+
+ if isinstance(cls.TEXT_ENCODING, basestring):
+ html = unicode(html, cls.TEXT_ENCODING)
+
+ except BadHeader, e:
+ info['error'] = "%d: %s" % (e.code, e.content)
+
+ if e.code is 404:
+ info['status'] = 1
+
+ elif e.code is 503:
+ info['status'] = 6
+
+ if html:
+ if hasattr(cls, "OFFLINE_PATTERN") and re.search(cls.OFFLINE_PATTERN, html):
+ info['status'] = 1
+
+ elif hasattr(cls, "TEMP_OFFLINE_PATTERN") and re.search(cls.TEMP_OFFLINE_PATTERN, html):
+ info['status'] = 6
+
+ else:
+ for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"):
+ try:
+ attr = getattr(cls, pattern)
+ pdict = re.search(attr, html).groupdict()
+
+ if all(True for k in pdict if k not in info['pattern']):
+ info['pattern'].update(pdict)
+
+ except AttributeError:
+ continue
+
+ else:
+ online = True
+
+ if online:
+ info['status'] = 2
+
+ if 'N' in info['pattern']:
+ info['name'] = replace_patterns(urllib.unquote(info['pattern']['N'].strip()),
+ cls.NAME_REPLACEMENTS)
+
+ if 'S' in info['pattern']:
+ size = replace_patterns(info['pattern']['S'] + info['pattern']['U'] if 'U' in info['pattern'] else info['pattern']['S'],
+ cls.SIZE_REPLACEMENTS)
+ info['size'] = parseFileSize(size)
+
+ elif isinstance(info['size'], basestring):
+ unit = info['units'] if 'units' in info else None
+ info['size'] = parseFileSize(info['size'], unit)
+
+ if 'H' in info['pattern']:
+ hashtype = info['pattern']['T'] if 'T' in info['pattern'] else "hash"
+ info[hashtype] = info['pattern']['H']
+
+ if not info['pattern']:
+ info.pop('pattern', None)
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ self.pyfile.error = "" #@TODO: Remove in 0.4.10
+
+ self.info = {}
+ self.html = ""
+ self.link = "" #@TODO: Move to hoster class in 0.4.10
+ self.directDL = False #@TODO: Move to hoster class in 0.4.10
+ self.multihost = False #@TODO: Move to hoster class in 0.4.10
+
+ if not self.getConfig('use_premium', True):
+ self.retryFree()
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ if (self.MULTI_HOSTER
+ and (self.__pattern != self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern']
+ or re.match(self.__pattern, self.pyfile.url) is None)):
+ self.multihost = True
+ return
+
+ if self.DIRECT_LINK is None:
+ self.directDL = bool(self.account)
+ else:
+ self.directDL = self.DIRECT_LINK
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def preload(self):
+ self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def process(self, pyfile):
+ try:
+ self.prepare()
+ self.checkInfo()
+
+ if self.directDL:
+ self.logDebug("Looking for direct download link...")
+ self.handleDirect(pyfile)
+
+ if self.multihost and not self.link and not self.lastDownload:
+ self.logDebug("Looking for leeched download link...")
+ self.handleMulti(pyfile)
+
+ if not self.link and not self.lastDownload:
+ self.MULTI_HOSTER = False
+ self.retry(1, reason="Multi hoster fails")
+
+ if not self.link and not self.lastDownload:
+ self.preload()
+ self.checkInfo()
+
+ if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as premium download")
+ self.handlePremium(pyfile)
+
+ elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as free download")
+ self.handleFree(pyfile)
+
+ self.downloadLink(self.link, self.DISPOSITION)
+ self.checkFile()
+
+ except Fail, e: #@TODO: Move to PluginThread in 0.4.10
+ if self.premium:
+ self.logWarning(_("Premium download failed"))
+ self.retryFree()
+ else:
+ raise Fail(e)
+ def downloadLink(self, link, disposition=True):
+ if link and isinstance(link, basestring):
+ self.correctCaptcha()
+
+ if not urlparse.urlparse(link).scheme:
+ url_p = urlparse.urlparse(self.pyfile.url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+ link = urlparse.urljoin(baseurl, link)
+
+ self.download(link, ref=False, disposition=disposition)
+
+
+ def checkFile(self, rules={}):
+ if self.cTask and not self.lastDownload:
+ self.invalidCaptcha()
+ self.retry(10, reason=_("Wrong captcha"))
+
+ elif not self.lastDownload or not os.path.exists(fs_encode(self.lastDownload)):
+ self.lastDownload = ""
+ self.error(self.pyfile.error or _("No file downloaded"))
+
+ else:
+ errmsg = self.checkDownload({'Empty file': re.compile(r'\A\s*\Z'),
+ 'Html error': re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)')})
+
+ if not errmsg:
+ for r, p in [('Html file', re.compile(r'\A\s*<!DOCTYPE html')),
+ ('Request error', re.compile(r'([Aa]n error occured while processing your request)'))]:
+ if r not in rules:
+ rules[r] = p
+
+ for r, a in [('Error', "ERROR_PATTERN"),
+ ('Premium only', "PREMIUM_ONLY_PATTERN"),
+ ('Wait error', "WAIT_PATTERN")]:
+ if r not in rules and hasattr(self, a):
+ rules[r] = getattr(self, a)
+
+ errmsg = self.checkDownload(rules)
+
+ if not errmsg:
+ return
+
+ errmsg = errmsg.strip().capitalize()
+
+ try:
+ errmsg += " | " + self.lastCheck.group(1).strip()
+ except Exception:
+ pass
+
+ self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
+ self.retry(3, 60, errmsg)
+
+
+ def checkErrors(self):
+ if not self.html:
+ self.logWarning(_("No html code to check"))
+ return
+
+ if hasattr(self, 'PREMIUM_ONLY_PATTERN') and not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
+ self.fail(_("Link require a premium account to be handled"))
+
+ elif hasattr(self, 'ERROR_PATTERN'):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ try:
+ errmsg = m.group(1).strip()
+ except Exception:
+ errmsg = m.group(0).strip()
+
+ self.info['error'] = errmsg
+
+ if "hour" in errmsg:
+ self.wait(1 * 60 * 60, True)
+
+ elif re.search("da(il)?y|today", errmsg):
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ elif "minute" in errmsg:
+ self.wait(1 * 60)
+
+ else:
+ self.error(errmsg)
+
+ elif hasattr(self, 'WAIT_PATTERN'):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ try:
+ waitmsg = m.group(1).strip()
+ except Exception:
+ waitmsg = m.group(0).strip()
+
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec|)', waitmsg, re.I))
+ self.wait(wait_time, wait_time > 300)
+
+ self.info.pop('error', None)
+
+
+ def checkStatus(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("Update file info...")
+ self.logDebug("Previous file info: %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("Current file info: %s" % self.info)
+
+ try:
+ status = self.info['status']
+
+ if status is 1:
+ self.offline()
+
+ elif status is 6:
+ self.tempOffline()
+
+ elif status is 8:
+ self.fail(self.info['error'] if 'error' in self.info else "Failed")
+
+ finally:
+ self.logDebug("File status: %s" % statusMap[status])
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("Update file info...")
+ self.logDebug("Previous file info: %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("Current file info: %s" % self.info)
+
+ try:
+ url = self.info['url'].strip()
+ name = self.info['name'].strip()
+ if name and name != url:
+ self.pyfile.name = name
+
+ except Exception:
+ pass
+
+ try:
+ size = self.info['size']
+ if size > 0:
+ self.pyfile.size = size
+
+ except Exception:
+ pass
+
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File size: %s byte" % self.pyfile.size if self.pyfile.size > 0 else "File size: Unknown")
+
+
+ def checkInfo(self):
+ self.checkNameSize()
+
+ if self.html:
+ self.checkErrors()
+ self.checkNameSize()
+
+ self.checkStatus(getinfo=False)
+
+
+ #: Deprecated
+ def getFileInfo(self):
+ self.info = {}
+ self.checkInfo()
+ return self.info
+
+
+ def handleDirect(self, pyfile):
+ link = self.directLink(pyfile.url, self.resumeDownload)
+
+ if link:
+ self.logInfo(_("Direct download link detected"))
+ self.link = link
+ else:
+ self.logDebug("Direct download link not found")
+
+
+ def handleMulti(self, pyfile): #: Multi-hoster handler
+ pass
+
+
+ def handleFree(self, pyfile):
+ if not hasattr(self, 'LINK_FREE_PATTERN'):
+ self.logError(_("Free download not implemented"))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+ else:
+ self.link = m.group(1)
+
+
+ def handlePremium(self, pyfile):
+ if not hasattr(self, 'LINK_PREMIUM_PATTERN'):
+ self.logError(_("Premium download not implemented"))
+ self.logDebug("Handled as free download")
+ self.handleFree(pyfile)
+
+ m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ if m is None:
+ self.error(_("Premium download link not found"))
+ else:
+ self.link = m.group(1)
+
+
+ def longWait(self, wait_time=None, max_tries=3):
+ if wait_time and isinstance(wait_time, (int, long, float)):
+ time_str = "%dh %dm" % divmod(wait_time / 60, 60)
+ else:
+ wait_time = 900
+ time_str = _("(unknown time)")
+ max_tries = 100
+
+ self.logInfo(_("Download limit reached, reconnect or wait %s") % time_str)
+
+ self.wait(wait_time, True)
+ self.retry(max_tries=max_tries, reason=_("Download limit reached"))
+
+
+ def parseHtmlForm(self, attr_str="", input_names={}):
+ return parseHtmlForm(attr_str, self.html, input_names)
+
+
+ def checkTrafficLeft(self):
+ if not self.account:
+ return True
+
+ traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
+
+ if traffic is None:
+ return False
+ elif traffic == -1:
+ return True
+ else:
+ size = self.pyfile.size
+ self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size / 1024, self.user, traffic / 1024))
+ return size <= traffic
+
+
+ def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
+ """getConfig with default value - sublass may not implements all config options"""
+ try:
+ return self.getConf(option)
+
+ except KeyError:
+ return default
+
+
+ def retryFree(self):
+ if not self.premium:
+ return
+ self.premium = False
+ self.account = None
+ self.req = self.core.requestFactory.getRequest(self.getClassName())
+ self.retries = 0
+ raise Retry(_("Fallback to free download"))
+
+
+ def wait(self, seconds=0, reconnect=None):
+ return _wait(self, seconds, reconnect)
+
+
+ def error(self, reason="", type="parse"):
+ return _error(self, reason, type)
diff --git a/pyload/plugin/internal/XFSAccount.py b/pyload/plugin/internal/XFSAccount.py
new file mode 100644
index 000000000..105df3cd5
--- /dev/null
+++ b/pyload/plugin/internal/XFSAccount.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.plugin.Account import Account
+from pyload.plugin.internal.SimpleHoster import parseHtmlForm, set_cookies
+
+
+class XFSAccount(Account):
+ __name = "XFSAccount"
+ __type = "account"
+ __version = "0.36"
+
+ __description = """XFileSharing account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ HOSTER_DOMAIN = None
+ HOSTER_URL = None
+ LOGIN_URL = None
+
+ COOKIES = True
+
+ PREMIUM_PATTERN = r'\(Premium only\)'
+
+ VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})'
+
+ TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?<b>\s*(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
+ TRAFFIC_LEFT_UNIT = "MB" #: used only if no group <U> was found
+
+ LEECH_TRAFFIC_PATTERN = r'Leech Traffic left:<b>.*?(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
+ LEECH_TRAFFIC_UNIT = "MB" #: used only if no group <U> was found
+
+ LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|account was banned|Error<'
+
+
+ def init(self):
+ if not self.HOSTER_DOMAIN:
+ self.logError(_("Missing HOSTER_DOMAIN"))
+ self.COOKIES = False
+
+ else:
+ if not self.HOSTER_URL:
+ self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+ set_cookies(req.cj, self.COOKIES)
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ leechtraffic = None
+ premium = None
+
+ if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
+ return {'validuntil' : validuntil,
+ 'trafficleft' : trafficleft,
+ 'leechtraffic': leechtraffic,
+ 'premium' : premium}
+
+ html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True)
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ self.logDebug("Valid until: %s" % validuntil)
+
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ trafficleft = -1
+ else:
+ premium = False
+ validuntil = None #: registered account type (not premium)
+ else:
+ self.logDebug("VALID_UNTIL_PATTERN not found")
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ try:
+ traffic = m.groupdict()
+ size = traffic['S']
+
+ if "nlimited" in size:
+ trafficleft = -1
+ if validuntil is None:
+ validuntil = -1
+ else:
+ if 'U' in traffic:
+ unit = traffic['U']
+ elif isinstance(self.TRAFFIC_LEFT_UNIT, basestring):
+ unit = self.TRAFFIC_LEFT_UNIT
+ else:
+ unit = ""
+
+ trafficleft = self.parseTraffic(size + unit)
+
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.logDebug("TRAFFIC_LEFT_PATTERN not found")
+
+ leech = [m.groupdict() for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, html)]
+ if leech:
+ leechtraffic = 0
+ try:
+ for traffic in leech:
+ size = traffic['S']
+
+ if "nlimited" in size:
+ leechtraffic = -1
+ if validuntil is None:
+ validuntil = -1
+ break
+ else:
+ if 'U' in traffic:
+ unit = traffic['U']
+ elif isinstance(self.LEECH_TRAFFIC_UNIT, basestring):
+ unit = self.LEECH_TRAFFIC_UNIT
+ else:
+ unit = ""
+
+ leechtraffic += self.parseTraffic(size + unit)
+
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.logDebug("LEECH_TRAFFIC_PATTERN not found")
+
+ return {'validuntil' : validuntil,
+ 'trafficleft' : trafficleft,
+ 'leechtraffic': leechtraffic,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
+ raise Exception(_("Missing HOSTER_DOMAIN"))
+
+ if not self.LOGIN_URL:
+ self.LOGIN_URL = urlparse.urljoin(self.HOSTER_URL, "login.html")
+ html = req.load(self.LOGIN_URL, decode=True)
+
+ action, inputs = parseHtmlForm('name="FL"', html)
+ if not inputs:
+ inputs = {'op' : "login",
+ 'redirect': self.HOSTER_URL}
+
+ inputs.update({'login' : user,
+ 'password': data['password']})
+
+ if not action:
+ action = self.HOSTER_URL
+ html = req.load(action, post=inputs, decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/internal/XFSCrypter.py b/pyload/plugin/internal/XFSCrypter.py
new file mode 100644
index 000000000..4297de842
--- /dev/null
+++ b/pyload/plugin/internal/XFSCrypter.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class XFSCrypter(SimpleCrypter):
+ __name = "XFSCrypter"
+ __type = "crypter"
+ __version = "0.07"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharing decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+ URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')]
+
+ LINK_PATTERN = r'<a href="(.+?)".*?>.+?(?:</a>)?\s*</(?:td|TD)>'
+ NAME_PATTERN = r'<[tT]itle>.*?\: (?P<N>.+) folder</[tT]itle>'
+
+ OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
+ TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
+
+
+ def prepare(self):
+ if not self.HOSTER_DOMAIN:
+ if self.account:
+ account = self.account
+ else:
+ account_name = (self.getClassName() + ".py").replace("Folder.py", "").replace(".py", "")
+ account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
+
+ if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
+ self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
+ else:
+ self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+
+ return super(XFSCrypter, self).prepare()
diff --git a/pyload/plugin/internal/XFSHoster.py b/pyload/plugin/internal/XFSHoster.py
new file mode 100644
index 000000000..ca0dca045
--- /dev/null
+++ b/pyload/plugin/internal/XFSHoster.py
@@ -0,0 +1,325 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import random
+import re
+import urlparse
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+from pyload.utils import html_unescape
+
+
+class XFSHoster(SimpleHoster):
+ __name = "XFSHoster"
+ __type = "hoster"
+ __version = "0.46"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharing hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("stickell" , "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+ TEXT_ENCODING = False
+ DIRECT_LINK = None
+ MULTI_HOSTER = True #@NOTE: Should be default to False for safe, but I'm lazy...
+
+ NAME_PATTERN = r'(Filename[ ]*:[ ]*</b>(</td><td nowrap>)?|name="fname"[ ]+value="|<[\w^_]+ class="(file)?name">)\s*(?P<N>.+?)(\s*<|")'
+ SIZE_PATTERN = r'(Size[ ]*:[ ]*</b>(</td><td>)?|File:.*>|</font>\s*\(|<[\w^_]+ class="size">)\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
+ TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
+
+ WAIT_PATTERN = r'<span id="countdown_str">.*?>(\d+)</span>|id="countdown" value=".*?(\d+).*?"'
+ PREMIUM_ONLY_PATTERN = r'>This file is available for Premium Users only'
+ ERROR_PATTERN = r'(?:class=["\']err["\'].*?>|<[Cc]enter><b>|>Error</td>|>\(ERROR:)(?:\s*<.+?>\s*)*(.+?)(?:["\']|<|\))'
+
+ LINK_LEECH_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
+ LINK_PATTERN = None #: final download url pattern
+
+ CAPTCHA_PATTERN = r'(https?://[^"\']+?/captchas?/[^"\']+)'
+ CAPTCHA_BLOCK_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>'
+ RECAPTCHA_PATTERN = None
+ SOLVEMEDIA_PATTERN = None
+
+ FORM_PATTERN = None
+ FORM_INPUTS_MAP = None #: dict passed as input_names to parseHtmlForm
+
+
+ def setup(self):
+ self.chunkLimit = -1 if self.premium else 1
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ """ Initialize important variables """
+ if not self.HOSTER_DOMAIN:
+ if self.account:
+ account = self.account
+ else:
+ account = self.pyfile.m.core.accountManager.getAccountPlugin(self.getClassName())
+
+ if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
+ self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
+ else:
+ self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+
+ if not self.LINK_PATTERN:
+ pattern = r'(https?://(?:www\.)?([^/]*?%s|\d+\.\d+\.\d+\.\d+)(\:\d+)?(/d/|(/files)?/\d+/\w+/).+?)["\'<]'
+ self.LINK_PATTERN = pattern % self.HOSTER_DOMAIN.replace('.', '\.')
+
+ self.captcha = None
+ self.errmsg = None
+
+ super(XFSHoster, self).prepare()
+
+ if self.DIRECT_LINK is None:
+ self.directDL = self.premium
+
+
+ def handleFree(self, pyfile):
+ for i in xrange(1, 6):
+ self.logDebug("Getting download link: #%d" % i)
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ data = self.getPostParameters()
+
+ self.html = self.load(pyfile.url, post=data, ref=True, decode=True, follow_location=False)
+
+ m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
+ if m and not "op=" in m.group(1):
+ break
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+ else:
+ self.logError(data['op'] if 'op' in data else _("UNKNOWN"))
+ return ""
+
+ self.link = m.group(1)
+
+
+ def handlePremium(self, pyfile):
+ return self.handleFree(pyfile)
+
+
+ def handleMulti(self, pyfile):
+ if not self.account:
+ self.fail(_("Only registered or premium users can use url leech feature"))
+
+ # only tested with easybytez.com
+ self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN)
+
+ action, inputs = self.parseHtmlForm()
+
+ upload_id = "%012d" % int(random.random() * 10 ** 12)
+ action += upload_id + "&js_on=1&utype=prem&upload_type=url"
+
+ inputs['tos'] = '1'
+ inputs['url_mass'] = pyfile.url
+ inputs['up1oad_type'] = 'url'
+
+ self.logDebug(action, inputs)
+
+ self.req.setOption("timeout", 600) #: wait for file to upload to easybytez.com
+
+ self.html = self.load(action, post=inputs)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ if self.errmsg:
+ self.retry(reason=self.errmsg)
+ else:
+ self.error(_("TEXTAREA F1 not found"))
+
+ self.logDebug(inputs)
+
+ stmsg = inputs['st']
+
+ if stmsg == 'OK':
+ self.html = self.load(action, post=inputs)
+
+ elif 'Can not leech file' in stmsg:
+ self.retry(20, 3 * 60, _("Can not leech file"))
+
+ elif 'today' in stmsg:
+ self.retry(wait_time=secondsToMidnight(gmt=2), reason=_("You've used all Leech traffic today"))
+
+ else:
+ self.fail(stmsg)
+
+ # get easybytez.com link for uploaded file
+ m = re.search(self.LINK_LEECH_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_LEECH_PATTERN not found"))
+
+ header = self.load(m.group(1), just_header=True, decode=True)
+
+ if 'location' in header: #: Direct download link
+ self.link = header['location']
+
+
+ def checkErrors(self):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m is None:
+ self.errmsg = None
+ else:
+ self.errmsg = m.group(1).strip()
+
+ self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
+
+ if 'wait' in self.errmsg:
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec|)', self.errmsg, re.I))
+ self.wait(wait_time, wait_time > 300)
+
+ elif 'country' in self.errmsg:
+ self.fail(_("Downloads are disabled for your country"))
+
+ elif 'captcha' in self.errmsg:
+ self.invalidCaptcha()
+
+ elif 'premium' in self.errmsg and 'require' in self.errmsg:
+ self.fail(_("File can be downloaded by premium users only"))
+
+ elif 'limit' in self.errmsg:
+ if 'days' in self.errmsg:
+ delay = secondsToMidnight(gmt=2)
+ retries = 3
+ else:
+ delay = 1 * 60 * 60
+ retries = 24
+
+ self.wantReconnect = True
+ self.retry(retries, delay, _("Download limit exceeded"))
+
+ elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
+ self.retry(reason=_("Link expired"))
+
+ elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg:
+ self.tempOffline()
+
+ elif 'up to' in self.errmsg:
+ self.fail(_("File too large for free download"))
+
+ else:
+ self.wantReconnect = True
+ self.retry(wait_time=60, reason=self.errmsg)
+
+ if self.errmsg:
+ self.info['error'] = self.errmsg
+ else:
+ self.info.pop('error', None)
+
+
+ def getPostParameters(self):
+ if self.FORM_PATTERN or self.FORM_INPUTS_MAP:
+ action, inputs = self.parseHtmlForm(self.FORM_PATTERN or "", self.FORM_INPUTS_MAP or {})
+ else:
+ action, inputs = self.parseHtmlForm(input_names={'op': re.compile(r'^download')})
+
+ if not inputs:
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ if self.errmsg:
+ self.retry(reason=self.errmsg)
+ else:
+ self.error(_("TEXTAREA F1 not found"))
+
+ self.logDebug(inputs)
+
+ if 'op' in inputs:
+ if "password" in inputs:
+ password = self.getPassword()
+ if password:
+ inputs['password'] = password
+ else:
+ self.fail(_("Missing password"))
+
+ if not self.premium:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1))
+ self.setWait(wait_time, False)
+
+ self.captcha = self.handleCaptcha(inputs)
+
+ self.wait()
+ else:
+ inputs['referer'] = self.pyfile.url
+
+ if self.premium:
+ inputs['method_premium'] = "Premium Download"
+ inputs.pop('method_free', None)
+ else:
+ inputs['method_free'] = "Free Download"
+ inputs.pop('method_premium', None)
+
+ return inputs
+
+
+ def handleCaptcha(self, inputs):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_url = m.group(1)
+ inputs['code'] = self.decryptCaptcha(captcha_url)
+ return 1
+
+ m = re.search(self.CAPTCHA_BLOCK_PATTERN, self.html, re.S)
+ if m:
+ captcha_div = m.group(1)
+ numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
+
+ self.logDebug(captcha_div)
+
+ inputs['code'] = "".join(a[1] for a in sorted(numerals, key=lambda num: int(num[0])))
+
+ self.logDebug("Captcha code: %s" % inputs['code'], numerals)
+ return 2
+
+ recaptcha = ReCaptcha(self)
+ try:
+ captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1)
+
+ except Exception:
+ captcha_key = recaptcha.detect_key()
+
+ else:
+ self.logDebug("ReCaptcha key: %s" % captcha_key)
+
+ if captcha_key:
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge(captcha_key)
+ return 3
+
+ solvemedia = SolveMedia(self)
+ try:
+ captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1)
+
+ except Exception:
+ captcha_key = solvemedia.detect_key()
+
+ else:
+ self.logDebug("SolveMedia key: %s" % captcha_key)
+
+ if captcha_key:
+ inputs['adcopy_response'], inputs['adcopy_challenge'] = solvemedia.challenge(captcha_key)
+ return 4
+
+ return 0
diff --git a/pyload/plugin/internal/__init__.py b/pyload/plugin/internal/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/internal/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/ocr/GigasizeCom.py b/pyload/plugin/ocr/GigasizeCom.py
new file mode 100644
index 000000000..0c15b03e7
--- /dev/null
+++ b/pyload/plugin/ocr/GigasizeCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class GigasizeCom(OCR):
+ __name = "GigasizeCom"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Gigasize.com ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.threshold(2.8)
+ self.run_tesser(True, False, False, True)
+ return self.result_captcha
diff --git a/pyload/plugin/ocr/LinksaveIn.py b/pyload/plugin/ocr/LinksaveIn.py
new file mode 100644
index 000000000..44ab08592
--- /dev/null
+++ b/pyload/plugin/ocr/LinksaveIn.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+import glob
+import os
+
+from pyload.plugin.OCR import OCR
+
+
+class LinksaveIn(OCR):
+ __name = "LinksaveIn"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Linksave.in ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+ self.data_dir = os.path.dirname(os.path.abspath(__file__)) + os.sep + "LinksaveIn" + os.sep
+
+
+ def load_image(self, image):
+ im = Image.open(image)
+ frame_nr = 0
+
+ lut = im.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ new = Image.new("RGB", im.size)
+ npix = new.load()
+ while True:
+ try:
+ im.seek(frame_nr)
+ except EOFError:
+ break
+ frame = im.copy()
+ pix = frame.load()
+ for x in xrange(frame.size[0]):
+ for y in xrange(frame.size[1]):
+ if lut[pix[x, y]] != (0, 0, 0):
+ npix[x, y] = lut[pix[x, y]]
+ frame_nr += 1
+ new.save(self.data_dir+"unblacked.png")
+ self.image = new.copy()
+ self.pixels = self.image.load()
+ self.result_captcha = ''
+
+
+ def get_bg(self):
+ stat = {}
+ cstat = {}
+ img = self.image.convert("P")
+ for bgpath in glob.glob(self.data_dir+"bg/*.gif"):
+ stat[bgpath] = 0
+ bg = Image.open(bgpath)
+
+ bglut = bg.resize((256, 1))
+ bglut.putdata(range(256))
+ bglut = list(bglut.convert("RGB").getdata())
+
+ lut = img.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ bgpix = bg.load()
+ pix = img.load()
+ for x in xrange(bg.size[0]):
+ for y in xrange(bg.size[1]):
+ rgb_bg = bglut[bgpix[x, y]]
+ rgb_c = lut[pix[x, y]]
+ try:
+ cstat[rgb_c] += 1
+ except Exception:
+ cstat[rgb_c] = 1
+ if rgb_bg == rgb_c:
+ stat[bgpath] += 1
+ max_p = 0
+ bg = ""
+ for bgpath, value in stat.iteritems():
+ if max_p < value:
+ bg = bgpath
+ max_p = value
+ return bg
+
+
+ def substract_bg(self, bgpath):
+ bg = Image.open(bgpath)
+ img = self.image.convert("P")
+
+ bglut = bg.resize((256, 1))
+ bglut.putdata(range(256))
+ bglut = list(bglut.convert("RGB").getdata())
+
+ lut = img.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ bgpix = bg.load()
+ pix = img.load()
+ orgpix = self.image.load()
+ for x in xrange(bg.size[0]):
+ for y in xrange(bg.size[1]):
+ rgb_bg = bglut[bgpix[x, y]]
+ rgb_c = lut[pix[x, y]]
+ if rgb_c == rgb_bg:
+ orgpix[x, y] = (255, 255, 255)
+
+
+ def eval_black_white(self):
+ new = Image.new("RGB", (140, 75))
+ pix = new.load()
+ orgpix = self.image.load()
+ thresh = 4
+ for x in xrange(new.size[0]):
+ for y in xrange(new.size[1]):
+ rgb = orgpix[x, y]
+ r, g, b = rgb
+ pix[x, y] = (255, 255, 255)
+ if r > max(b, g)+thresh:
+ pix[x, y] = (0, 0, 0)
+ if g < min(r, b):
+ pix[x, y] = (0, 0, 0)
+ if g > max(r, b)+thresh:
+ pix[x, y] = (0, 0, 0)
+ if b > max(r, g)+thresh:
+ pix[x, y] = (0, 0, 0)
+ self.image = new
+ self.pixels = self.image.load()
+
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ bg = self.get_bg()
+ self.substract_bg(bg)
+ self.eval_black_white()
+ self.to_greyscale()
+ self.image.save(self.data_dir+"cleaned_pass1.png")
+ self.clean(4)
+ self.clean(4)
+ self.image.save(self.data_dir+"cleaned_pass2.png")
+ letters = self.split_captcha_letters()
+ final = ""
+ for n, letter in enumerate(letters):
+ self.image = letter
+ self.image.save(ocr.data_dir+"letter%d.png" % n)
+ self.run_tesser(True, True, False, False)
+ final += self.result_captcha
+
+ return final
diff --git a/pyload/plugin/ocr/NetloadIn.py b/pyload/plugin/ocr/NetloadIn.py
new file mode 100644
index 000000000..57651428b
--- /dev/null
+++ b/pyload/plugin/ocr/NetloadIn.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class NetloadIn(OCR):
+ __name = "NetloadIn"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Netload.in ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.to_greyscale()
+ self.clean(3)
+ self.clean(3)
+ self.run_tesser(True, True, False, False)
+
+ self.result_captcha = self.result_captcha.replace(" ", "")[:4] #: cut to 4 numbers
+
+ return self.result_captcha
diff --git a/pyload/plugin/ocr/ShareonlineBiz.py b/pyload/plugin/ocr/ShareonlineBiz.py
new file mode 100644
index 000000000..8efbdee35
--- /dev/null
+++ b/pyload/plugin/ocr/ShareonlineBiz.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class ShareonlineBiz(OCR):
+ __name = "ShareonlineBiz"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Shareonline.biz ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.to_greyscale()
+ self.image = self.image.resize((160, 50))
+ self.pixels = self.image.load()
+ self.threshold(1.85)
+ # self.eval_black_white(240)
+ # self.derotate_by_average()
+
+ letters = self.split_captcha_letters()
+
+ final = ""
+ for letter in letters:
+ self.image = letter
+ self.run_tesser(True, True, False, False)
+ final += self.result_captcha
+
+ return final
+
+ # tesseract at 60%
diff --git a/pyload/plugin/ocr/__init__.py b/pyload/plugin/ocr/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/ocr/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/ClickNLoadBackend.py b/pyload/remote/ClickNLoadBackend.py
new file mode 100644
index 000000000..2d3e29dbc
--- /dev/null
+++ b/pyload/remote/ClickNLoadBackend.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import re
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+from cgi import FieldStorage
+from urllib import unquote
+from base64 import standard_b64decode
+from binascii import unhexlify
+
+try:
+ from Crypto.Cipher import AES
+except Exception:
+ pass
+
+from pyload.manager.Remote import BackendBase
+
+core = None
+js = None
+
+
+class ClickNLoadBackend(BackendBase):
+
+ def setup(self, host, port):
+ self.httpd = HTTPServer((host, port), CNLHandler)
+ global core, js
+ core = self.m.core
+ js = core.js
+
+
+ def serve(self):
+ while self.enabled:
+ self.httpd.handle_request()
+
+
+class CNLHandler(BaseHTTPRequestHandler):
+
+ def add_package(self, name, urls, queue=0):
+ print "name", name
+ print "urls", urls
+ print "queue", queue
+
+
+ def get_post(self, name, default=""):
+ if name in self.post:
+ return self.post[name]
+ else:
+ return default
+
+
+ def start_response(self, string):
+
+ self.send_response(200)
+
+ self.send_header("Content-Length", len(string))
+ self.send_header("Content-Language", "de")
+ self.send_header("Vary", "Accept-Language, Cookie")
+ self.send_header("Cache-Control", "no-cache, must-revalidate")
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+
+ def do_GET(self):
+ path = self.path.strip("/").lower()
+ # self.wfile.write(path+"\n")
+
+ self.map = [(r"add$", self.add),
+ (r"addcrypted$", self.addcrypted),
+ (r"addcrypted2$", self.addcrypted2),
+ (r"flashgot", self.flashgot),
+ (r"crossdomain\.xml", self.crossdomain),
+ (r"checkSupportForUrl", self.checksupport),
+ (r"jdcheck.js", self.jdcheck),
+ (r"", self.flash)]
+
+ func = None
+ for r, f in self.map:
+ if re.match(r"(flash(got)?/?)?" + r, path):
+ func = f
+ break
+
+ if func:
+ try:
+ resp = func()
+ if not resp: resp = "success"
+ resp += "\r\n"
+ self.start_response(resp)
+ self.wfile.write(resp)
+ except Exception, e:
+ self.send_error(500, str(e))
+ else:
+ self.send_error(404, "Not Found")
+
+
+ def do_POST(self):
+ form = FieldStorage(
+ fp=self.rfile,
+ headers=self.headers,
+ environ={'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': self.headers['Content-Type'],
+ })
+
+ self.post = {}
+ for name in form.keys():
+ self.post[name] = form[name].value
+
+ return self.do_GET()
+
+
+ def flash(self):
+ return "JDownloader"
+
+
+ def add(self):
+ package = self.get_post('referer', 'ClickNLoad Package')
+ urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
+
+ self.add_package(package, urls, 0)
+
+
+ def addcrypted(self):
+ package = self.get_post('referer', 'ClickNLoad Package')
+ dlc = self.get_post('crypted').replace(" ", "+")
+
+ core.upload_container(package, dlc)
+
+
+ def addcrypted2(self):
+ package = self.get_post("source", "ClickNLoad Package")
+ crypted = self.get_post("crypted")
+ jk = self.get_post("jk")
+
+ crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
+ jk = "%s f()" % jk
+ jk = js.eval(jk)
+ Key = unhexlify(jk)
+ IV = Key
+
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n")
+
+ result = filter(lambda x: x != "", result)
+
+ self.add_package(package, result, 0)
+
+
+ def flashgot(self):
+ autostart = int(self.get_post('autostart', 0))
+ package = self.get_post('package', "FlashGot")
+ urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
+
+ self.add_package(package, urls, autostart)
+
+
+ def crossdomain(self):
+ rep = "<?xml version=\"1.0\"?>\n"
+ rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
+ rep += "<cross-domain-policy>\n"
+ rep += "<allow-access-from domain=\"*\" />\n"
+ rep += "</cross-domain-policy>"
+ return rep
+
+
+ def checksupport(self):
+ pass
+
+
+ def jdcheck(self):
+ rep = "jdownloader=true;\n"
+ rep += "var version='10629';\n"
+ return rep
diff --git a/pyload/remote/SocketBackend.py b/pyload/remote/SocketBackend.py
new file mode 100644
index 000000000..8b74d19ff
--- /dev/null
+++ b/pyload/remote/SocketBackend.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import SocketServer
+
+from pyload.manager.Remote import BackendBase
+
+
+class RequestHandler(SocketServer.BaseRequestHandler):
+
+ def setup(self):
+ pass
+
+
+ def handle(self):
+ print self.request.recv(1024)
+
+
+class SocketBackend(BackendBase):
+
+ def setup(self, host, port):
+ # local only
+ self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler)
+
+
+ def serve(self):
+ self.server.serve_forever()
diff --git a/pyload/remote/ThriftBackend.py b/pyload/remote/ThriftBackend.py
new file mode 100644
index 000000000..f71e264e2
--- /dev/null
+++ b/pyload/remote/ThriftBackend.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from os.path import exists
+
+from pyload.manager.Remote import BackendBase
+
+from pyload.remote.thriftbackend.Processor import Processor
+from pyload.remote.thriftbackend.Protocol import ProtocolFactory
+from pyload.remote.thriftbackend.Socket import ServerSocket
+from pyload.remote.thriftbackend.Transport import TransportFactory
+# from pyload.remote.thriftbackend.Transport import TransportFactoryCompressed
+
+from thrift.server import TServer
+
+
+class ThriftBackend(BackendBase):
+
+ def setup(self, host, port):
+ processor = Processor(self.core.api)
+
+ key = None
+ cert = None
+
+ if self.core.config.get("ssl", "activated"):
+ if exists(self.core.config.get("ssl", "cert")) and exists(self.core.config.get("ssl", "key")):
+ self.core.log.info(_("Using SSL ThriftBackend"))
+ key = self.core.config.get("ssl", "key")
+ cert = self.core.config.get("ssl", "cert")
+
+ transport = ServerSocket(port, host, key, cert)
+
+
+ # tfactory = TransportFactoryCompressed()
+ tfactory = TransportFactory()
+ pfactory = ProtocolFactory()
+
+ self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+ # self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory)
+
+ # server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
+
+
+ def serve(self):
+ self.server.serve()
diff --git a/pyload/remote/__init__.py b/pyload/remote/__init__.py
new file mode 100644
index 000000000..60dfa77c7
--- /dev/null
+++ b/pyload/remote/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+activated = True
diff --git a/pyload/remote/socketbackend/__init__.py b/pyload/remote/socketbackend/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/remote/socketbackend/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/socketbackend/create_ttypes.py b/pyload/remote/socketbackend/create_ttypes.py
new file mode 100644
index 000000000..00752dc6b
--- /dev/null
+++ b/pyload/remote/socketbackend/create_ttypes.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import inspect
+import os
+import platform
+import sys
+
+sys.path.append(os.path.join(pypath, "pyload", "remote"))
+
+from pyload.remote.thriftbackend.thriftgen.pyload import ttypes
+from pyload.remote.thriftbackend.thriftgen.pyload.Pyload import Iface
+
+
+def main():
+ enums = []
+ classes = []
+
+ print "generating lightweight ttypes.py"
+
+ for name in dir(ttypes):
+ klass = getattr(ttypes, name)
+
+ if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (
+ issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)):
+ continue
+
+ if hasattr(klass, "thrift_spec"):
+ classes.append(klass)
+ else:
+ enums.append(klass)
+
+
+ with open(os.path.join(pypath, "pyload", "api", "types.py"), "wb") as f:
+ f.write(
+"""# -*- coding: utf-8 -*-
+# Autogenerated by pyload
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+
+
+class BaseObject(object):
+\t__slots__ = []
+
+""")
+
+ # generate enums
+ for enum in enums:
+ name = enum.__name__
+ f.write("class %s:\n" % name)
+
+ for attr in dir(enum):
+ if attr.startswith("_") or attr in ("read", "write"):
+ continue
+
+ f.write("\t%s = %s\n" % (attr, getattr(enum, attr)))
+
+ f.write("\n")
+
+ for klass in classes:
+ name = klass.__name__
+ base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject"
+ f.write("class %s(%s):\n" % (name, base))
+ f.write("\t__slots__ = %s\n\n" % klass.__slots__)
+
+ # create init
+ args = ["self"] + ["%s=None" % x for x in klass.__slots__]
+
+ f.write("\tdef __init__(%s):\n" % ", ".join(args))
+ for attr in klass.__slots__:
+ f.write("\t\tself.%s = %s\n" % (attr, attr))
+
+ f.write("\n")
+
+ f.write("class Iface(object):\n")
+
+ for name in dir(Iface):
+ if name.startswith("_"):
+ continue
+
+ func = inspect.getargspec(getattr(Iface, name))
+
+ f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args)))
+
+ f.write("\n")
+
+ f.close()
diff --git a/pyload/remote/thriftbackend/Processor.py b/pyload/remote/thriftbackend/Processor.py
new file mode 100644
index 000000000..204047e2f
--- /dev/null
+++ b/pyload/remote/thriftbackend/Processor.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+
+
+class Processor(Pyload.Processor):
+
+ def __init__(self, *args, **kwargs):
+ Pyload.Processor.__init__(self, *args, **kwargs)
+ self.authenticated = {}
+
+
+ def process(self, iprot, oprot):
+ trans = oprot.trans
+ if trans not in self.authenticated:
+ self.authenticated[trans] = False
+ oldclose = trans.close
+
+
+ def wrap():
+ if self in self.authenticated:
+ del self.authenticated[trans]
+ oldclose()
+
+ trans.close = wrap
+ authenticated = self.authenticated[trans]
+ (name, type, seqid) = iprot.readMessageBegin()
+
+ # unknown method
+ if name not in self._processMap:
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name)
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ # not logged in
+ elif not authenticated and not name == "login":
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 20 - Not logged in (in situ declared error code)
+ x = Pyload.TApplicationException(20, 'Not logged in')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ elif not authenticated and name == "login":
+ args = Pyload.login_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = Pyload.login_result()
+ # api login
+ self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0])
+
+ result.success = bool(self.authenticated[trans])
+ oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ elif self._handler.isAuthorized(name, authenticated):
+ self._processMap[name](self, seqid, iprot, oprot)
+
+ else:
+ # no permission
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 21 - Not authorized
+ x = Pyload.TApplicationException(21, 'Not authorized')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ return True
diff --git a/pyload/remote/thriftbackend/Protocol.py b/pyload/remote/thriftbackend/Protocol.py
new file mode 100644
index 000000000..ecf0680ad
--- /dev/null
+++ b/pyload/remote/thriftbackend/Protocol.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from thrift.protocol import TBinaryProtocol
+
+
+class Protocol(TBinaryProtocol.TBinaryProtocol):
+
+ def writeString(self, str):
+ try:
+ str = str.encode("utf8", "ignore")
+ except Exception:
+ pass
+
+ self.writeI32(len(str))
+ self.trans.write(str)
+
+
+ def readString(self):
+ len = self.readI32()
+ str = self.trans.readAll(len)
+ try:
+ str = str.decode("utf8", "ignore")
+ except Exception:
+ pass
+
+ return str
+
+
+class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory):
+
+ def getProtocol(self, trans):
+ prot = Protocol(trans, self.strictRead, self.strictWrite)
+ return prot
diff --git a/pyload/remote/thriftbackend/Socket.py b/pyload/remote/thriftbackend/Socket.py
new file mode 100644
index 000000000..0ca9ed178
--- /dev/null
+++ b/pyload/remote/thriftbackend/Socket.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import socket
+import errno
+
+from time import sleep
+
+from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException
+
+WantReadError = Exception #: overwritten when ssl is used
+
+
+class SecureSocketConnection(object):
+
+ def __init__(self, connection):
+ self.__dict__['connection'] = connection
+
+
+ def __getattr__(self, name):
+ return getattr(self.__dict__['connection'], name)
+
+
+ def __setattr__(self, name, value):
+ setattr(self.__dict__['connection'], name, value)
+
+
+ def shutdown(self, how=1):
+ self.__dict__['connection'].shutdown()
+
+
+ def accept(self):
+ connection, address = self.__dict__['connection'].accept()
+ return SecureSocketConnection(connection), address
+
+
+ def send(self, buff):
+ try:
+ return self.__dict__['connection'].send(buff)
+ except WantReadError:
+ sleep(0.1)
+ return self.send(buff)
+
+
+ def recv(self, buff):
+ try:
+ return self.__dict__['connection'].recv(buff)
+ except WantReadError:
+ sleep(0.1)
+ return self.recv(buff)
+
+
+class Socket(TSocket):
+
+ def __init__(self, host='localhost', port=7228, ssl=False):
+ TSocket.__init__(self, host, port)
+ self.ssl = ssl
+
+
+ def open(self):
+ if self.ssl:
+ SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
+ WantReadError = SSL.WantReadError
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ c.set_connect_state()
+ self.handle = SecureSocketConnection(c)
+ else:
+ self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ # errno 104 connection reset
+
+ self.handle.settimeout(self._timeout)
+ self.handle.connect((self.host, self.port))
+
+
+ def read(self, sz):
+ try:
+ buff = self.handle.recv(sz)
+ except socket.error, e:
+ if (e.args[0] == errno.ECONNRESET and
+ (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
+ # freebsd and Mach don't follow POSIX semantic of recv
+ # and fail with ECONNRESET if peer performed shutdown.
+ # See corresponding comment and code in TSocket::read()
+ # in lib/cpp/src/transport/TSocket.cpp.
+ self.close()
+ # Trigger the check to raise the END_OF_FILE exception below.
+ buff = ''
+ else:
+ raise
+ except Exception, e:
+ # SSL connection was closed
+ if e.args == (-1, 'Unexpected EOF'):
+ buff = ''
+ elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],):
+ # a socket not using ssl tried to connect
+ buff = ''
+ else:
+ raise
+
+ if not len(buff):
+ raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes')
+ return buff
+
+
+class ServerSocket(TServerSocket, Socket):
+
+ def __init__(self, port=7228, host="0.0.0.0", key="", cert=""):
+ self.host = host
+ self.port = port
+ self.key = key
+ self.cert = cert
+ self.handle = None
+
+
+ def listen(self):
+ if self.cert and self.key:
+ SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
+ WantReadError = SSL.WantReadError
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_privatekey_file(self.key)
+ ctx.use_certificate_file(self.cert)
+
+ tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ tmpConnection.set_accept_state()
+ self.handle = SecureSocketConnection(tmpConnection)
+
+ else:
+ self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+
+ self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if hasattr(self.handle, 'set_timeout'):
+ self.handle.set_timeout(None)
+ self.handle.bind((self.host, self.port))
+ self.handle.listen(128)
+
+
+ def accept(self):
+ client, addr = self.handle.accept()
+ result = Socket()
+ result.setHandle(client)
+ return result
diff --git a/pyload/remote/thriftbackend/ThriftClient.py b/pyload/remote/thriftbackend/ThriftClient.py
new file mode 100644
index 000000000..b018fbcc4
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftClient.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import thrift
+
+from socket import error
+from traceback import print_exc
+
+from thrift.transport import TTransport
+# from thrift.transport.TZlibTransport import TZlibTransport
+from Socket import Socket
+from Protocol import Protocol
+
+# modules should import ttypes from here, when want to avoid importing API
+
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+
+ConnectionClosed = TTransport.TTransportException
+
+
+class WrongLogin(Exception):
+ pass
+
+
+class NoConnection(Exception):
+ pass
+
+
+class NoSSL(Exception):
+ pass
+
+
+class ThriftClient(object):
+
+ def __init__(self, host="localhost", port=7227, user="", password=""):
+
+ self.createConnection(host, port)
+ try:
+ self.transport.open()
+ except error, e:
+ if e.args and e.args[0] in (111, 10061):
+ raise NoConnection
+ else:
+ print_exc()
+ raise NoConnection
+
+ try:
+ correct = self.client.login(user, password)
+ except error, e:
+ if e.args and e.args[0] == 104:
+ # connection reset by peer, probably wants ssl
+ try:
+ self.createConnection(host, port, True)
+ # set timeout or a ssl socket will block when querying none ssl server
+ self.socket.setTimeout(10)
+
+ except ImportError:
+ #@TODO untested
+ raise NoSSL
+ try:
+ self.transport.open()
+ correct = self.client.login(user, password)
+ finally:
+ self.socket.setTimeout(None)
+ elif e.args and e.args[0] == 32:
+ raise NoConnection
+ else:
+ print_exc()
+ raise NoConnection
+
+ if not correct:
+ self.transport.close()
+ raise WrongLogin
+
+
+ def createConnection(self, host, port, ssl=False):
+ self.socket = Socket(host, port, ssl)
+ self.transport = TTransport.TBufferedTransport(self.socket)
+# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket))
+
+ protocol = Protocol(self.transport)
+ self.client = Pyload.Client(protocol)
+
+
+ def close(self):
+ self.transport.close()
+
+
+ def __getattr__(self, item):
+ return getattr(self.client, item)
diff --git a/pyload/remote/thriftbackend/ThriftTest.py b/pyload/remote/thriftbackend/ThriftTest.py
new file mode 100644
index 000000000..c95e060b8
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftTest.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+
+import os
+import platform
+import sys
+
+if "64" in platform.machine():
+ sys.path.append(os.path.join(pypath, "lib64"))
+sys.path.append(os.path.join(pypath, "lib", "Python", "Lib"))
+
+
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+from Socket import Socket
+
+from thrift import Thrift
+from thrift.transport import TTransport
+
+from Protocol import Protocol
+
+from time import time
+
+import xmlrpclib
+
+
+def bench(f, *args, **kwargs):
+ s = time()
+ ret = [f(*args, **kwargs) for _i in xrange(0, 100)]
+ e = time()
+ try:
+ print "%s: %f s" % (f._Method__name, e-s)
+ except Exception:
+ print "%s: %f s" % (f.__name__, e-s)
+ return ret
+
+from getpass import getpass
+user = raw_input("user ")
+passwd = getpass("password ")
+
+server_url = "http%s://%s:%s@%s:%s/" % (
+ "",
+ user,
+ passwd,
+ "127.0.0.1",
+ 7227
+)
+proxy = xmlrpclib.ServerProxy(server_url, allow_none=True)
+
+bench(proxy.get_server_version)
+bench(proxy.status_server)
+bench(proxy.status_downloads)
+# bench(proxy.get_queue)
+# bench(proxy.get_collector)
+print
+try:
+
+ # Make socket
+ transport = Socket('localhost', 7228, False)
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TBufferedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = Protocol(transport)
+
+ # Create a client to use the protocol encoder
+ client = Pyload.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ print "Login", client.login(user, passwd)
+
+ bench(client.getServerVersion)
+ bench(client.statusServer)
+ bench(client.statusDownloads)
+ # bench(client.getQueue)
+ # bench(client.getCollector)
+
+ print
+ print client.getServerVersion()
+ print client.statusServer()
+ print client.statusDownloads()
+ q = client.getQueue()
+
+ for p in q:
+ data = client.getPackageData(p.pid)
+ print data
+ print "Package Name: ", data.name
+
+ # Close!
+ transport.close()
+
+except Thrift.TException, tx:
+ print 'ThriftExpection: %s' % tx.message
diff --git a/pyload/remote/thriftbackend/Transport.py b/pyload/remote/thriftbackend/Transport.py
new file mode 100644
index 000000000..1d3d81718
--- /dev/null
+++ b/pyload/remote/thriftbackend/Transport.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from thrift.transport.TTransport import TBufferedTransport
+from thrift.transport.TZlibTransport import TZlibTransport
+
+
+class Transport(TBufferedTransport):
+ DEFAULT_BUFFER = 4096
+
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ TBufferedTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+
+class TransportCompressed(TZlibTransport):
+ DEFAULT_BUFFER = 4096
+
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ TZlibTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+
+class TransportFactory(object):
+
+ def getTransport(self, trans):
+ buffered = Transport(trans)
+ return buffered
+
+
+class TransportFactoryCompressed(object):
+ _last_trans = None
+ _last_z = None
+
+
+ def getTransport(self, trans, compresslevel=9):
+ if trans == self._last_trans:
+ return self._last_z
+ ztrans = TransportCompressed(Transport(trans), compresslevel)
+ self._last_trans = trans
+ self._last_z = ztrans
+ return ztrans
diff --git a/pyload/remote/thriftbackend/__init__.py b/pyload/remote/thriftbackend/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/remote/thriftbackend/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/thriftbackend/pyload.thrift b/pyload/remote/thriftbackend/pyload.thrift
new file mode 100644
index 000000000..3aef7af00
--- /dev/null
+++ b/pyload/remote/thriftbackend/pyload.thrift
@@ -0,0 +1,337 @@
+namespace java org.pyload.thrift
+
+typedef i32 FileID
+typedef i32 PackageID
+typedef i32 TaskID
+typedef i32 ResultID
+typedef i32 InteractionID
+typedef list<string> LinkList
+typedef string PluginName
+typedef byte Progress
+typedef byte Priority
+
+
+enum DownloadStatus {
+ Finished
+ Offline,
+ Online,
+ Queued,
+ Skipped,
+ Waiting,
+ TempOffline,
+ Starting,
+ Failed,
+ Aborted,
+ Decrypting,
+ Custom,
+ Downloading,
+ Processing,
+ Unknown
+}
+
+enum Destination {
+ Collector,
+ Queue
+}
+
+enum ElementType {
+ Package,
+ File
+}
+
+// types for user interaction
+// some may only be place holder currently not supported
+// also all input - output combination are not reasonable, see InteractionManager for further info
+enum Input {
+ NONE,
+ TEXT,
+ TEXTBOX,
+ PASSWORD,
+ BOOL, // confirm like, yes or no dialog
+ CLICK, // for positional captchas
+ CHOICE, // choice from list
+ MULTIPLE, // multiple choice from list of elements
+ LIST, // arbitary list of elements
+ TABLE // table like data structure
+}
+// more can be implemented by need
+
+// this describes the type of the outgoing interaction
+// ensure they can be logcial or'ed
+enum Output {
+ CAPTCHA = 1,
+ QUESTION = 2,
+ NOTIFICATION = 4,
+}
+
+struct DownloadInfo {
+ 1: FileID fid,
+ 2: string name,
+ 3: i64 speed,
+ 4: i32 eta,
+ 5: string format_eta,
+ 6: i64 bleft,
+ 7: i64 size,
+ 8: string format_size,
+ 9: Progress percent,
+ 10: DownloadStatus status,
+ 11: string statusmsg,
+ 12: string format_wait,
+ 13: i64 wait_until,
+ 14: PackageID packageID,
+ 15: string packageName,
+ 16: PluginName plugin,
+}
+
+struct ServerStatus {
+ 1: bool pause,
+ 2: i16 active,
+ 3: i16 queue,
+ 4: i16 total,
+ 5: i64 speed,
+ 6: bool download,
+ 7: bool reconnect
+}
+
+struct ConfigItem {
+ 1: string name,
+ 2: string description,
+ 3: string value,
+ 4: string type,
+}
+
+struct ConfigSection {
+ 1: string name,
+ 2: string description,
+ 3: list<ConfigItem> items,
+ 4: optional string outline
+}
+
+struct FileData {
+ 1: FileID fid,
+ 2: string url,
+ 3: string name,
+ 4: PluginName plugin,
+ 5: i64 size,
+ 6: string format_size,
+ 7: DownloadStatus status,
+ 8: string statusmsg,
+ 9: PackageID packageID,
+ 10: string error,
+ 11: i16 order
+}
+
+struct PackageData {
+ 1: PackageID pid,
+ 2: string name,
+ 3: string folder,
+ 4: string site,
+ 5: string password,
+ 6: Destination dest,
+ 7: i16 order,
+ 8: optional i16 linksdone,
+ 9: optional i64 sizedone,
+ 10: optional i64 sizetotal,
+ 11: optional i16 linkstotal,
+ 12: optional list<FileData> links,
+ 13: optional list<FileID> fids
+}
+
+struct InteractionTask {
+ 1: InteractionID iid,
+ 2: Input input,
+ 3: list<string> structure,
+ 4: list<string> preset,
+ 5: Output output,
+ 6: list<string> data,
+ 7: string title,
+ 8: string description,
+ 9: string plugin,
+}
+
+struct CaptchaTask {
+ 1: i16 tid,
+ 2: binary data,
+ 3: string type,
+ 4: string resultType
+}
+
+struct EventInfo {
+ 1: string eventname,
+ 2: optional i32 id,
+ 3: optional ElementType type,
+ 4: optional Destination destination
+}
+
+struct UserData {
+ 1: string name,
+ 2: string email,
+ 3: i32 role,
+ 4: i32 permission,
+ 5: string templateName
+}
+
+struct AccountInfo {
+ 1: i64 validuntil,
+ 2: string login,
+ 3: map<string, list<string>> options,
+ 4: bool valid,
+ 5: i64 trafficleft,
+ 6: i64 maxtraffic,
+ 7: bool premium,
+ 8: string type,
+}
+
+struct ServiceCall {
+ 1: PluginName plugin,
+ 2: string func,
+ 3: optional list<string> arguments,
+ 4: optional bool parseArguments, //default False
+}
+
+struct OnlineStatus {
+ 1: string name,
+ 2: PluginName plugin,
+ 3: string packagename,
+ 4: DownloadStatus status,
+ 5: i64 size, // size <= 0: unknown
+}
+
+struct OnlineCheck {
+ 1: ResultID rid, // -1 -> nothing more to get
+ 2: map<string, OnlineStatus> data, //url to result
+}
+
+
+// exceptions
+
+exception PackageDoesNotExists{
+ 1: PackageID pid
+}
+
+exception FileDoesNotExists{
+ 1: FileID fid
+}
+
+exception ServiceDoesNotExists{
+ 1: string plugin
+ 2: string func
+}
+
+exception ServiceException{
+ 1: string msg
+}
+
+service Pyload {
+
+ //config
+ string getConfigValue(1: string category, 2: string option, 3: string section),
+ void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section),
+ map<string, ConfigSection> getConfig(),
+ map<string, ConfigSection> getPluginConfig(),
+
+ // server status
+ void pauseServer(),
+ void unpauseServer(),
+ bool togglePause(),
+ ServerStatus statusServer(),
+ i64 freeSpace(),
+ string getServerVersion(),
+ void kill(),
+ void restart(),
+ list<string> getLog(1: i32 offset),
+ bool isTimeDownload(),
+ bool isTimeReconnect(),
+ bool toggleReconnect(),
+
+ // download preparing
+
+ // packagename - urls
+ map<string, LinkList> generatePackages(1: LinkList links),
+ map<PluginName, LinkList> checkURLs(1: LinkList urls),
+ map<PluginName, LinkList> parseURLs(1: string html, 2: string url),
+
+ // parses results and generates packages
+ OnlineCheck checkOnlineStatus(1: LinkList urls),
+ OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
+
+ // poll results from previosly started online check
+ OnlineCheck pollResults(1: ResultID rid),
+
+ // downloads - information
+ list<DownloadInfo> statusDownloads(),
+ PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e),
+ list<PackageData> getQueue(),
+ list<PackageData> getCollector(),
+ list<PackageData> getQueueData(),
+ list<PackageData> getCollectorData(),
+ map<i16, PackageID> getPackageOrder(1: Destination destination),
+ map<i16, FileID> getFileOrder(1: PackageID pid)
+
+ // downloads - adding/deleting
+ list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest),
+ PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest),
+ void addFiles(1: PackageID pid, 2: LinkList links),
+ void uploadContainer(1: string filename, 2: binary data),
+ void deleteFiles(1: list<FileID> fids),
+ void deletePackages(1: list<PackageID> pids),
+
+ // downloads - modifying
+ void pushToQueue(1: PackageID pid),
+ void pullFromQueue(1: PackageID pid),
+ void restartPackage(1: PackageID pid),
+ void restartFile(1: FileID fid),
+ void recheckPackage(1: PackageID pid),
+ void stopAllDownloads(),
+ void stopDownloads(1: list<FileID> fids),
+ void setPackageName(1: PackageID pid, 2: string name),
+ void movePackage(1: Destination destination, 2: PackageID pid),
+ void moveFiles(1: list<FileID> fids, 2: PackageID pid),
+ void orderPackage(1: PackageID pid, 2: i16 position),
+ void orderFile(1: FileID fid, 2: i16 position),
+ void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
+ list<PackageID> deleteFinished(),
+ void restartFailed(),
+
+ //events
+ list<EventInfo> getEvents(1: string uuid)
+
+ //accounts
+ list<AccountInfo> getAccounts(1: bool refresh),
+ list<string> getAccountTypes()
+ void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options),
+ void removeAccount(1: PluginName plugin, 2: string account),
+
+ //auth
+ bool login(1: string username, 2: string password),
+ UserData getUserData(1: string username, 2:string password),
+ map<string, UserData> getAllUserData(),
+
+ //services
+
+ // servicename: description
+ map<PluginName, map<string, string>> getServices(),
+ bool hasService(1: PluginName plugin, 2: string func),
+ string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e),
+
+
+ //info
+ // {plugin: {name: value}}
+ map<PluginName, map<string, string>> getAllInfo(),
+ map<string, string> getInfoByPlugin(1: PluginName plugin),
+
+ //scheduler
+
+ // TODO
+
+
+ // User interaction
+
+ //captcha
+ bool isCaptchaWaiting(),
+ CaptchaTask getCaptchaTask(1: bool exclusive),
+ string getCaptchaTaskStatus(1: TaskID tid),
+ void setCaptchaResult(1: TaskID tid, 2: string result),
+}
diff --git a/pyload/remote/thriftbackend/thriftgen/__init__.py b/pyload/remote/thriftbackend/thriftgen/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
new file mode 100644
index 000000000..ddc1dd451
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
@@ -0,0 +1,570 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+import sys
+import pprint
+from urlparse import urlparse
+from thrift.transport import TTransport
+from thrift.transport import TSocket
+from thrift.transport import THttpClient
+from thrift.protocol import TBinaryProtocol
+
+import Pyload
+from ttypes import *
+
+if len(sys.argv) <= 1 or sys.argv[1] == '--help':
+ print
+ print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]'
+ print
+ print 'Functions:'
+ print ' string getConfigValue(string category, string option, string section)'
+ print ' void setConfigValue(string category, string option, string value, string section)'
+ print ' getConfig()'
+ print ' getPluginConfig()'
+ print ' void pauseServer()'
+ print ' void unpauseServer()'
+ print ' bool togglePause()'
+ print ' ServerStatus statusServer()'
+ print ' i64 freeSpace()'
+ print ' string getServerVersion()'
+ print ' void kill()'
+ print ' void restart()'
+ print ' getLog(i32 offset)'
+ print ' bool isTimeDownload()'
+ print ' bool isTimeReconnect()'
+ print ' bool toggleReconnect()'
+ print ' generatePackages(LinkList links)'
+ print ' checkURLs(LinkList urls)'
+ print ' parseURLs(string html, string url)'
+ print ' OnlineCheck checkOnlineStatus(LinkList urls)'
+ print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)'
+ print ' OnlineCheck pollResults(ResultID rid)'
+ print ' statusDownloads()'
+ print ' PackageData getPackageData(PackageID pid)'
+ print ' PackageData getPackageInfo(PackageID pid)'
+ print ' FileData getFileData(FileID fid)'
+ print ' getQueue()'
+ print ' getCollector()'
+ print ' getQueueData()'
+ print ' getCollectorData()'
+ print ' getPackageOrder(Destination destination)'
+ print ' getFileOrder(PackageID pid)'
+ print ' generateAndAddPackages(LinkList links, Destination dest)'
+ print ' PackageID addPackage(string name, LinkList links, Destination dest)'
+ print ' void addFiles(PackageID pid, LinkList links)'
+ print ' void uploadContainer(string filename, string data)'
+ print ' void deleteFiles( fids)'
+ print ' void deletePackages( pids)'
+ print ' void pushToQueue(PackageID pid)'
+ print ' void pullFromQueue(PackageID pid)'
+ print ' void restartPackage(PackageID pid)'
+ print ' void restartFile(FileID fid)'
+ print ' void recheckPackage(PackageID pid)'
+ print ' void stopAllDownloads()'
+ print ' void stopDownloads( fids)'
+ print ' void setPackageName(PackageID pid, string name)'
+ print ' void movePackage(Destination destination, PackageID pid)'
+ print ' void moveFiles( fids, PackageID pid)'
+ print ' void orderPackage(PackageID pid, i16 position)'
+ print ' void orderFile(FileID fid, i16 position)'
+ print ' void setPackageData(PackageID pid, data)'
+ print ' deleteFinished()'
+ print ' void restartFailed()'
+ print ' getEvents(string uuid)'
+ print ' getAccounts(bool refresh)'
+ print ' getAccountTypes()'
+ print ' void updateAccount(PluginName plugin, string account, string password, options)'
+ print ' void removeAccount(PluginName plugin, string account)'
+ print ' bool login(string username, string password)'
+ print ' UserData getUserData(string username, string password)'
+ print ' getAllUserData()'
+ print ' getServices()'
+ print ' bool hasService(PluginName plugin, string func)'
+ print ' string call(ServiceCall info)'
+ print ' getAllInfo()'
+ print ' getInfoByPlugin(PluginName plugin)'
+ print ' bool isCaptchaWaiting()'
+ print ' CaptchaTask getCaptchaTask(bool exclusive)'
+ print ' string getCaptchaTaskStatus(TaskID tid)'
+ print ' void setCaptchaResult(TaskID tid, string result)'
+ print
+ sys.exit(0)
+
+pp = pprint.PrettyPrinter(indent = 2)
+host = 'localhost'
+port = 9090
+uri = ''
+framed = False
+http = False
+argi = 1
+
+if sys.argv[argi] == '-h':
+ parts = sys.argv[argi+1].split(':')
+ host = parts[0]
+ if len(parts) > 1:
+ port = int(parts[1])
+ argi += 2
+
+if sys.argv[argi] == '-u':
+ url = urlparse(sys.argv[argi+1])
+ parts = url[1].split(':')
+ host = parts[0]
+ if len(parts) > 1:
+ port = int(parts[1])
+ else:
+ port = 80
+ uri = url[2]
+ if url[4]:
+ uri += '?%s' % url[4]
+ http = True
+ argi += 2
+
+if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':
+ framed = True
+ argi += 1
+
+cmd = sys.argv[argi]
+args = sys.argv[argi+1:]
+
+if http:
+ transport = THttpClient.THttpClient(host, port, uri)
+else:
+ socket = TSocket.TSocket(host, port)
+ if framed:
+ transport = TTransport.TFramedTransport(socket)
+ else:
+ transport = TTransport.TBufferedTransport(socket)
+protocol = TBinaryProtocol.TBinaryProtocol(transport)
+client = Pyload.Client(protocol)
+transport.open()
+
+if cmd == 'getConfigValue':
+ if len(args) != 3:
+ print 'getConfigValue requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.getConfigValue(args[0], args[1], args[2],))
+
+elif cmd == 'setConfigValue':
+ if len(args) != 4:
+ print 'setConfigValue requires 4 args'
+ sys.exit(1)
+ pp.pprint(client.setConfigValue(args[0], args[1], args[2], args[3],))
+
+elif cmd == 'getConfig':
+ if len(args) != 0:
+ print 'getConfig requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getConfig())
+
+elif cmd == 'getPluginConfig':
+ if len(args) != 0:
+ print 'getPluginConfig requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getPluginConfig())
+
+elif cmd == 'pauseServer':
+ if len(args) != 0:
+ print 'pauseServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.pauseServer())
+
+elif cmd == 'unpauseServer':
+ if len(args) != 0:
+ print 'unpauseServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.unpauseServer())
+
+elif cmd == 'togglePause':
+ if len(args) != 0:
+ print 'togglePause requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.togglePause())
+
+elif cmd == 'statusServer':
+ if len(args) != 0:
+ print 'statusServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.statusServer())
+
+elif cmd == 'freeSpace':
+ if len(args) != 0:
+ print 'freeSpace requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.freeSpace())
+
+elif cmd == 'getServerVersion':
+ if len(args) != 0:
+ print 'getServerVersion requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getServerVersion())
+
+elif cmd == 'kill':
+ if len(args) != 0:
+ print 'kill requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.kill())
+
+elif cmd == 'restart':
+ if len(args) != 0:
+ print 'restart requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.restart())
+
+elif cmd == 'getLog':
+ if len(args) != 1:
+ print 'getLog requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getLog(eval(args[0]),))
+
+elif cmd == 'isTimeDownload':
+ if len(args) != 0:
+ print 'isTimeDownload requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isTimeDownload())
+
+elif cmd == 'isTimeReconnect':
+ if len(args) != 0:
+ print 'isTimeReconnect requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isTimeReconnect())
+
+elif cmd == 'toggleReconnect':
+ if len(args) != 0:
+ print 'toggleReconnect requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.toggleReconnect())
+
+elif cmd == 'generatePackages':
+ if len(args) != 1:
+ print 'generatePackages requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.generatePackages(eval(args[0]),))
+
+elif cmd == 'checkURLs':
+ if len(args) != 1:
+ print 'checkURLs requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.checkURLs(eval(args[0]),))
+
+elif cmd == 'parseURLs':
+ if len(args) != 2:
+ print 'parseURLs requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.parseURLs(args[0], args[1],))
+
+elif cmd == 'checkOnlineStatus':
+ if len(args) != 1:
+ print 'checkOnlineStatus requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.checkOnlineStatus(eval(args[0]),))
+
+elif cmd == 'checkOnlineStatusContainer':
+ if len(args) != 3:
+ print 'checkOnlineStatusContainer requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.checkOnlineStatusContainer(eval(args[0]), args[1], args[2],))
+
+elif cmd == 'pollResults':
+ if len(args) != 1:
+ print 'pollResults requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pollResults(eval(args[0]),))
+
+elif cmd == 'statusDownloads':
+ if len(args) != 0:
+ print 'statusDownloads requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.statusDownloads())
+
+elif cmd == 'getPackageData':
+ if len(args) != 1:
+ print 'getPackageData requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageData(eval(args[0]),))
+
+elif cmd == 'getPackageInfo':
+ if len(args) != 1:
+ print 'getPackageInfo requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageInfo(eval(args[0]),))
+
+elif cmd == 'getFileData':
+ if len(args) != 1:
+ print 'getFileData requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getFileData(eval(args[0]),))
+
+elif cmd == 'getQueue':
+ if len(args) != 0:
+ print 'getQueue requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getQueue())
+
+elif cmd == 'getCollector':
+ if len(args) != 0:
+ print 'getCollector requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getCollector())
+
+elif cmd == 'getQueueData':
+ if len(args) != 0:
+ print 'getQueueData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getQueueData())
+
+elif cmd == 'getCollectorData':
+ if len(args) != 0:
+ print 'getCollectorData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getCollectorData())
+
+elif cmd == 'getPackageOrder':
+ if len(args) != 1:
+ print 'getPackageOrder requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageOrder(eval(args[0]),))
+
+elif cmd == 'getFileOrder':
+ if len(args) != 1:
+ print 'getFileOrder requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getFileOrder(eval(args[0]),))
+
+elif cmd == 'generateAndAddPackages':
+ if len(args) != 2:
+ print 'generateAndAddPackages requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.generateAndAddPackages(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'addPackage':
+ if len(args) != 3:
+ print 'addPackage requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.addPackage(args[0], eval(args[1]), eval(args[2]),))
+
+elif cmd == 'addFiles':
+ if len(args) != 2:
+ print 'addFiles requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.addFiles(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'uploadContainer':
+ if len(args) != 2:
+ print 'uploadContainer requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.uploadContainer(args[0], args[1],))
+
+elif cmd == 'deleteFiles':
+ if len(args) != 1:
+ print 'deleteFiles requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.deleteFiles(eval(args[0]),))
+
+elif cmd == 'deletePackages':
+ if len(args) != 1:
+ print 'deletePackages requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.deletePackages(eval(args[0]),))
+
+elif cmd == 'pushToQueue':
+ if len(args) != 1:
+ print 'pushToQueue requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pushToQueue(eval(args[0]),))
+
+elif cmd == 'pullFromQueue':
+ if len(args) != 1:
+ print 'pullFromQueue requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pullFromQueue(eval(args[0]),))
+
+elif cmd == 'restartPackage':
+ if len(args) != 1:
+ print 'restartPackage requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.restartPackage(eval(args[0]),))
+
+elif cmd == 'restartFile':
+ if len(args) != 1:
+ print 'restartFile requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.restartFile(eval(args[0]),))
+
+elif cmd == 'recheckPackage':
+ if len(args) != 1:
+ print 'recheckPackage requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.recheckPackage(eval(args[0]),))
+
+elif cmd == 'stopAllDownloads':
+ if len(args) != 0:
+ print 'stopAllDownloads requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.stopAllDownloads())
+
+elif cmd == 'stopDownloads':
+ if len(args) != 1:
+ print 'stopDownloads requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.stopDownloads(eval(args[0]),))
+
+elif cmd == 'setPackageName':
+ if len(args) != 2:
+ print 'setPackageName requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setPackageName(eval(args[0]), args[1],))
+
+elif cmd == 'movePackage':
+ if len(args) != 2:
+ print 'movePackage requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.movePackage(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'moveFiles':
+ if len(args) != 2:
+ print 'moveFiles requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.moveFiles(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'orderPackage':
+ if len(args) != 2:
+ print 'orderPackage requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.orderPackage(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'orderFile':
+ if len(args) != 2:
+ print 'orderFile requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.orderFile(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'setPackageData':
+ if len(args) != 2:
+ print 'setPackageData requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setPackageData(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'deleteFinished':
+ if len(args) != 0:
+ print 'deleteFinished requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.deleteFinished())
+
+elif cmd == 'restartFailed':
+ if len(args) != 0:
+ print 'restartFailed requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.restartFailed())
+
+elif cmd == 'getEvents':
+ if len(args) != 1:
+ print 'getEvents requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getEvents(args[0],))
+
+elif cmd == 'getAccounts':
+ if len(args) != 1:
+ print 'getAccounts requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getAccounts(eval(args[0]),))
+
+elif cmd == 'getAccountTypes':
+ if len(args) != 0:
+ print 'getAccountTypes requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAccountTypes())
+
+elif cmd == 'updateAccount':
+ if len(args) != 4:
+ print 'updateAccount requires 4 args'
+ sys.exit(1)
+ pp.pprint(client.updateAccount(eval(args[0]), args[1], args[2], eval(args[3]),))
+
+elif cmd == 'removeAccount':
+ if len(args) != 2:
+ print 'removeAccount requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.removeAccount(eval(args[0]), args[1],))
+
+elif cmd == 'login':
+ if len(args) != 2:
+ print 'login requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.login(args[0], args[1],))
+
+elif cmd == 'getUserData':
+ if len(args) != 2:
+ print 'getUserData requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.getUserData(args[0], args[1],))
+
+elif cmd == 'getAllUserData':
+ if len(args) != 0:
+ print 'getAllUserData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAllUserData())
+
+elif cmd == 'getServices':
+ if len(args) != 0:
+ print 'getServices requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getServices())
+
+elif cmd == 'hasService':
+ if len(args) != 2:
+ print 'hasService requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.hasService(eval(args[0]), args[1],))
+
+elif cmd == 'call':
+ if len(args) != 1:
+ print 'call requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.call(eval(args[0]),))
+
+elif cmd == 'getAllInfo':
+ if len(args) != 0:
+ print 'getAllInfo requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAllInfo())
+
+elif cmd == 'getInfoByPlugin':
+ if len(args) != 1:
+ print 'getInfoByPlugin requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getInfoByPlugin(eval(args[0]),))
+
+elif cmd == 'isCaptchaWaiting':
+ if len(args) != 0:
+ print 'isCaptchaWaiting requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isCaptchaWaiting())
+
+elif cmd == 'getCaptchaTask':
+ if len(args) != 1:
+ print 'getCaptchaTask requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getCaptchaTask(eval(args[0]),))
+
+elif cmd == 'getCaptchaTaskStatus':
+ if len(args) != 1:
+ print 'getCaptchaTaskStatus requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),))
+
+elif cmd == 'setCaptchaResult':
+ if len(args) != 2:
+ print 'setCaptchaResult requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setCaptchaResult(eval(args[0]), args[1],))
+
+else:
+ print 'Unrecognized method %s' % cmd
+ sys.exit(1)
+
+transport.close()
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
new file mode 100644
index 000000000..1ba11dbb6
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -0,0 +1,5976 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+from ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.protocol.TBase import TBase, TExceptionBase
+
+
+class Iface(object):
+
+ def getConfigValue(self, category, option, section):
+ """
+ Parameters:
+ - category
+ - option
+ - section
+ """
+ pass
+
+
+ def setConfigValue(self, category, option, value, section):
+ """
+ Parameters:
+ - category
+ - option
+ - value
+ - section
+ """
+ pass
+
+
+ def getConfig(self,):
+ pass
+
+
+ def getPluginConfig(self,):
+ pass
+
+
+ def pauseServer(self,):
+ pass
+
+
+ def unpauseServer(self,):
+ pass
+
+
+ def togglePause(self,):
+ pass
+
+
+ def statusServer(self,):
+ pass
+
+
+ def freeSpace(self,):
+ pass
+
+
+ def getServerVersion(self,):
+ pass
+
+
+ def kill(self,):
+ pass
+
+
+ def restart(self,):
+ pass
+
+
+ def getLog(self, offset):
+ """
+ Parameters:
+ - offset
+ """
+ pass
+
+
+ def isTimeDownload(self,):
+ pass
+
+
+ def isTimeReconnect(self,):
+ pass
+
+
+ def toggleReconnect(self,):
+ pass
+
+
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
+ pass
+
+
+ def checkURLs(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ pass
+
+
+ def parseURLs(self, html, url):
+ """
+ Parameters:
+ - html
+ - url
+ """
+ pass
+
+
+ def checkOnlineStatus(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ pass
+
+
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ """
+ Parameters:
+ - urls
+ - filename
+ - data
+ """
+ pass
+
+
+ def pollResults(self, rid):
+ """
+ Parameters:
+ - rid
+ """
+ pass
+
+
+ def statusDownloads(self,):
+ pass
+
+
+ def getPackageData(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def getPackageInfo(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def getFileData(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ pass
+
+
+ def getQueue(self,):
+ pass
+
+
+ def getCollector(self,):
+ pass
+
+
+ def getQueueData(self,):
+ pass
+
+
+ def getCollectorData(self,):
+ pass
+
+
+ def getPackageOrder(self, destination):
+ """
+ Parameters:
+ - destination
+ """
+ pass
+
+
+ def getFileOrder(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def generateAndAddPackages(self, links, dest):
+ """
+ Parameters:
+ - links
+ - dest
+ """
+ pass
+
+
+ def addPackage(self, name, links, dest):
+ """
+ Parameters:
+ - name
+ - links
+ - dest
+ """
+ pass
+
+
+ def addFiles(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
+ pass
+
+
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ pass
+
+
+ def deleteFiles(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ pass
+
+
+ def deletePackages(self, pids):
+ """
+ Parameters:
+ - pids
+ """
+ pass
+
+
+ def pushToQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def pullFromQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def restartPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def restartFile(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ pass
+
+
+ def recheckPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+
+ def stopAllDownloads(self,):
+ pass
+
+
+ def stopDownloads(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ pass
+
+
+ def setPackageName(self, pid, name):
+ """
+ Parameters:
+ - pid
+ - name
+ """
+ pass
+
+
+ def movePackage(self, destination, pid):
+ """
+ Parameters:
+ - destination
+ - pid
+ """
+ pass
+
+
+ def moveFiles(self, fids, pid):
+ """
+ Parameters:
+ - fids
+ - pid
+ """
+ pass
+
+
+ def orderPackage(self, pid, position):
+ """
+ Parameters:
+ - pid
+ - position
+ """
+ pass
+
+
+ def orderFile(self, fid, position):
+ """
+ Parameters:
+ - fid
+ - position
+ """
+ pass
+
+
+ def setPackageData(self, pid, data):
+ """
+ Parameters:
+ - pid
+ - data
+ """
+ pass
+
+
+ def deleteFinished(self,):
+ pass
+
+
+ def restartFailed(self,):
+ pass
+
+
+ def getEvents(self, uuid):
+ """
+ Parameters:
+ - uuid
+ """
+ pass
+
+
+ def getAccounts(self, refresh):
+ """
+ Parameters:
+ - refresh
+ """
+ pass
+
+
+ def getAccountTypes(self,):
+ pass
+
+
+ def updateAccount(self, plugin, account, password, options):
+ """
+ Parameters:
+ - plugin
+ - account
+ - password
+ - options
+ """
+ pass
+
+
+ def removeAccount(self, plugin, account):
+ """
+ Parameters:
+ - plugin
+ - account
+ """
+ pass
+
+
+ def login(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ pass
+
+
+ def getUserData(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ pass
+
+
+ def getAllUserData(self,):
+ pass
+
+
+ def getServices(self,):
+ pass
+
+
+ def hasService(self, plugin, func):
+ """
+ Parameters:
+ - plugin
+ - func
+ """
+ pass
+
+
+ def call(self, info):
+ """
+ Parameters:
+ - info
+ """
+ pass
+
+
+ def getAllInfo(self,):
+ pass
+
+
+ def getInfoByPlugin(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ pass
+
+
+ def isCaptchaWaiting(self,):
+ pass
+
+
+ def getCaptchaTask(self, exclusive):
+ """
+ Parameters:
+ - exclusive
+ """
+ pass
+
+
+ def getCaptchaTaskStatus(self, tid):
+ """
+ Parameters:
+ - tid
+ """
+ pass
+
+
+ def setCaptchaResult(self, tid, result):
+ """
+ Parameters:
+ - tid
+ - result
+ """
+ pass
+
+
+class Client(Iface):
+
+ def __init__(self, iprot, oprot=None):
+ self._iprot = self._oprot = iprot
+ if oprot is not None:
+ self._oprot = oprot
+ self._seqid = 0
+
+
+ def getConfigValue(self, category, option, section):
+ """
+ Parameters:
+ - category
+ - option
+ - section
+ """
+ self.send_getConfigValue(category, option, section)
+ return self.recv_getConfigValue()
+
+
+ def send_getConfigValue(self, category, option, section):
+ self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid)
+ args = getConfigValue_args()
+ args.category = category
+ args.option = option
+ args.section = section
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getConfigValue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getConfigValue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result");
+
+
+ def setConfigValue(self, category, option, value, section):
+ """
+ Parameters:
+ - category
+ - option
+ - value
+ - section
+ """
+ self.send_setConfigValue(category, option, value, section)
+ self.recv_setConfigValue()
+
+
+ def send_setConfigValue(self, category, option, value, section):
+ self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid)
+ args = setConfigValue_args()
+ args.category = category
+ args.option = option
+ args.value = value
+ args.section = section
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_setConfigValue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setConfigValue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def getConfig(self,):
+ self.send_getConfig()
+ return self.recv_getConfig()
+
+
+ def send_getConfig(self,):
+ self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid)
+ args = getConfig_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getConfig(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result");
+
+
+ def getPluginConfig(self,):
+ self.send_getPluginConfig()
+ return self.recv_getPluginConfig()
+
+
+ def send_getPluginConfig(self,):
+ self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid)
+ args = getPluginConfig_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getPluginConfig(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPluginConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result");
+
+
+ def pauseServer(self,):
+ self.send_pauseServer()
+ self.recv_pauseServer()
+
+
+ def send_pauseServer(self,):
+ self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid)
+ args = pauseServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_pauseServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pauseServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def unpauseServer(self,):
+ self.send_unpauseServer()
+ self.recv_unpauseServer()
+
+
+ def send_unpauseServer(self,):
+ self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid)
+ args = unpauseServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_unpauseServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = unpauseServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def togglePause(self,):
+ self.send_togglePause()
+ return self.recv_togglePause()
+
+
+ def send_togglePause(self,):
+ self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid)
+ args = togglePause_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_togglePause(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = togglePause_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result");
+
+
+ def statusServer(self,):
+ self.send_statusServer()
+ return self.recv_statusServer()
+
+
+ def send_statusServer(self,):
+ self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid)
+ args = statusServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_statusServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = statusServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result");
+
+
+ def freeSpace(self,):
+ self.send_freeSpace()
+ return self.recv_freeSpace()
+
+
+ def send_freeSpace(self,):
+ self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid)
+ args = freeSpace_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_freeSpace(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = freeSpace_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result");
+
+
+ def getServerVersion(self,):
+ self.send_getServerVersion()
+ return self.recv_getServerVersion()
+
+
+ def send_getServerVersion(self,):
+ self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid)
+ args = getServerVersion_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getServerVersion(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getServerVersion_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result");
+
+
+ def kill(self,):
+ self.send_kill()
+ self.recv_kill()
+
+
+ def send_kill(self,):
+ self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid)
+ args = kill_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_kill(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = kill_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def restart(self,):
+ self.send_restart()
+ self.recv_restart()
+
+
+ def send_restart(self,):
+ self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid)
+ args = restart_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_restart(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restart_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def getLog(self, offset):
+ """
+ Parameters:
+ - offset
+ """
+ self.send_getLog(offset)
+ return self.recv_getLog()
+
+
+ def send_getLog(self, offset):
+ self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid)
+ args = getLog_args()
+ args.offset = offset
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getLog(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getLog_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result");
+
+
+ def isTimeDownload(self,):
+ self.send_isTimeDownload()
+ return self.recv_isTimeDownload()
+
+
+ def send_isTimeDownload(self,):
+ self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid)
+ args = isTimeDownload_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_isTimeDownload(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isTimeDownload_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result");
+
+
+ def isTimeReconnect(self,):
+ self.send_isTimeReconnect()
+ return self.recv_isTimeReconnect()
+
+
+ def send_isTimeReconnect(self,):
+ self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid)
+ args = isTimeReconnect_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_isTimeReconnect(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isTimeReconnect_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result");
+
+
+ def toggleReconnect(self,):
+ self.send_toggleReconnect()
+ return self.recv_toggleReconnect()
+
+
+ def send_toggleReconnect(self,):
+ self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid)
+ args = toggleReconnect_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_toggleReconnect(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = toggleReconnect_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result");
+
+
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
+ self.send_generatePackages(links)
+ return self.recv_generatePackages()
+
+
+ def send_generatePackages(self, links):
+ self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid)
+ args = generatePackages_args()
+ args.links = links
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_generatePackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = generatePackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result");
+
+
+ def checkURLs(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ self.send_checkURLs(urls)
+ return self.recv_checkURLs()
+
+
+ def send_checkURLs(self, urls):
+ self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid)
+ args = checkURLs_args()
+ args.urls = urls
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_checkURLs(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkURLs_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result");
+
+
+ def parseURLs(self, html, url):
+ """
+ Parameters:
+ - html
+ - url
+ """
+ self.send_parseURLs(html, url)
+ return self.recv_parseURLs()
+
+
+ def send_parseURLs(self, html, url):
+ self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid)
+ args = parseURLs_args()
+ args.html = html
+ args.url = url
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_parseURLs(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = parseURLs_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result");
+
+
+ def checkOnlineStatus(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ self.send_checkOnlineStatus(urls)
+ return self.recv_checkOnlineStatus()
+
+
+ def send_checkOnlineStatus(self, urls):
+ self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid)
+ args = checkOnlineStatus_args()
+ args.urls = urls
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_checkOnlineStatus(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkOnlineStatus_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result");
+
+
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ """
+ Parameters:
+ - urls
+ - filename
+ - data
+ """
+ self.send_checkOnlineStatusContainer(urls, filename, data)
+ return self.recv_checkOnlineStatusContainer()
+
+
+ def send_checkOnlineStatusContainer(self, urls, filename, data):
+ self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid)
+ args = checkOnlineStatusContainer_args()
+ args.urls = urls
+ args.filename = filename
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_checkOnlineStatusContainer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkOnlineStatusContainer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result");
+
+
+ def pollResults(self, rid):
+ """
+ Parameters:
+ - rid
+ """
+ self.send_pollResults(rid)
+ return self.recv_pollResults()
+
+
+ def send_pollResults(self, rid):
+ self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid)
+ args = pollResults_args()
+ args.rid = rid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_pollResults(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pollResults_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result");
+
+
+ def statusDownloads(self,):
+ self.send_statusDownloads()
+ return self.recv_statusDownloads()
+
+
+ def send_statusDownloads(self,):
+ self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid)
+ args = statusDownloads_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_statusDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = statusDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result");
+
+
+ def getPackageData(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getPackageData(pid)
+ return self.recv_getPackageData()
+
+
+ def send_getPackageData(self, pid):
+ self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid)
+ args = getPackageData_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getPackageData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result");
+
+
+ def getPackageInfo(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getPackageInfo(pid)
+ return self.recv_getPackageInfo()
+
+
+ def send_getPackageInfo(self, pid):
+ self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid)
+ args = getPackageInfo_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getPackageInfo(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result");
+
+
+ def getFileData(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ self.send_getFileData(fid)
+ return self.recv_getFileData()
+
+
+ def send_getFileData(self, fid):
+ self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid)
+ args = getFileData_args()
+ args.fid = fid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getFileData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getFileData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result");
+
+
+ def getQueue(self,):
+ self.send_getQueue()
+ return self.recv_getQueue()
+
+
+ def send_getQueue(self,):
+ self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid)
+ args = getQueue_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result");
+
+
+ def getCollector(self,):
+ self.send_getCollector()
+ return self.recv_getCollector()
+
+
+ def send_getCollector(self,):
+ self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid)
+ args = getCollector_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getCollector(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCollector_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result");
+
+
+ def getQueueData(self,):
+ self.send_getQueueData()
+ return self.recv_getQueueData()
+
+
+ def send_getQueueData(self,):
+ self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid)
+ args = getQueueData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getQueueData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getQueueData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result");
+
+
+ def getCollectorData(self,):
+ self.send_getCollectorData()
+ return self.recv_getCollectorData()
+
+
+ def send_getCollectorData(self,):
+ self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid)
+ args = getCollectorData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getCollectorData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCollectorData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result");
+
+
+ def getPackageOrder(self, destination):
+ """
+ Parameters:
+ - destination
+ """
+ self.send_getPackageOrder(destination)
+ return self.recv_getPackageOrder()
+
+
+ def send_getPackageOrder(self, destination):
+ self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid)
+ args = getPackageOrder_args()
+ args.destination = destination
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getPackageOrder(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageOrder_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result");
+
+
+ def getFileOrder(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getFileOrder(pid)
+ return self.recv_getFileOrder()
+
+
+ def send_getFileOrder(self, pid):
+ self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid)
+ args = getFileOrder_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getFileOrder(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getFileOrder_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result");
+
+
+ def generateAndAddPackages(self, links, dest):
+ """
+ Parameters:
+ - links
+ - dest
+ """
+ self.send_generateAndAddPackages(links, dest)
+ return self.recv_generateAndAddPackages()
+
+
+ def send_generateAndAddPackages(self, links, dest):
+ self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
+ args = generateAndAddPackages_args()
+ args.links = links
+ args.dest = dest
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_generateAndAddPackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = generateAndAddPackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result");
+
+
+ def addPackage(self, name, links, dest):
+ """
+ Parameters:
+ - name
+ - links
+ - dest
+ """
+ self.send_addPackage(name, links, dest)
+ return self.recv_addPackage()
+
+
+ def send_addPackage(self, name, links, dest):
+ self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
+ args = addPackage_args()
+ args.name = name
+ args.links = links
+ args.dest = dest
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_addPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = addPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result");
+
+
+ def addFiles(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
+ self.send_addFiles(pid, links)
+ self.recv_addFiles()
+
+
+ def send_addFiles(self, pid, links):
+ self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid)
+ args = addFiles_args()
+ args.pid = pid
+ args.links = links
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_addFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = addFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ self.send_uploadContainer(filename, data)
+ self.recv_uploadContainer()
+
+
+ def send_uploadContainer(self, filename, data):
+ self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid)
+ args = uploadContainer_args()
+ args.filename = filename
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_uploadContainer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = uploadContainer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def deleteFiles(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ self.send_deleteFiles(fids)
+ self.recv_deleteFiles()
+
+
+ def send_deleteFiles(self, fids):
+ self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid)
+ args = deleteFiles_args()
+ args.fids = fids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_deleteFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deleteFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def deletePackages(self, pids):
+ """
+ Parameters:
+ - pids
+ """
+ self.send_deletePackages(pids)
+ self.recv_deletePackages()
+
+
+ def send_deletePackages(self, pids):
+ self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid)
+ args = deletePackages_args()
+ args.pids = pids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_deletePackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deletePackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def pushToQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_pushToQueue(pid)
+ self.recv_pushToQueue()
+
+
+ def send_pushToQueue(self, pid):
+ self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid)
+ args = pushToQueue_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_pushToQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pushToQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def pullFromQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_pullFromQueue(pid)
+ self.recv_pullFromQueue()
+
+
+ def send_pullFromQueue(self, pid):
+ self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid)
+ args = pullFromQueue_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_pullFromQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pullFromQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def restartPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_restartPackage(pid)
+ self.recv_restartPackage()
+
+
+ def send_restartPackage(self, pid):
+ self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid)
+ args = restartPackage_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_restartPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def restartFile(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ self.send_restartFile(fid)
+ self.recv_restartFile()
+
+
+ def send_restartFile(self, fid):
+ self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid)
+ args = restartFile_args()
+ args.fid = fid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_restartFile(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartFile_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def recheckPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_recheckPackage(pid)
+ self.recv_recheckPackage()
+
+
+ def send_recheckPackage(self, pid):
+ self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid)
+ args = recheckPackage_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_recheckPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = recheckPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def stopAllDownloads(self,):
+ self.send_stopAllDownloads()
+ self.recv_stopAllDownloads()
+
+
+ def send_stopAllDownloads(self,):
+ self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid)
+ args = stopAllDownloads_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_stopAllDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = stopAllDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def stopDownloads(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ self.send_stopDownloads(fids)
+ self.recv_stopDownloads()
+
+
+ def send_stopDownloads(self, fids):
+ self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid)
+ args = stopDownloads_args()
+ args.fids = fids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_stopDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = stopDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def setPackageName(self, pid, name):
+ """
+ Parameters:
+ - pid
+ - name
+ """
+ self.send_setPackageName(pid, name)
+ self.recv_setPackageName()
+
+
+ def send_setPackageName(self, pid, name):
+ self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid)
+ args = setPackageName_args()
+ args.pid = pid
+ args.name = name
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_setPackageName(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPackageName_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def movePackage(self, destination, pid):
+ """
+ Parameters:
+ - destination
+ - pid
+ """
+ self.send_movePackage(destination, pid)
+ self.recv_movePackage()
+
+
+ def send_movePackage(self, destination, pid):
+ self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid)
+ args = movePackage_args()
+ args.destination = destination
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_movePackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = movePackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def moveFiles(self, fids, pid):
+ """
+ Parameters:
+ - fids
+ - pid
+ """
+ self.send_moveFiles(fids, pid)
+ self.recv_moveFiles()
+
+
+ def send_moveFiles(self, fids, pid):
+ self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid)
+ args = moveFiles_args()
+ args.fids = fids
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_moveFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = moveFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def orderPackage(self, pid, position):
+ """
+ Parameters:
+ - pid
+ - position
+ """
+ self.send_orderPackage(pid, position)
+ self.recv_orderPackage()
+
+
+ def send_orderPackage(self, pid, position):
+ self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid)
+ args = orderPackage_args()
+ args.pid = pid
+ args.position = position
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_orderPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = orderPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def orderFile(self, fid, position):
+ """
+ Parameters:
+ - fid
+ - position
+ """
+ self.send_orderFile(fid, position)
+ self.recv_orderFile()
+
+
+ def send_orderFile(self, fid, position):
+ self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid)
+ args = orderFile_args()
+ args.fid = fid
+ args.position = position
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_orderFile(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = orderFile_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def setPackageData(self, pid, data):
+ """
+ Parameters:
+ - pid
+ - data
+ """
+ self.send_setPackageData(pid, data)
+ self.recv_setPackageData()
+
+
+ def send_setPackageData(self, pid, data):
+ self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid)
+ args = setPackageData_args()
+ args.pid = pid
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_setPackageData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPackageData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.e is not None:
+ raise result.e
+ return
+
+
+ def deleteFinished(self,):
+ self.send_deleteFinished()
+ return self.recv_deleteFinished()
+
+
+ def send_deleteFinished(self,):
+ self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid)
+ args = deleteFinished_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_deleteFinished(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deleteFinished_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result");
+
+
+ def restartFailed(self,):
+ self.send_restartFailed()
+ self.recv_restartFailed()
+
+
+ def send_restartFailed(self,):
+ self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid)
+ args = restartFailed_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_restartFailed(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartFailed_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def getEvents(self, uuid):
+ """
+ Parameters:
+ - uuid
+ """
+ self.send_getEvents(uuid)
+ return self.recv_getEvents()
+
+
+ def send_getEvents(self, uuid):
+ self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid)
+ args = getEvents_args()
+ args.uuid = uuid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getEvents(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getEvents_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result");
+
+
+ def getAccounts(self, refresh):
+ """
+ Parameters:
+ - refresh
+ """
+ self.send_getAccounts(refresh)
+ return self.recv_getAccounts()
+
+
+ def send_getAccounts(self, refresh):
+ self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid)
+ args = getAccounts_args()
+ args.refresh = refresh
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getAccounts(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAccounts_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result");
+
+
+ def getAccountTypes(self,):
+ self.send_getAccountTypes()
+ return self.recv_getAccountTypes()
+
+
+ def send_getAccountTypes(self,):
+ self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid)
+ args = getAccountTypes_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getAccountTypes(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAccountTypes_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result");
+
+
+ def updateAccount(self, plugin, account, password, options):
+ """
+ Parameters:
+ - plugin
+ - account
+ - password
+ - options
+ """
+ self.send_updateAccount(plugin, account, password, options)
+ self.recv_updateAccount()
+
+
+ def send_updateAccount(self, plugin, account, password, options):
+ self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid)
+ args = updateAccount_args()
+ args.plugin = plugin
+ args.account = account
+ args.password = password
+ args.options = options
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_updateAccount(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = updateAccount_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def removeAccount(self, plugin, account):
+ """
+ Parameters:
+ - plugin
+ - account
+ """
+ self.send_removeAccount(plugin, account)
+ self.recv_removeAccount()
+
+
+ def send_removeAccount(self, plugin, account):
+ self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid)
+ args = removeAccount_args()
+ args.plugin = plugin
+ args.account = account
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_removeAccount(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = removeAccount_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+ def login(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ self.send_login(username, password)
+ return self.recv_login()
+
+
+ def send_login(self, username, password):
+ self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid)
+ args = login_args()
+ args.username = username
+ args.password = password
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_login(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = login_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result");
+
+
+ def getUserData(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ self.send_getUserData(username, password)
+ return self.recv_getUserData()
+
+
+ def send_getUserData(self, username, password):
+ self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid)
+ args = getUserData_args()
+ args.username = username
+ args.password = password
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getUserData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getUserData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result");
+
+
+ def getAllUserData(self,):
+ self.send_getAllUserData()
+ return self.recv_getAllUserData()
+
+
+ def send_getAllUserData(self,):
+ self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid)
+ args = getAllUserData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getAllUserData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllUserData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result");
+
+
+ def getServices(self,):
+ self.send_getServices()
+ return self.recv_getServices()
+
+
+ def send_getServices(self,):
+ self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid)
+ args = getServices_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getServices(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getServices_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result");
+
+
+ def hasService(self, plugin, func):
+ """
+ Parameters:
+ - plugin
+ - func
+ """
+ self.send_hasService(plugin, func)
+ return self.recv_hasService()
+
+
+ def send_hasService(self, plugin, func):
+ self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid)
+ args = hasService_args()
+ args.plugin = plugin
+ args.func = func
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_hasService(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = hasService_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result");
+
+
+ def call(self, info):
+ """
+ Parameters:
+ - info
+ """
+ self.send_call(info)
+ return self.recv_call()
+
+
+ def send_call(self, info):
+ self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid)
+ args = call_args()
+ args.info = info
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_call(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = call_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.ex is not None:
+ raise result.ex
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result");
+
+
+ def getAllInfo(self,):
+ self.send_getAllInfo()
+ return self.recv_getAllInfo()
+
+
+ def send_getAllInfo(self,):
+ self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid)
+ args = getAllInfo_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getAllInfo(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result");
+
+
+ def getInfoByPlugin(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ self.send_getInfoByPlugin(plugin)
+ return self.recv_getInfoByPlugin()
+
+
+ def send_getInfoByPlugin(self, plugin):
+ self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid)
+ args = getInfoByPlugin_args()
+ args.plugin = plugin
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getInfoByPlugin(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getInfoByPlugin_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result");
+
+
+ def isCaptchaWaiting(self,):
+ self.send_isCaptchaWaiting()
+ return self.recv_isCaptchaWaiting()
+
+
+ def send_isCaptchaWaiting(self,):
+ self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid)
+ args = isCaptchaWaiting_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_isCaptchaWaiting(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isCaptchaWaiting_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result");
+
+
+ def getCaptchaTask(self, exclusive):
+ """
+ Parameters:
+ - exclusive
+ """
+ self.send_getCaptchaTask(exclusive)
+ return self.recv_getCaptchaTask()
+
+
+ def send_getCaptchaTask(self, exclusive):
+ self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid)
+ args = getCaptchaTask_args()
+ args.exclusive = exclusive
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getCaptchaTask(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCaptchaTask_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result");
+
+
+ def getCaptchaTaskStatus(self, tid):
+ """
+ Parameters:
+ - tid
+ """
+ self.send_getCaptchaTaskStatus(tid)
+ return self.recv_getCaptchaTaskStatus()
+
+
+ def send_getCaptchaTaskStatus(self, tid):
+ self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid)
+ args = getCaptchaTaskStatus_args()
+ args.tid = tid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_getCaptchaTaskStatus(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCaptchaTaskStatus_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result");
+
+
+ def setCaptchaResult(self, tid, result):
+ """
+ Parameters:
+ - tid
+ - result
+ """
+ self.send_setCaptchaResult(tid, result)
+ self.recv_setCaptchaResult()
+
+
+ def send_setCaptchaResult(self, tid, result):
+ self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid)
+ args = setCaptchaResult_args()
+ args.tid = tid
+ args.result = result
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+
+ def recv_setCaptchaResult(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setCaptchaResult_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+class Processor(Iface, TProcessor):
+
+ def __init__(self, handler):
+ self._handler = handler
+ self._processMap = {}
+ self._processMap['getConfigValue'] = Processor.process_getConfigValue
+ self._processMap['setConfigValue'] = Processor.process_setConfigValue
+ self._processMap['getConfig'] = Processor.process_getConfig
+ self._processMap['getPluginConfig'] = Processor.process_getPluginConfig
+ self._processMap['pauseServer'] = Processor.process_pauseServer
+ self._processMap['unpauseServer'] = Processor.process_unpauseServer
+ self._processMap['togglePause'] = Processor.process_togglePause
+ self._processMap['statusServer'] = Processor.process_statusServer
+ self._processMap['freeSpace'] = Processor.process_freeSpace
+ self._processMap['getServerVersion'] = Processor.process_getServerVersion
+ self._processMap['kill'] = Processor.process_kill
+ self._processMap['restart'] = Processor.process_restart
+ self._processMap['getLog'] = Processor.process_getLog
+ self._processMap['isTimeDownload'] = Processor.process_isTimeDownload
+ self._processMap['isTimeReconnect'] = Processor.process_isTimeReconnect
+ self._processMap['toggleReconnect'] = Processor.process_toggleReconnect
+ self._processMap['generatePackages'] = Processor.process_generatePackages
+ self._processMap['checkURLs'] = Processor.process_checkURLs
+ self._processMap['parseURLs'] = Processor.process_parseURLs
+ self._processMap['checkOnlineStatus'] = Processor.process_checkOnlineStatus
+ self._processMap['checkOnlineStatusContainer'] = Processor.process_checkOnlineStatusContainer
+ self._processMap['pollResults'] = Processor.process_pollResults
+ self._processMap['statusDownloads'] = Processor.process_statusDownloads
+ self._processMap['getPackageData'] = Processor.process_getPackageData
+ self._processMap['getPackageInfo'] = Processor.process_getPackageInfo
+ self._processMap['getFileData'] = Processor.process_getFileData
+ self._processMap['getQueue'] = Processor.process_getQueue
+ self._processMap['getCollector'] = Processor.process_getCollector
+ self._processMap['getQueueData'] = Processor.process_getQueueData
+ self._processMap['getCollectorData'] = Processor.process_getCollectorData
+ self._processMap['getPackageOrder'] = Processor.process_getPackageOrder
+ self._processMap['getFileOrder'] = Processor.process_getFileOrder
+ self._processMap['generateAndAddPackages'] = Processor.process_generateAndAddPackages
+ self._processMap['addPackage'] = Processor.process_addPackage
+ self._processMap['addFiles'] = Processor.process_addFiles
+ self._processMap['uploadContainer'] = Processor.process_uploadContainer
+ self._processMap['deleteFiles'] = Processor.process_deleteFiles
+ self._processMap['deletePackages'] = Processor.process_deletePackages
+ self._processMap['pushToQueue'] = Processor.process_pushToQueue
+ self._processMap['pullFromQueue'] = Processor.process_pullFromQueue
+ self._processMap['restartPackage'] = Processor.process_restartPackage
+ self._processMap['restartFile'] = Processor.process_restartFile
+ self._processMap['recheckPackage'] = Processor.process_recheckPackage
+ self._processMap['stopAllDownloads'] = Processor.process_stopAllDownloads
+ self._processMap['stopDownloads'] = Processor.process_stopDownloads
+ self._processMap['setPackageName'] = Processor.process_setPackageName
+ self._processMap['movePackage'] = Processor.process_movePackage
+ self._processMap['moveFiles'] = Processor.process_moveFiles
+ self._processMap['orderPackage'] = Processor.process_orderPackage
+ self._processMap['orderFile'] = Processor.process_orderFile
+ self._processMap['setPackageData'] = Processor.process_setPackageData
+ self._processMap['deleteFinished'] = Processor.process_deleteFinished
+ self._processMap['restartFailed'] = Processor.process_restartFailed
+ self._processMap['getEvents'] = Processor.process_getEvents
+ self._processMap['getAccounts'] = Processor.process_getAccounts
+ self._processMap['getAccountTypes'] = Processor.process_getAccountTypes
+ self._processMap['updateAccount'] = Processor.process_updateAccount
+ self._processMap['removeAccount'] = Processor.process_removeAccount
+ self._processMap['login'] = Processor.process_login
+ self._processMap['getUserData'] = Processor.process_getUserData
+ self._processMap['getAllUserData'] = Processor.process_getAllUserData
+ self._processMap['getServices'] = Processor.process_getServices
+ self._processMap['hasService'] = Processor.process_hasService
+ self._processMap['call'] = Processor.process_call
+ self._processMap['getAllInfo'] = Processor.process_getAllInfo
+ self._processMap['getInfoByPlugin'] = Processor.process_getInfoByPlugin
+ self._processMap['isCaptchaWaiting'] = Processor.process_isCaptchaWaiting
+ self._processMap['getCaptchaTask'] = Processor.process_getCaptchaTask
+ self._processMap['getCaptchaTaskStatus'] = Processor.process_getCaptchaTaskStatus
+ self._processMap['setCaptchaResult'] = Processor.process_setCaptchaResult
+
+
+ def process(self, iprot, oprot):
+ (name, type, seqid) = iprot.readMessageBegin()
+ if name not in self._processMap:
+ iprot.skip(TType.STRUCT)
+ iprot.readMessageEnd()
+ x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+ oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+ else:
+ self._processMap[name](self, seqid, iprot, oprot)
+ return True
+
+
+ def process_getConfigValue(self, seqid, iprot, oprot):
+ args = getConfigValue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getConfigValue_result()
+ result.success = self._handler.getConfigValue(args.category, args.option, args.section)
+ oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_setConfigValue(self, seqid, iprot, oprot):
+ args = setConfigValue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setConfigValue_result()
+ self._handler.setConfigValue(args.category, args.option, args.value, args.section)
+ oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getConfig(self, seqid, iprot, oprot):
+ args = getConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getConfig_result()
+ result.success = self._handler.getConfig()
+ oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getPluginConfig(self, seqid, iprot, oprot):
+ args = getPluginConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPluginConfig_result()
+ result.success = self._handler.getPluginConfig()
+ oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_pauseServer(self, seqid, iprot, oprot):
+ args = pauseServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pauseServer_result()
+ self._handler.pauseServer()
+ oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_unpauseServer(self, seqid, iprot, oprot):
+ args = unpauseServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = unpauseServer_result()
+ self._handler.unpauseServer()
+ oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_togglePause(self, seqid, iprot, oprot):
+ args = togglePause_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = togglePause_result()
+ result.success = self._handler.togglePause()
+ oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_statusServer(self, seqid, iprot, oprot):
+ args = statusServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = statusServer_result()
+ result.success = self._handler.statusServer()
+ oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_freeSpace(self, seqid, iprot, oprot):
+ args = freeSpace_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = freeSpace_result()
+ result.success = self._handler.freeSpace()
+ oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getServerVersion(self, seqid, iprot, oprot):
+ args = getServerVersion_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getServerVersion_result()
+ result.success = self._handler.getServerVersion()
+ oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_kill(self, seqid, iprot, oprot):
+ args = kill_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = kill_result()
+ self._handler.kill()
+ oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_restart(self, seqid, iprot, oprot):
+ args = restart_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restart_result()
+ self._handler.restart()
+ oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getLog(self, seqid, iprot, oprot):
+ args = getLog_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getLog_result()
+ result.success = self._handler.getLog(args.offset)
+ oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_isTimeDownload(self, seqid, iprot, oprot):
+ args = isTimeDownload_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isTimeDownload_result()
+ result.success = self._handler.isTimeDownload()
+ oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_isTimeReconnect(self, seqid, iprot, oprot):
+ args = isTimeReconnect_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isTimeReconnect_result()
+ result.success = self._handler.isTimeReconnect()
+ oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_toggleReconnect(self, seqid, iprot, oprot):
+ args = toggleReconnect_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = toggleReconnect_result()
+ result.success = self._handler.toggleReconnect()
+ oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_generatePackages(self, seqid, iprot, oprot):
+ args = generatePackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generatePackages_result()
+ result.success = self._handler.generatePackages(args.links)
+ oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_checkURLs(self, seqid, iprot, oprot):
+ args = checkURLs_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkURLs_result()
+ result.success = self._handler.checkURLs(args.urls)
+ oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_parseURLs(self, seqid, iprot, oprot):
+ args = parseURLs_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = parseURLs_result()
+ result.success = self._handler.parseURLs(args.html, args.url)
+ oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_checkOnlineStatus(self, seqid, iprot, oprot):
+ args = checkOnlineStatus_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkOnlineStatus_result()
+ result.success = self._handler.checkOnlineStatus(args.urls)
+ oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_checkOnlineStatusContainer(self, seqid, iprot, oprot):
+ args = checkOnlineStatusContainer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkOnlineStatusContainer_result()
+ result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data)
+ oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_pollResults(self, seqid, iprot, oprot):
+ args = pollResults_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pollResults_result()
+ result.success = self._handler.pollResults(args.rid)
+ oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_statusDownloads(self, seqid, iprot, oprot):
+ args = statusDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = statusDownloads_result()
+ result.success = self._handler.statusDownloads()
+ oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getPackageData(self, seqid, iprot, oprot):
+ args = getPackageData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageData_result()
+ try:
+ result.success = self._handler.getPackageData(args.pid)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getPackageInfo(self, seqid, iprot, oprot):
+ args = getPackageInfo_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageInfo_result()
+ try:
+ result.success = self._handler.getPackageInfo(args.pid)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getFileData(self, seqid, iprot, oprot):
+ args = getFileData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getFileData_result()
+ try:
+ result.success = self._handler.getFileData(args.fid)
+ except FileDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getQueue(self, seqid, iprot, oprot):
+ args = getQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getQueue_result()
+ result.success = self._handler.getQueue()
+ oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getCollector(self, seqid, iprot, oprot):
+ args = getCollector_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCollector_result()
+ result.success = self._handler.getCollector()
+ oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getQueueData(self, seqid, iprot, oprot):
+ args = getQueueData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getQueueData_result()
+ result.success = self._handler.getQueueData()
+ oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getCollectorData(self, seqid, iprot, oprot):
+ args = getCollectorData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCollectorData_result()
+ result.success = self._handler.getCollectorData()
+ oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getPackageOrder(self, seqid, iprot, oprot):
+ args = getPackageOrder_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageOrder_result()
+ result.success = self._handler.getPackageOrder(args.destination)
+ oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getFileOrder(self, seqid, iprot, oprot):
+ args = getFileOrder_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getFileOrder_result()
+ result.success = self._handler.getFileOrder(args.pid)
+ oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_generateAndAddPackages(self, seqid, iprot, oprot):
+ args = generateAndAddPackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generateAndAddPackages_result()
+ result.success = self._handler.generateAndAddPackages(args.links, args.dest)
+ oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_addPackage(self, seqid, iprot, oprot):
+ args = addPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addPackage_result()
+ result.success = self._handler.addPackage(args.name, args.links, args.dest)
+ oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_addFiles(self, seqid, iprot, oprot):
+ args = addFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addFiles_result()
+ self._handler.addFiles(args.pid, args.links)
+ oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_uploadContainer(self, seqid, iprot, oprot):
+ args = uploadContainer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = uploadContainer_result()
+ self._handler.uploadContainer(args.filename, args.data)
+ oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_deleteFiles(self, seqid, iprot, oprot):
+ args = deleteFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deleteFiles_result()
+ self._handler.deleteFiles(args.fids)
+ oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_deletePackages(self, seqid, iprot, oprot):
+ args = deletePackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deletePackages_result()
+ self._handler.deletePackages(args.pids)
+ oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_pushToQueue(self, seqid, iprot, oprot):
+ args = pushToQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pushToQueue_result()
+ self._handler.pushToQueue(args.pid)
+ oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_pullFromQueue(self, seqid, iprot, oprot):
+ args = pullFromQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pullFromQueue_result()
+ self._handler.pullFromQueue(args.pid)
+ oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_restartPackage(self, seqid, iprot, oprot):
+ args = restartPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartPackage_result()
+ self._handler.restartPackage(args.pid)
+ oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_restartFile(self, seqid, iprot, oprot):
+ args = restartFile_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartFile_result()
+ self._handler.restartFile(args.fid)
+ oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_recheckPackage(self, seqid, iprot, oprot):
+ args = recheckPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = recheckPackage_result()
+ self._handler.recheckPackage(args.pid)
+ oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_stopAllDownloads(self, seqid, iprot, oprot):
+ args = stopAllDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = stopAllDownloads_result()
+ self._handler.stopAllDownloads()
+ oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_stopDownloads(self, seqid, iprot, oprot):
+ args = stopDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = stopDownloads_result()
+ self._handler.stopDownloads(args.fids)
+ oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_setPackageName(self, seqid, iprot, oprot):
+ args = setPackageName_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPackageName_result()
+ self._handler.setPackageName(args.pid, args.name)
+ oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_movePackage(self, seqid, iprot, oprot):
+ args = movePackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = movePackage_result()
+ self._handler.movePackage(args.destination, args.pid)
+ oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_moveFiles(self, seqid, iprot, oprot):
+ args = moveFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = moveFiles_result()
+ self._handler.moveFiles(args.fids, args.pid)
+ oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_orderPackage(self, seqid, iprot, oprot):
+ args = orderPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = orderPackage_result()
+ self._handler.orderPackage(args.pid, args.position)
+ oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_orderFile(self, seqid, iprot, oprot):
+ args = orderFile_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = orderFile_result()
+ self._handler.orderFile(args.fid, args.position)
+ oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_setPackageData(self, seqid, iprot, oprot):
+ args = setPackageData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPackageData_result()
+ try:
+ self._handler.setPackageData(args.pid, args.data)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_deleteFinished(self, seqid, iprot, oprot):
+ args = deleteFinished_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deleteFinished_result()
+ result.success = self._handler.deleteFinished()
+ oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_restartFailed(self, seqid, iprot, oprot):
+ args = restartFailed_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartFailed_result()
+ self._handler.restartFailed()
+ oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getEvents(self, seqid, iprot, oprot):
+ args = getEvents_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getEvents_result()
+ result.success = self._handler.getEvents(args.uuid)
+ oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getAccounts(self, seqid, iprot, oprot):
+ args = getAccounts_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAccounts_result()
+ result.success = self._handler.getAccounts(args.refresh)
+ oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getAccountTypes(self, seqid, iprot, oprot):
+ args = getAccountTypes_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAccountTypes_result()
+ result.success = self._handler.getAccountTypes()
+ oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_updateAccount(self, seqid, iprot, oprot):
+ args = updateAccount_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = updateAccount_result()
+ self._handler.updateAccount(args.plugin, args.account, args.password, args.options)
+ oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_removeAccount(self, seqid, iprot, oprot):
+ args = removeAccount_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = removeAccount_result()
+ self._handler.removeAccount(args.plugin, args.account)
+ oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_login(self, seqid, iprot, oprot):
+ args = login_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = login_result()
+ result.success = self._handler.login(args.username, args.password)
+ oprot.writeMessageBegin("login", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getUserData(self, seqid, iprot, oprot):
+ args = getUserData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getUserData_result()
+ result.success = self._handler.getUserData(args.username, args.password)
+ oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getAllUserData(self, seqid, iprot, oprot):
+ args = getAllUserData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAllUserData_result()
+ result.success = self._handler.getAllUserData()
+ oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getServices(self, seqid, iprot, oprot):
+ args = getServices_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getServices_result()
+ result.success = self._handler.getServices()
+ oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_hasService(self, seqid, iprot, oprot):
+ args = hasService_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = hasService_result()
+ result.success = self._handler.hasService(args.plugin, args.func)
+ oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_call(self, seqid, iprot, oprot):
+ args = call_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = call_result()
+ try:
+ result.success = self._handler.call(args.info)
+ except ServiceDoesNotExists, ex:
+ result.ex = ex
+ except ServiceException, e:
+ result.e = e
+ oprot.writeMessageBegin("call", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getAllInfo(self, seqid, iprot, oprot):
+ args = getAllInfo_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAllInfo_result()
+ result.success = self._handler.getAllInfo()
+ oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getInfoByPlugin(self, seqid, iprot, oprot):
+ args = getInfoByPlugin_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getInfoByPlugin_result()
+ result.success = self._handler.getInfoByPlugin(args.plugin)
+ oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_isCaptchaWaiting(self, seqid, iprot, oprot):
+ args = isCaptchaWaiting_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isCaptchaWaiting_result()
+ result.success = self._handler.isCaptchaWaiting()
+ oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getCaptchaTask(self, seqid, iprot, oprot):
+ args = getCaptchaTask_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCaptchaTask_result()
+ result.success = self._handler.getCaptchaTask(args.exclusive)
+ oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_getCaptchaTaskStatus(self, seqid, iprot, oprot):
+ args = getCaptchaTaskStatus_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCaptchaTaskStatus_result()
+ result.success = self._handler.getCaptchaTaskStatus(args.tid)
+ oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+ def process_setCaptchaResult(self, seqid, iprot, oprot):
+ args = setCaptchaResult_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setCaptchaResult_result()
+ self._handler.setCaptchaResult(args.tid, args.result)
+ oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+
+class getConfigValue_args(TBase):
+ """
+ Attributes:
+ - category
+ - option
+ - section
+ """
+
+ __slots__ = [
+ 'category',
+ 'option',
+ 'section',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'category', None, None,), #: 1
+ (2, TType.STRING, 'option', None, None,), #: 2
+ (3, TType.STRING, 'section', None, None,), #: 3
+ )
+
+
+ def __init__(self, category=None, option=None, section=None,):
+ self.category = category
+ self.option = option
+ self.section = section
+
+
+class getConfigValue_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class setConfigValue_args(TBase):
+ """
+ Attributes:
+ - category
+ - option
+ - value
+ - section
+ """
+
+ __slots__ = [
+ 'category',
+ 'option',
+ 'value',
+ 'section',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'category', None, None,), #: 1
+ (2, TType.STRING, 'option', None, None,), #: 2
+ (3, TType.STRING, 'value', None, None,), #: 3
+ (4, TType.STRING, 'section', None, None,), #: 4
+ )
+
+
+ def __init__(self, category=None, option=None, value=None, section=None,):
+ self.category = category
+ self.option = option
+ self.value = value
+ self.section = section
+
+
+class setConfigValue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getConfig_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getConfig_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPluginConfig_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getPluginConfig_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class pauseServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pauseServer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class unpauseServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class unpauseServer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class togglePause_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class togglePause_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class statusServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class statusServer_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class freeSpace_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class freeSpace_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.I64, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getServerVersion_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getServerVersion_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class kill_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class kill_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restart_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restart_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getLog_args(TBase):
+ """
+ Attributes:
+ - offset
+ """
+
+ __slots__ = [
+ 'offset',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'offset', None, None,), #: 1
+ )
+
+
+ def __init__(self, offset=None,):
+ self.offset = offset
+
+
+class getLog_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRING, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isTimeDownload_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isTimeDownload_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isTimeReconnect_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isTimeReconnect_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class toggleReconnect_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class toggleReconnect_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class generatePackages_args(TBase):
+ """
+ Attributes:
+ - links
+ """
+
+ __slots__ = [
+ 'links',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'links', (TType.STRING, None), None,), #: 1
+ )
+
+
+ def __init__(self, links=None,):
+ self.links = links
+
+
+class generatePackages_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkURLs_args(TBase):
+ """
+ Attributes:
+ - urls
+ """
+
+ __slots__ = [
+ 'urls',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1
+ )
+
+
+ def __init__(self, urls=None,):
+ self.urls = urls
+
+
+class checkURLs_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class parseURLs_args(TBase):
+ """
+ Attributes:
+ - html
+ - url
+ """
+
+ __slots__ = [
+ 'html',
+ 'url',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'html', None, None,), #: 1
+ (2, TType.STRING, 'url', None, None,), #: 2
+ )
+
+
+ def __init__(self, html=None, url=None,):
+ self.html = html
+ self.url = url
+
+
+class parseURLs_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkOnlineStatus_args(TBase):
+ """
+ Attributes:
+ - urls
+ """
+
+ __slots__ = [
+ 'urls',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1
+ )
+
+
+ def __init__(self, urls=None,):
+ self.urls = urls
+
+
+class checkOnlineStatus_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkOnlineStatusContainer_args(TBase):
+ """
+ Attributes:
+ - urls
+ - filename
+ - data
+ """
+
+ __slots__ = [
+ 'urls',
+ 'filename',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1
+ (2, TType.STRING, 'filename', None, None,), #: 2
+ (3, TType.STRING, 'data', None, None,), #: 3
+ )
+
+
+ def __init__(self, urls=None, filename=None, data=None,):
+ self.urls = urls
+ self.filename = filename
+ self.data = data
+
+
+class checkOnlineStatusContainer_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class pollResults_args(TBase):
+ """
+ Attributes:
+ - rid
+ """
+
+ __slots__ = [
+ 'rid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'rid', None, None,), #: 1
+ )
+
+
+ def __init__(self, rid=None,):
+ self.rid = rid
+
+
+class pollResults_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class statusDownloads_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class statusDownloads_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (DownloadInfo, DownloadInfo.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPackageData_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getPackageData_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), #: 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1
+ )
+
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getPackageInfo_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getPackageInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), #: 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1
+ )
+
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getFileData_args(TBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ )
+
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+
+class getFileData_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None,), #: 0
+ (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None,), #: 1
+ )
+
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getQueue_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getQueue_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCollector_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getCollector_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getQueueData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getQueueData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCollectorData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getCollectorData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPackageOrder_args(TBase):
+ """
+ Attributes:
+ - destination
+ """
+
+ __slots__ = [
+ 'destination',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'destination', None, None,), #: 1
+ )
+
+
+ def __init__(self, destination=None,):
+ self.destination = destination
+
+
+class getPackageOrder_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getFileOrder_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getFileOrder_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class generateAndAddPackages_args(TBase):
+ """
+ Attributes:
+ - links
+ - dest
+ """
+
+ __slots__ = [
+ 'links',
+ 'dest',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'links', (TType.STRING, None), None,), #: 1
+ (2, TType.I32, 'dest', None, None,), #: 2
+ )
+
+
+ def __init__(self, links=None, dest=None,):
+ self.links = links
+ self.dest = dest
+
+
+class generateAndAddPackages_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.I32, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class addPackage_args(TBase):
+ """
+ Attributes:
+ - name
+ - links
+ - dest
+ """
+
+ __slots__ = [
+ 'name',
+ 'links',
+ 'dest',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'name', None, None,), #: 1
+ (2, TType.LIST, 'links', (TType.STRING, None), None,), #: 2
+ (3, TType.I32, 'dest', None, None,), #: 3
+ )
+
+
+ def __init__(self, name=None, links=None, dest=None,):
+ self.name = name
+ self.links = links
+ self.dest = dest
+
+
+class addPackage_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.I32, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class addFiles_args(TBase):
+ """
+ Attributes:
+ - pid
+ - links
+ """
+
+ __slots__ = [
+ 'pid',
+ 'links',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ (2, TType.LIST, 'links', (TType.STRING, None), None,), #: 2
+ )
+
+
+ def __init__(self, pid=None, links=None,):
+ self.pid = pid
+ self.links = links
+
+
+class addFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class uploadContainer_args(TBase):
+ """
+ Attributes:
+ - filename
+ - data
+ """
+
+ __slots__ = [
+ 'filename',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'filename', None, None,), #: 1
+ (2, TType.STRING, 'data', None, None,), #: 2
+ )
+
+
+ def __init__(self, filename=None, data=None,):
+ self.filename = filename
+ self.data = data
+
+
+class uploadContainer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deleteFiles_args(TBase):
+ """
+ Attributes:
+ - fids
+ """
+
+ __slots__ = [
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1
+ )
+
+
+ def __init__(self, fids=None,):
+ self.fids = fids
+
+
+class deleteFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deletePackages_args(TBase):
+ """
+ Attributes:
+ - pids
+ """
+
+ __slots__ = [
+ 'pids',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'pids', (TType.I32, None), None,), #: 1
+ )
+
+
+ def __init__(self, pids=None,):
+ self.pids = pids
+
+
+class deletePackages_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pushToQueue_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class pushToQueue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pullFromQueue_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class pullFromQueue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class restartPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartFile_args(TBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ )
+
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+
+class restartFile_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class recheckPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class recheckPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopAllDownloads_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopAllDownloads_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopDownloads_args(TBase):
+ """
+ Attributes:
+ - fids
+ """
+
+ __slots__ = [
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1
+ )
+
+
+ def __init__(self, fids=None,):
+ self.fids = fids
+
+
+class stopDownloads_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setPackageName_args(TBase):
+ """
+ Attributes:
+ - pid
+ - name
+ """
+
+ __slots__ = [
+ 'pid',
+ 'name',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ (2, TType.STRING, 'name', None, None,), #: 2
+ )
+
+
+ def __init__(self, pid=None, name=None,):
+ self.pid = pid
+ self.name = name
+
+
+class setPackageName_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class movePackage_args(TBase):
+ """
+ Attributes:
+ - destination
+ - pid
+ """
+
+ __slots__ = [
+ 'destination',
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'destination', None, None,), #: 1
+ (2, TType.I32, 'pid', None, None,), #: 2
+ )
+
+
+ def __init__(self, destination=None, pid=None,):
+ self.destination = destination
+ self.pid = pid
+
+
+class movePackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class moveFiles_args(TBase):
+ """
+ Attributes:
+ - fids
+ - pid
+ """
+
+ __slots__ = [
+ 'fids',
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1
+ (2, TType.I32, 'pid', None, None,), #: 2
+ )
+
+
+ def __init__(self, fids=None, pid=None,):
+ self.fids = fids
+ self.pid = pid
+
+
+class moveFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class orderPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ - position
+ """
+
+ __slots__ = [
+ 'pid',
+ 'position',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ (2, TType.I16, 'position', None, None,), #: 2
+ )
+
+
+ def __init__(self, pid=None, position=None,):
+ self.pid = pid
+ self.position = position
+
+
+class orderPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class orderFile_args(TBase):
+ """
+ Attributes:
+ - fid
+ - position
+ """
+
+ __slots__ = [
+ 'fid',
+ 'position',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ (2, TType.I16, 'position', None, None,), #: 2
+ )
+
+
+ def __init__(self, fid=None, position=None,):
+ self.fid = fid
+ self.position = position
+
+
+class orderFile_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setPackageData_args(TBase):
+ """
+ Attributes:
+ - pid
+ - data
+ """
+
+ __slots__ = [
+ 'pid',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ (2, TType.MAP, 'data', (TType.STRING, None, TType.STRING, None), None,), #: 2
+ )
+
+
+ def __init__(self, pid=None, data=None,):
+ self.pid = pid
+ self.data = data
+
+
+class setPackageData_result(TBase):
+ """
+ Attributes:
+ - e
+ """
+
+ __slots__ = [
+ 'e',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1
+ )
+
+
+ def __init__(self, e=None,):
+ self.e = e
+
+
+class deleteFinished_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deleteFinished_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.I32, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class restartFailed_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartFailed_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getEvents_args(TBase):
+ """
+ Attributes:
+ - uuid
+ """
+
+ __slots__ = [
+ 'uuid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'uuid', None, None,), #: 1
+ )
+
+
+ def __init__(self, uuid=None,):
+ self.uuid = uuid
+
+
+class getEvents_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (EventInfo, EventInfo.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAccounts_args(TBase):
+ """
+ Attributes:
+ - refresh
+ """
+
+ __slots__ = [
+ 'refresh',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.BOOL, 'refresh', None, None,), #: 1
+ )
+
+
+ def __init__(self, refresh=None,):
+ self.refresh = refresh
+
+
+class getAccounts_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (AccountInfo, AccountInfo.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAccountTypes_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAccountTypes_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRING, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class updateAccount_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - account
+ - password
+ - options
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'account',
+ 'password',
+ 'options',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ (2, TType.STRING, 'account', None, None,), #: 2
+ (3, TType.STRING, 'password', None, None,), #: 3
+ (4, TType.MAP, 'options', (TType.STRING, None, TType.STRING, None), None,), #: 4
+ )
+
+
+ def __init__(self, plugin=None, account=None, password=None, options=None,):
+ self.plugin = plugin
+ self.account = account
+ self.password = password
+ self.options = options
+
+
+class updateAccount_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class removeAccount_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - account
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'account',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ (2, TType.STRING, 'account', None, None,), #: 2
+ )
+
+
+ def __init__(self, plugin=None, account=None,):
+ self.plugin = plugin
+ self.account = account
+
+
+class removeAccount_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class login_args(TBase):
+ """
+ Attributes:
+ - username
+ - password
+ """
+
+ __slots__ = [
+ 'username',
+ 'password',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'username', None, None,), #: 1
+ (2, TType.STRING, 'password', None, None,), #: 2
+ )
+
+
+ def __init__(self, username=None, password=None,):
+ self.username = username
+ self.password = password
+
+
+class login_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getUserData_args(TBase):
+ """
+ Attributes:
+ - username
+ - password
+ """
+
+ __slots__ = [
+ 'username',
+ 'password',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'username', None, None,), #: 1
+ (2, TType.STRING, 'password', None, None,), #: 2
+ )
+
+
+ def __init__(self, username=None, password=None,):
+ self.username = username
+ self.password = password
+
+
+class getUserData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAllUserData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAllUserData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (UserData, UserData.thrift_spec)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getServices_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getServices_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class hasService_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ (2, TType.STRING, 'func', None, None,), #: 2
+ )
+
+
+ def __init__(self, plugin=None, func=None,):
+ self.plugin = plugin
+ self.func = func
+
+
+class hasService_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class call_args(TBase):
+ """
+ Attributes:
+ - info
+ """
+
+ __slots__ = [
+ 'info',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None,), #: 1
+ )
+
+
+ def __init__(self, info=None,):
+ self.info = info
+
+
+class call_result(TBase):
+ """
+ Attributes:
+ - success
+ - ex
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'ex',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), #: 0
+ (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None,), #: 1
+ (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None,), #: 2
+ )
+
+
+ def __init__(self, success=None, ex=None, e=None,):
+ self.success = success
+ self.ex = ex
+ self.e = e
+
+
+class getAllInfo_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAllInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getInfoByPlugin_args(TBase):
+ """
+ Attributes:
+ - plugin
+ """
+
+ __slots__ = [
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ )
+
+
+ def __init__(self, plugin=None,):
+ self.plugin = plugin
+
+
+class getInfoByPlugin_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRING, None), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isCaptchaWaiting_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isCaptchaWaiting_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCaptchaTask_args(TBase):
+ """
+ Attributes:
+ - exclusive
+ """
+
+ __slots__ = [
+ 'exclusive',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.BOOL, 'exclusive', None, None,), #: 1
+ )
+
+
+ def __init__(self, exclusive=None,):
+ self.exclusive = exclusive
+
+
+class getCaptchaTask_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCaptchaTaskStatus_args(TBase):
+ """
+ Attributes:
+ - tid
+ """
+
+ __slots__ = [
+ 'tid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'tid', None, None,), #: 1
+ )
+
+
+ def __init__(self, tid=None,):
+ self.tid = tid
+
+
+class getCaptchaTaskStatus_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), #: 0
+ )
+
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class setCaptchaResult_args(TBase):
+ """
+ Attributes:
+ - tid
+ - result
+ """
+
+ __slots__ = [
+ 'tid',
+ 'result',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'tid', None, None,), #: 1
+ (2, TType.STRING, 'result', None, None,), #: 2
+ )
+
+
+ def __init__(self, tid=None, result=None,):
+ self.tid = tid
+ self.result = result
+
+
+class setCaptchaResult_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py
new file mode 100644
index 000000000..9a0fb88bf
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+__all__ = ['ttypes', 'constants', 'Pyload']
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/constants.py b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
new file mode 100644
index 000000000..e0a811c8a
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
@@ -0,0 +1,11 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+from ttypes import *
+
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
new file mode 100644
index 000000000..8abd775a9
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -0,0 +1,860 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+
+from thrift.protocol.TBase import TBase, TExceptionBase
+
+
+class DownloadStatus(TBase):
+ Finished = 0
+ Offline = 1
+ Online = 2
+ Queued = 3
+ Skipped = 4
+ Waiting = 5
+ TempOffline = 6
+ Starting = 7
+ Failed = 8
+ Aborted = 9
+ Decrypting = 10
+ Custom = 11
+ Downloading = 12
+ Processing = 13
+ Unknown = 14
+
+ _VALUES_TO_NAMES = {
+ 0: "Finished",
+ 1: "Offline",
+ 2: "Online",
+ 3: "Queued",
+ 4: "Skipped",
+ 5: "Waiting",
+ 6: "TempOffline",
+ 7: "Starting",
+ 8: "Failed",
+ 9: "Aborted",
+ 10: "Decrypting",
+ 11: "Custom",
+ 12: "Downloading",
+ 13: "Processing",
+ 14: "Unknown",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Finished": 0,
+ "Offline": 1,
+ "Online": 2,
+ "Queued": 3,
+ "Skipped": 4,
+ "Waiting": 5,
+ "TempOffline": 6,
+ "Starting": 7,
+ "Failed": 8,
+ "Aborted": 9,
+ "Decrypting": 10,
+ "Custom": 11,
+ "Downloading": 12,
+ "Processing": 13,
+ "Unknown": 14,
+ }
+
+
+class Destination(TBase):
+ Collector = 0
+ Queue = 1
+
+ _VALUES_TO_NAMES = {
+ 0: "Collector",
+ 1: "Queue",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Collector": 0,
+ "Queue": 1,
+ }
+
+
+class ElementType(TBase):
+ Package = 0
+ File = 1
+
+ _VALUES_TO_NAMES = {
+ 0: "Package",
+ 1: "File",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Package": 0,
+ "File": 1,
+ }
+
+
+class Input(TBase):
+ NONE = 0
+ TEXT = 1
+ TEXTBOX = 2
+ PASSWORD = 3
+ BOOL = 4
+ CLICK = 5
+ CHOICE = 6
+ MULTIPLE = 7
+ LIST = 8
+ TABLE = 9
+
+ _VALUES_TO_NAMES = {
+ 0: "NONE",
+ 1: "TEXT",
+ 2: "TEXTBOX",
+ 3: "PASSWORD",
+ 4: "BOOL",
+ 5: "CLICK",
+ 6: "CHOICE",
+ 7: "MULTIPLE",
+ 8: "LIST",
+ 9: "TABLE",
+ }
+
+ _NAMES_TO_VALUES = {
+ "NONE": 0,
+ "TEXT": 1,
+ "TEXTBOX": 2,
+ "PASSWORD": 3,
+ "BOOL": 4,
+ "CLICK": 5,
+ "CHOICE": 6,
+ "MULTIPLE": 7,
+ "LIST": 8,
+ "TABLE": 9,
+ }
+
+
+class Output(TBase):
+ CAPTCHA = 1
+ QUESTION = 2
+ NOTIFICATION = 4
+
+ _VALUES_TO_NAMES = {
+ 1: "CAPTCHA",
+ 2: "QUESTION",
+ 4: "NOTIFICATION",
+ }
+
+ _NAMES_TO_VALUES = {
+ "CAPTCHA": 1,
+ "QUESTION": 2,
+ "NOTIFICATION": 4,
+ }
+
+
+class DownloadInfo(TBase):
+ """
+ Attributes:
+ - fid
+ - name
+ - speed
+ - eta
+ - format_eta
+ - bleft
+ - size
+ - format_size
+ - percent
+ - status
+ - statusmsg
+ - format_wait
+ - wait_until
+ - packageID
+ - packageName
+ - plugin
+ """
+
+ __slots__ = [
+ 'fid',
+ 'name',
+ 'speed',
+ 'eta',
+ 'format_eta',
+ 'bleft',
+ 'size',
+ 'format_size',
+ 'percent',
+ 'status',
+ 'statusmsg',
+ 'format_wait',
+ 'wait_until',
+ 'packageID',
+ 'packageName',
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ (2, TType.STRING, 'name', None, None,), #: 2
+ (3, TType.I64, 'speed', None, None,), #: 3
+ (4, TType.I32, 'eta', None, None,), #: 4
+ (5, TType.STRING, 'format_eta', None, None,), #: 5
+ (6, TType.I64, 'bleft', None, None,), #: 6
+ (7, TType.I64, 'size', None, None,), #: 7
+ (8, TType.STRING, 'format_size', None, None,), #: 8
+ (9, TType.BYTE, 'percent', None, None,), #: 9
+ (10, TType.I32, 'status', None, None,), #: 10
+ (11, TType.STRING, 'statusmsg', None, None,), #: 11
+ (12, TType.STRING, 'format_wait', None, None,), #: 12
+ (13, TType.I64, 'wait_until', None, None,), #: 13
+ (14, TType.I32, 'packageID', None, None,), #: 14
+ (15, TType.STRING, 'packageName', None, None,), #: 15
+ (16, TType.STRING, 'plugin', None, None,), #: 16
+ )
+
+
+ def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,):
+ self.fid = fid
+ self.name = name
+ self.speed = speed
+ self.eta = eta
+ self.format_eta = format_eta
+ self.bleft = bleft
+ self.size = size
+ self.format_size = format_size
+ self.percent = percent
+ self.status = status
+ self.statusmsg = statusmsg
+ self.format_wait = format_wait
+ self.wait_until = wait_until
+ self.packageID = packageID
+ self.packageName = packageName
+ self.plugin = plugin
+
+
+class ServerStatus(TBase):
+ """
+ Attributes:
+ - pause
+ - active
+ - queue
+ - total
+ - speed
+ - download
+ - reconnect
+ """
+
+ __slots__ = [
+ 'pause',
+ 'active',
+ 'queue',
+ 'total',
+ 'speed',
+ 'download',
+ 'reconnect',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.BOOL, 'pause', None, None,), #: 1
+ (2, TType.I16, 'active', None, None,), #: 2
+ (3, TType.I16, 'queue', None, None,), #: 3
+ (4, TType.I16, 'total', None, None,), #: 4
+ (5, TType.I64, 'speed', None, None,), #: 5
+ (6, TType.BOOL, 'download', None, None,), #: 6
+ (7, TType.BOOL, 'reconnect', None, None,), #: 7
+ )
+
+
+ def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,):
+ self.pause = pause
+ self.active = active
+ self.queue = queue
+ self.total = total
+ self.speed = speed
+ self.download = download
+ self.reconnect = reconnect
+
+
+class ConfigItem(TBase):
+ """
+ Attributes:
+ - name
+ - description
+ - value
+ - type
+ """
+
+ __slots__ = [
+ 'name',
+ 'description',
+ 'value',
+ 'type',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'name', None, None,), #: 1
+ (2, TType.STRING, 'description', None, None,), #: 2
+ (3, TType.STRING, 'value', None, None,), #: 3
+ (4, TType.STRING, 'type', None, None,), #: 4
+ )
+
+
+ def __init__(self, name=None, description=None, value=None, type=None,):
+ self.name = name
+ self.description = description
+ self.value = value
+ self.type = type
+
+
+class ConfigSection(TBase):
+ """
+ Attributes:
+ - name
+ - description
+ - items
+ - outline
+ """
+
+ __slots__ = [
+ 'name',
+ 'description',
+ 'items',
+ 'outline',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'name', None, None,), #: 1
+ (2, TType.STRING, 'description', None, None,), #: 2
+ (3, TType.LIST, 'items', (TType.STRUCT, (ConfigItem, ConfigItem.thrift_spec)), None,), #: 3
+ (4, TType.STRING, 'outline', None, None,), #: 4
+ )
+
+
+ def __init__(self, name=None, description=None, items=None, outline=None,):
+ self.name = name
+ self.description = description
+ self.items = items
+ self.outline = outline
+
+
+class FileData(TBase):
+ """
+ Attributes:
+ - fid
+ - url
+ - name
+ - plugin
+ - size
+ - format_size
+ - status
+ - statusmsg
+ - packageID
+ - error
+ - order
+ """
+
+ __slots__ = [
+ 'fid',
+ 'url',
+ 'name',
+ 'plugin',
+ 'size',
+ 'format_size',
+ 'status',
+ 'statusmsg',
+ 'packageID',
+ 'error',
+ 'order',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ (2, TType.STRING, 'url', None, None,), #: 2
+ (3, TType.STRING, 'name', None, None,), #: 3
+ (4, TType.STRING, 'plugin', None, None,), #: 4
+ (5, TType.I64, 'size', None, None,), #: 5
+ (6, TType.STRING, 'format_size', None, None,), #: 6
+ (7, TType.I32, 'status', None, None,), #: 7
+ (8, TType.STRING, 'statusmsg', None, None,), #: 8
+ (9, TType.I32, 'packageID', None, None,), #: 9
+ (10, TType.STRING, 'error', None, None,), #: 10
+ (11, TType.I16, 'order', None, None,), #: 11
+ )
+
+
+ def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,):
+ self.fid = fid
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.format_size = format_size
+ self.status = status
+ self.statusmsg = statusmsg
+ self.packageID = packageID
+ self.error = error
+ self.order = order
+
+
+class PackageData(TBase):
+ """
+ Attributes:
+ - pid
+ - name
+ - folder
+ - site
+ - password
+ - dest
+ - order
+ - linksdone
+ - sizedone
+ - sizetotal
+ - linkstotal
+ - links
+ - fids
+ """
+
+ __slots__ = [
+ 'pid',
+ 'name',
+ 'folder',
+ 'site',
+ 'password',
+ 'dest',
+ 'order',
+ 'linksdone',
+ 'sizedone',
+ 'sizetotal',
+ 'linkstotal',
+ 'links',
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ (2, TType.STRING, 'name', None, None,), #: 2
+ (3, TType.STRING, 'folder', None, None,), #: 3
+ (4, TType.STRING, 'site', None, None,), #: 4
+ (5, TType.STRING, 'password', None, None,), #: 5
+ (6, TType.I32, 'dest', None, None,), #: 6
+ (7, TType.I16, 'order', None, None,), #: 7
+ (8, TType.I16, 'linksdone', None, None,), #: 8
+ (9, TType.I64, 'sizedone', None, None,), #: 9
+ (10, TType.I64, 'sizetotal', None, None,), #: 10
+ (11, TType.I16, 'linkstotal', None, None,), #: 11
+ (12, TType.LIST, 'links', (TType.STRUCT, (FileData, FileData.thrift_spec)), None,), #: 12
+ (13, TType.LIST, 'fids', (TType.I32, None), None,), #: 13
+ )
+
+
+ def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,):
+ self.pid = pid
+ self.name = name
+ self.folder = folder
+ self.site = site
+ self.password = password
+ self.dest = dest
+ self.order = order
+ self.linksdone = linksdone
+ self.sizedone = sizedone
+ self.sizetotal = sizetotal
+ self.linkstotal = linkstotal
+ self.links = links
+ self.fids = fids
+
+
+class InteractionTask(TBase):
+ """
+ Attributes:
+ - iid
+ - input
+ - structure
+ - preset
+ - output
+ - data
+ - title
+ - description
+ - plugin
+ """
+
+ __slots__ = [
+ 'iid',
+ 'input',
+ 'structure',
+ 'preset',
+ 'output',
+ 'data',
+ 'title',
+ 'description',
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'iid', None, None,), #: 1
+ (2, TType.I32, 'input', None, None,), #: 2
+ (3, TType.LIST, 'structure', (TType.STRING, None), None,), #: 3
+ (4, TType.LIST, 'preset', (TType.STRING, None), None,), #: 4
+ (5, TType.I32, 'output', None, None,), #: 5
+ (6, TType.LIST, 'data', (TType.STRING, None), None,), #: 6
+ (7, TType.STRING, 'title', None, None,), #: 7
+ (8, TType.STRING, 'description', None, None,), #: 8
+ (9, TType.STRING, 'plugin', None, None,), #: 9
+ )
+
+
+ def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,):
+ self.iid = iid
+ self.input = input
+ self.structure = structure
+ self.preset = preset
+ self.output = output
+ self.data = data
+ self.title = title
+ self.description = description
+ self.plugin = plugin
+
+
+class CaptchaTask(TBase):
+ """
+ Attributes:
+ - tid
+ - data
+ - type
+ - resultType
+ """
+
+ __slots__ = [
+ 'tid',
+ 'data',
+ 'type',
+ 'resultType',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I16, 'tid', None, None,), #: 1
+ (2, TType.STRING, 'data', None, None,), #: 2
+ (3, TType.STRING, 'type', None, None,), #: 3
+ (4, TType.STRING, 'resultType', None, None,), #: 4
+ )
+
+
+ def __init__(self, tid=None, data=None, type=None, resultType=None,):
+ self.tid = tid
+ self.data = data
+ self.type = type
+ self.resultType = resultType
+
+
+class EventInfo(TBase):
+ """
+ Attributes:
+ - eventname
+ - id
+ - type
+ - destination
+ """
+
+ __slots__ = [
+ 'eventname',
+ 'id',
+ 'type',
+ 'destination',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'eventname', None, None,), #: 1
+ (2, TType.I32, 'id', None, None,), #: 2
+ (3, TType.I32, 'type', None, None,), #: 3
+ (4, TType.I32, 'destination', None, None,), #: 4
+ )
+
+
+ def __init__(self, eventname=None, id=None, type=None, destination=None,):
+ self.eventname = eventname
+ self.id = id
+ self.type = type
+ self.destination = destination
+
+
+class UserData(TBase):
+ """
+ Attributes:
+ - name
+ - email
+ - role
+ - permission
+ - templateName
+ """
+
+ __slots__ = [
+ 'name',
+ 'email',
+ 'role',
+ 'permission',
+ 'templateName',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'name', None, None,), #: 1
+ (2, TType.STRING, 'email', None, None,), #: 2
+ (3, TType.I32, 'role', None, None,), #: 3
+ (4, TType.I32, 'permission', None, None,), #: 4
+ (5, TType.STRING, 'templateName', None, None,), #: 5
+ )
+
+
+ def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,):
+ self.name = name
+ self.email = email
+ self.role = role
+ self.permission = permission
+ self.templateName = templateName
+
+
+class AccountInfo(TBase):
+ """
+ Attributes:
+ - validuntil
+ - login
+ - options
+ - valid
+ - trafficleft
+ - maxtraffic
+ - premium
+ - type
+ """
+
+ __slots__ = [
+ 'validuntil',
+ 'login',
+ 'options',
+ 'valid',
+ 'trafficleft',
+ 'maxtraffic',
+ 'premium',
+ 'type',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I64, 'validuntil', None, None,), #: 1
+ (2, TType.STRING, 'login', None, None,), #: 2
+ (3, TType.MAP, 'options', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 3
+ (4, TType.BOOL, 'valid', None, None,), #: 4
+ (5, TType.I64, 'trafficleft', None, None,), #: 5
+ (6, TType.I64, 'maxtraffic', None, None,), #: 6
+ (7, TType.BOOL, 'premium', None, None,), #: 7
+ (8, TType.STRING, 'type', None, None,), #: 8
+ )
+
+
+ def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,):
+ self.validuntil = validuntil
+ self.login = login
+ self.options = options
+ self.valid = valid
+ self.trafficleft = trafficleft
+ self.maxtraffic = maxtraffic
+ self.premium = premium
+ self.type = type
+
+
+class ServiceCall(TBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ - arguments
+ - parseArguments
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ 'arguments',
+ 'parseArguments',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ (2, TType.STRING, 'func', None, None,), #: 2
+ (3, TType.LIST, 'arguments', (TType.STRING, None), None,), #: 3
+ (4, TType.BOOL, 'parseArguments', None, None,), #: 4
+ )
+
+
+ def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,):
+ self.plugin = plugin
+ self.func = func
+ self.arguments = arguments
+ self.parseArguments = parseArguments
+
+
+class OnlineStatus(TBase):
+ """
+ Attributes:
+ - name
+ - plugin
+ - packagename
+ - status
+ - size
+ """
+
+ __slots__ = [
+ 'name',
+ 'plugin',
+ 'packagename',
+ 'status',
+ 'size',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'name', None, None,), #: 1
+ (2, TType.STRING, 'plugin', None, None,), #: 2
+ (3, TType.STRING, 'packagename', None, None,), #: 3
+ (4, TType.I32, 'status', None, None,), #: 4
+ (5, TType.I64, 'size', None, None,), #: 5
+ )
+
+
+ def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,):
+ self.name = name
+ self.plugin = plugin
+ self.packagename = packagename
+ self.status = status
+ self.size = size
+
+
+class OnlineCheck(TBase):
+ """
+ Attributes:
+ - rid
+ - data
+ """
+
+ __slots__ = [
+ 'rid',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'rid', None, None,), #: 1
+ (2, TType.MAP, 'data', (TType.STRING, None, TType.STRUCT, (OnlineStatus, OnlineStatus.thrift_spec)), None,), #: 2
+ )
+
+
+ def __init__(self, rid=None, data=None,):
+ self.rid = rid
+ self.data = data
+
+
+class PackageDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'pid', None, None,), #: 1
+ )
+
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+ def __str__(self):
+ return repr(self)
+
+
+class FileDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.I32, 'fid', None, None,), #: 1
+ )
+
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+
+ def __str__(self):
+ return repr(self)
+
+
+class ServiceDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'plugin', None, None,), #: 1
+ (2, TType.STRING, 'func', None, None,), #: 2
+ )
+
+
+ def __init__(self, plugin=None, func=None,):
+ self.plugin = plugin
+ self.func = func
+
+
+ def __str__(self):
+ return repr(self)
+
+
+class ServiceException(TExceptionBase):
+ """
+ Attributes:
+ - msg
+ """
+
+ __slots__ = [
+ 'msg',
+ ]
+
+ thrift_spec = (
+ None, #: 0
+ (1, TType.STRING, 'msg', None, None,), #: 1
+ )
+
+
+ def __init__(self, msg=None,):
+ self.msg = msg
+
+
+ def __str__(self):
+ return repr(self)
diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py
new file mode 100644
index 000000000..da84fe51c
--- /dev/null
+++ b/pyload/utils/__init__.py
@@ -0,0 +1,270 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+""" Store all useful functions here """
+
+import bitmath
+import os
+import re
+import sys
+import time
+
+# from gettext import gettext
+import pylgettext as gettext
+from htmlentitydefs import name2codepoint
+from os.path import join
+from string import maketrans
+from urllib import unquote
+
+# abstraction layer for json operations
+try:
+ import simplejson as json
+except ImportError:
+ import json
+
+json_loads = json.loads
+json_dumps = json.dumps
+
+
+def chmod(*args):
+ try:
+ os.chmod(*args)
+ except Exception:
+ pass
+
+
+def decode(string):
+ """ Decode string to unicode with utf8 """
+ if type(string) == str:
+ return string.decode("utf8", "replace")
+ else:
+ return string
+
+
+def encode(string):
+ """ Decode string to utf8 """
+ if type(string) == unicode:
+ return string.encode("utf8", "replace")
+ else:
+ return string
+
+
+def remove_chars(string, repl):
+ """ removes all chars in repl from string"""
+ if type(repl) == unicode:
+ for badc in list(repl):
+ string = string.replace(badc, "")
+ return string
+ else:
+ if type(string) == str:
+ return string.translate(maketrans("", ""), repl)
+ elif type(string) == unicode:
+ return string.translate(dict((ord(s), None) for s in repl))
+
+
+def safe_filename(name):
+ """ remove bad chars """
+ name = unquote(name).encode('ascii', 'replace') #: Non-ASCII chars usually breaks file saving. Replacing.
+ if os.name == 'nt':
+ return remove_chars(name, u'\00\01\02\03\04\05\06\07\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32'
+ u'\33\34\35\36\37/?%*|"<>')
+ else:
+ return remove_chars(name, u'\0\\"')
+
+
+#: Deprecated method
+def save_path(name):
+ return safe_filename(name)
+
+
+def fs_join(*args):
+ """ joins a path, encoding aware """
+ return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args]))
+
+
+#: Deprecated method
+def save_join(*args):
+ return fs_join(*args)
+
+
+# File System Encoding functions:
+# Use fs_encode before accesing files on disk, it will encode the string properly
+
+if sys.getfilesystemencoding().startswith('ANSI'):
+
+ def fs_encode(string):
+ return safe_filename(encode(string))
+
+ fs_decode = decode #: decode utf8
+
+else:
+ fs_encode = fs_decode = lambda x: x #: do nothing
+
+
+def get_console_encoding(enc):
+ if os.name == "nt":
+ if enc == "cp65001": #: aka UTF-8
+ print "WARNING: Windows codepage 65001 is not supported."
+ enc = "cp850"
+ else:
+ enc = "utf8"
+
+ return enc
+
+
+def compare_time(start, end):
+ start = map(int, start)
+ end = map(int, end)
+
+ if start == end:
+ return True
+
+ now = list(time.localtime()[3:5])
+ if start < now < end:
+ return True
+ elif start > end and (now > start or now < end):
+ return True
+ elif start < now > end < start:
+ return True
+ return False
+
+
+def formatSize(size):
+ """formats size of bytes"""
+ return bitmath.Byte(int(size)).best_prefix()
+
+
+def formatSpeed(speed):
+ return formatSize(speed) + "/s"
+
+
+def freeSpace(folder):
+ if os.name == "nt":
+ import ctypes
+
+ free_bytes = ctypes.c_ulonglong(0)
+ ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
+ return free_bytes.value
+ else:
+ s = os.statvfs(folder)
+ return s.f_frsize * s.f_bavail
+
+
+def fs_bsize(path):
+ """ get optimal file system buffer size (in bytes) for I/O calls """
+ path = fs_encode(path)
+
+ if os.name == "nt":
+ import ctypes
+
+ drive = "%s\\" % os.path.splitdrive(path)[0]
+ cluster_sectors, sector_size = ctypes.c_longlong(0)
+ ctypes.windll.kernel32.GetDiskFreeSpaceW(ctypes.c_wchar_p(drive), ctypes.pointer(cluster_sectors), ctypes.pointer(sector_size), None, None)
+ return cluster_sectors * sector_size
+ else:
+ return os.statvfs(path).f_frsize
+
+
+def uniqify(seq): #: Originally by Dave Kirby
+ """ Remove duplicates from list preserving order """
+ seen = set()
+ seen_add = seen.add
+ return [x for x in seq if x not in seen and not seen_add(x)]
+
+
+def parseFileSize(string, unit=None): #: returns bytes
+ if not unit:
+ m = re.match(r"([\d.,]+) *([a-zA-Z]*)", string.strip().lower())
+ if m:
+ traffic = float(m.group(1).replace(",", "."))
+ unit = m.group(2)
+ else:
+ return 0
+ else:
+ if isinstance(string, basestring):
+ traffic = float(string.replace(",", "."))
+ else:
+ traffic = string
+
+ # ignore case
+ unit = unit.lower().strip()
+
+ if unit in ("eb", "ebyte", "exabyte", "eib", "e"):
+ traffic *= 1 << 60
+ elif unit in ("pb", "pbyte", "petabyte", "pib", "p"):
+ traffic *= 1 << 50
+ elif unit in ("tb", "tbyte", "terabyte", "tib", "t"):
+ traffic *= 1 << 40
+ elif unit in ("gb", "gbyte", "gigabyte", "gib", "g", "gig"):
+ traffic *= 1 << 30
+ elif unit in ("mb", "mbyte", "megabyte", "mib", "m"):
+ traffic *= 1 << 20
+ elif unit in ("kb", "kbyte", "kilobyte", "kib", "k"):
+ traffic *= 1 << 10
+
+ return traffic
+
+
+def lock(func):
+
+ def new(*args):
+ # print "Handler: %s args: %s" % (func, args[1:])
+ args[0].lock.acquire()
+ try:
+ return func(*args)
+ finally:
+ args[0].lock.release()
+
+ return new
+
+
+def fixup(m):
+ text = m.group(0)
+ if text[:2] == "&#":
+ # character reference
+ try:
+ if text[:3] == "&#x":
+ return unichr(int(text[3:-1], 16))
+ else:
+ return unichr(int(text[2:-1]))
+ except ValueError:
+ pass
+ else:
+ # named entity
+ try:
+ name = text[1:-1]
+ text = unichr(name2codepoint[name])
+ except KeyError:
+ pass
+
+ return text #: leave as is
+
+
+def has_method(obj, name):
+ """ Check if "name" was defined in obj, (false if it was inhereted) """
+ return hasattr(obj, '__dict__') and name in obj.__dict__
+
+
+def html_unescape(text):
+ """Removes HTML or XML character references and entities from a text string"""
+ return re.sub("&#?\w+;", fixup, text)
+
+
+def versiontuple(v): #: By kindall (http://stackoverflow.com/a/11887825)
+ return tuple(map(int, (v.split("."))))
+
+
+def load_translation(name, locale, default="en"):
+ """ Load language and return its translation object or None """
+ from traceback import print_exc
+ from os.path import join
+ try:
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation(name, join(pypath, "locale"),
+ languages=[locale, default], fallback=True)
+ except Exception:
+ print_exc()
+ return None
+ else:
+ translation.install(True)
+ return translation
diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py
new file mode 100644
index 000000000..8ed55d0f7
--- /dev/null
+++ b/pyload/utils/packagetools.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urlparse
+
+
+endings = ("jdeatme", "3gp", "7zip", "7z", "abr", "ac3", "aiff", "aifc", "aif", "ai",
+ "au", "avi", "apk", "bin", "bmp", "bat", "bz2", "cbr", "cbz", "ccf", "chm",
+ "cr2", "cso", "cue", "cvd", "dta", "deb", "divx", "djvu", "dlc", "dmg", "doc",
+ "docx", "dot", "eps", "epub", "exe", "ff", "flv", "flac", "f4v", "gsd", "gif",
+ "gpg", "gz", "iwd", "idx", "iso", "ipa", "ipsw", "java", "jar", "jpe?g", "load",
+ "m2ts", "m4v", "m4a", "md5", "mkv", "mp2", "mp3", "mp4", "mobi", "mov", "movie",
+ "mpeg", "mpe", "mpg", "mpq", "msi", "msu", "msp", "mv", "mws", "nfo", "npk", "oga",
+ "ogg", "ogv", "otrkey", "par2", "pkg", "png", "pdf", "pptx?", "ppsx?", "ppz", "pot",
+ "psd", "qt", "rmvb", "rm", "rar", "ram", "ra", "rev", "rnd", "rpm", "run", "rsdf",
+ "reg", "rtf", "shnf", "sh(?!tml)", "ssa", "smi", "sub", "srt", "snd", "sfv", "sfx",
+ "swf", "swc", "tar\.(gz|bz2|xz)", "tar", "tgz", "tiff?", "ts", "txt", "viv", "vivo",
+ "vob", "vtt", "webm", "wav", "wmv", "wma", "xla", "xls", "xpi", "zeno", "zip",
+ "[r-z]\d{2}", "_[_a-z]{2}", "\d{3,4}(?=\?|$|\"|\r|\n)")
+
+rarPats = [re.compile(r'(.*)(\.|_|-)pa?r?t?\.?\d+.(rar|exe)$', re.I),
+ re.compile(r'(.*)(\.|_|-)part\.?[0]*[1].(rar|exe)$', re.I),
+ re.compile(r'(.*)\.rar$', re.I),
+ re.compile(r'(.*)\.r\d+$', re.I),
+ re.compile(r'(.*)(\.|_|-)\d+$', re.I)]
+
+zipPats = [re.compile(r'(.*)\.zip$', re.I),
+ re.compile(r'(.*)\.z\d+$', re.I),
+ re.compile(r'(?is).*\.7z\.[\d]+$', re.I),
+ re.compile(r'(.*)\.a.$', re.I)]
+
+ffsjPats = [re.compile(r'(.*)\._((_[a-z])|([a-z]{2}))(\.|$)'),
+ re.compile(r'(.*)(\.|_|-)[\d]+(\.(' + '|'.join(endings) + ')$)', re.I)]
+
+iszPats = [re.compile(r'(.*)\.isz$', re.I),
+ re.compile(r'(.*)\.i\d{2}$', re.I)]
+
+pat0 = re.compile(r'www\d*\.', re.I)
+
+pat1 = re.compile(r'(\.?CD\d+)', re.I)
+pat2 = re.compile(r'(\.?part\d+)', re.I)
+
+pat3 = re.compile(r'(.+)[\.\-_]+$')
+pat4 = re.compile(r'(.+)\.\d+\.xtm$')
+
+
+def matchFirst(string, *args):
+ """ matches against list of regexp and returns first match """
+ for patternlist in args:
+ for pattern in patternlist:
+ m = pattern.search(string)
+ if m is not None:
+ name = m.group(1)
+ return name
+
+ return string
+
+
+def parseNames(files):
+ """ Generates packages names from name, data lists
+
+ :param files: list of (name, data)
+ :return: packagenames mapped to data lists (eg. urls)
+ """
+ packs = {}
+
+ for file, url in files:
+ patternMatch = False
+
+ if file is None:
+ continue
+
+ # remove trailing /
+ name = file.rstrip('/')
+
+ # extract last path part .. if there is a path
+ split = name.rsplit("/", 1)
+ if len(split) > 1:
+ name = split.pop(1)
+
+ # check if an already existing package may be ok for this file
+ # found = False
+ # for pack in packs:
+ # if pack in file:
+ # packs[pack].append(url)
+ # found = True
+ # break
+ #
+ # if found:
+ # continue
+
+ # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern
+ before = name
+ name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats)
+ if before != name:
+ patternMatch = True
+
+ # xtremsplit pattern
+ m = pat4.search(name)
+ if m is not None:
+ name = m.group(1)
+
+ # remove part and cd pattern
+ m = pat1.search(name)
+ if m is not None:
+ name = name.replace(m.group(0), "")
+ patternMatch = True
+
+ m = pat2.search(name)
+ if m is not None:
+ name = name.replace(m.group(0), "")
+ patternMatch = True
+
+ # additional checks if extension pattern matched
+ if patternMatch:
+ # remove extension
+ index = name.rfind(".")
+ if index <= 0:
+ index = name.rfind("_")
+ if index > 0:
+ length = len(name) - index
+ if length <= 4:
+ name = name[:-length]
+
+ # remove endings like . _ -
+ m = pat3.search(name)
+ if m is not None:
+ name = m.group(1)
+
+ # replace . and _ with space
+ name = name.replace(".", " ")
+ name = name.replace("_", " ")
+
+ name = name.strip()
+ else:
+ name = ""
+
+ #@NOTE: fallback: package by hoster
+ if not name:
+ name = urlparse(file).netloc
+ if name:
+ name = pat0.sub("", name)
+
+ # fallback : default name
+ if not name:
+ name = _("Unnamed package")
+
+ # build mapping
+ if name in packs:
+ packs[name].append(url)
+ else:
+ packs[name] = [url]
+
+ return packs
diff --git a/pyload/utils/printer.py b/pyload/utils/printer.py
new file mode 100644
index 000000000..e4f6a360a
--- /dev/null
+++ b/pyload/utils/printer.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+import colorama
+
+colorama.init(autoreset=True)
+
+
+def color(color, text):
+ return colorama.Fore[c.upper()](text)
+
+for c in colorama.Fore:
+ eval("%(color)s = lambda msg: color(%(color)s, msg)" % {'color': c.lower()})
+
+
+def overline(line, msg):
+ print "\033[%(line)s;0H\033[2K%(msg)s" % {'line': str(line), 'msg': msg}
diff --git a/pyload/utils/pylgettext.py b/pyload/utils/pylgettext.py
new file mode 100644
index 000000000..76bb268ec
--- /dev/null
+++ b/pyload/utils/pylgettext.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+from gettext import *
+
+_searchdirs = None
+
+origfind = find
+
+
+def setpaths(pathlist):
+ global _searchdirs
+ _searchdirs = pathlist if isinstance(pathlist, list) else list(pathlist)
+
+
+def addpath(path):
+ global _searchdirs
+ if _searchdirs is None:
+ _searchdirs = list(path)
+ else:
+ if path not in _searchdirs:
+ _searchdirs.append(path)
+
+
+def delpath(path):
+ global _searchdirs
+ if _searchdirs is not None:
+ if path in _searchdirs:
+ _searchdirs.remove(path)
+
+
+def clearpath():
+ global _searchdirs
+ _searchdirs = None
+
+
+def find(domain, localedir=None, languages=None, all=False):
+ if _searchdirs is None:
+ return origfind(domain, localedir, languages, all)
+ searches = [localedir] + _searchdirs
+ results = []
+ for dir in searches:
+ res = origfind(domain, dir, languages, all)
+ if all is False:
+ results.append(res)
+ else:
+ results.extend(res)
+ if all is False:
+ results = filter(lambda x: x is not None, results)
+ if len(results) == 0:
+ return None
+ else:
+ return results[0]
+ else:
+ return results
+
+# Is there a smarter/cleaner pythonic way for this?
+translation.func_globals['find'] = find
diff --git a/pyload/webui/__init__.py b/pyload/webui/__init__.py
new file mode 100644
index 000000000..841e5abd9
--- /dev/null
+++ b/pyload/webui/__init__.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, vuolter
+
+import sys
+import pyload.utils.pylgettext as gettext
+
+import os
+from os.path import join, abspath, dirname, exists
+from os import makedirs
+
+THEME_DIR = abspath(join(dirname(__file__), "themes"))
+PYLOAD_DIR = abspath(join(THEME_DIR, "..", "..", ".."))
+
+sys.path.append(PYLOAD_DIR)
+
+from pyload.utils import decode, formatSize
+
+import bottle
+from bottle import run, app
+
+from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache
+from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware
+
+SETUP = None
+PYLOAD = None
+
+from pyload.manager.thread import Server
+from pyload.network.JsEngine import JsEngine
+
+if not Server.core:
+ if Server.setup:
+ SETUP = Server.setup
+ config = SETUP.config
+ JS = JsEngine(SETUP)
+ else:
+ raise Exception("Could not access pyLoad Core")
+else:
+ PYLOAD = Server.core.api
+ config = Server.core.config
+ JS = JsEngine(Server.core)
+
+THEME = config.get('webui', 'theme')
+DL_ROOT = config.get('general', 'download_folder')
+LOG_ROOT = config.get('log', 'log_folder')
+PREFIX = config.get('webui', 'prefix')
+
+if PREFIX:
+ PREFIX = PREFIX.rstrip("/")
+ if not PREFIX.startswith("/"):
+ PREFIX = "/" + PREFIX
+
+DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv
+bottle.debug(DEBUG)
+
+cache = join("tmp", "jinja_cache")
+if not exists(cache):
+ makedirs(cache)
+
+bcc = FileSystemBytecodeCache(cache, '%s.cache')
+
+loader = FileSystemLoader([THEME_DIR, join(THEME_DIR, THEME)])
+
+env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False,
+ bytecode_cache=bcc)
+
+from filters import quotepath, path_make_relative, path_make_absolute, truncate, date
+
+env.filters['quotepath'] = quotepath
+env.filters['truncate'] = truncate
+env.filters['date'] = date
+env.filters['path_make_relative'] = path_make_relative
+env.filters['path_make_absolute'] = path_make_absolute
+env.filters['decode'] = decode
+env.filters['type'] = lambda x: str(type(x))
+env.filters['formatsize'] = formatSize
+env.filters['getitem'] = lambda x, y: x.__getitem__(y)
+if PREFIX:
+ env.filters['url'] = lambda x: x
+else:
+ env.filters['url'] = lambda x: PREFIX + x if x.startswith("/") else x
+
+gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+translation = gettext.translation("django", join(PYLOAD_DIR, "locale"),
+ languages=[config.get("general", "language"), "en"],fallback=True)
+translation.install(True)
+env.install_gettext_translations(translation)
+
+from beaker.middleware import SessionMiddleware
+
+session_opts = {
+ 'session.type': 'file',
+ 'session.cookie_expires': False,
+ 'session.data_dir': './tmp',
+ 'session.auto': False
+}
+
+web = StripPathMiddleware(SessionMiddleware(app(), session_opts))
+web = GZipMiddleWare(web)
+
+if PREFIX:
+ web = PrefixMiddleware(web, prefix=PREFIX)
+
+import pyload.webui.app
+
+
+def run_simple(host="0.0.0.0", port="8000"):
+ run(app=web, host=host, port=port, quiet=True)
+
+
+def run_lightweight(host="0.0.0.0", port="8000"):
+ run(app=web, host=host, port=port, server="bjoern", quiet=True)
+
+
+def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""):
+ from wsgiserver import CherryPyWSGIServer
+
+ if cert and key:
+ CherryPyWSGIServer.ssl_certificate = cert
+ CherryPyWSGIServer.ssl_private_key = key
+
+ CherryPyWSGIServer.numthreads = theads
+
+ from pyload.webui.app.utils import CherryPyWSGI
+
+ run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True)
+
+
+def run_fcgi(host="0.0.0.0", port="8000"):
+ from bottle import FlupFCGIServer
+
+ run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True)
diff --git a/pyload/webui/app/__init__.py b/pyload/webui/app/__init__.py
new file mode 100644
index 000000000..43c9ecbe9
--- /dev/null
+++ b/pyload/webui/app/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from pyload.webui.app import api, cnl, json, pyloadweb
diff --git a/pyload/webui/app/api.py b/pyload/webui/app/api.py
new file mode 100644
index 000000000..70d86c112
--- /dev/null
+++ b/pyload/webui/app/api.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from itertools import chain
+from traceback import format_exc, print_exc
+
+from bottle import route, request, response, HTTPError
+
+from pyload.webui.app.utils import toDict, set_session
+from pyload.webui import PYLOAD
+from pyload.utils import json
+from SafeEval import const_eval as literal_eval
+from pyload.api import BaseObject
+
+
+
+# json encoder that accepts TBase objects
+class TBaseEncoder(json.JSONEncoder):
+
+ def default(self, o):
+ if isinstance(o, BaseObject):
+ return toDict(o)
+ return json.JSONEncoder.default(self, o)
+
+
+# accepting positional arguments, as well as kwargs via post and get
+@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>')
+@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>', method='POST')
+def call_api(func, args=""):
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ s = request.environ.get('beaker.session')
+ if 'session' in request.POST:
+ s = s.get_by_id(request.POST['session'])
+
+ if not s or not s.get("authenticated", False):
+ return HTTPError(403, json.dumps("Forbidden"))
+
+ if not PYLOAD.isAuthorized(func, {"role": s['role'], "permission": s['perms']}):
+ return HTTPError(401, json.dumps("Unauthorized"))
+
+ args = args.split("/")[1:]
+ kwargs = {}
+
+ for x, y in chain(request.GET.iteritems(), request.POST.iteritems()):
+ if x == "session":
+ continue
+ kwargs[x] = unquote(y)
+
+ try:
+ return callApi(func, *args, **kwargs)
+ except Exception, e:
+ print_exc()
+ return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()}))
+
+
+def callApi(func, *args, **kwargs):
+ if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
+ print "Invalid API call", func
+ return HTTPError(404, json.dumps("Not Found"))
+
+ result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args],
+ **dict((x, literal_eval(y)) for x, y in kwargs.iteritems()))
+
+ # null is invalid json response
+ return json.dumps(result or True, cls=TBaseEncoder)
+
+
+# post -> username, password
+@route('/api/login', method='POST')
+def login():
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ user = request.forms.get("username")
+ password = request.forms.get("password")
+
+ info = PYLOAD.checkAuth(user, password)
+
+ if not info:
+ return json.dumps(False)
+
+ s = set_session(request, info)
+
+ # get the session id by dirty way, documentations seems wrong
+ try:
+ sid = s._headers['cookie_out'].split("=")[1].split(";")[0]
+ return json.dumps(sid)
+ except Exception:
+ return json.dumps(True)
+
+
+@route('/api/logout')
+def logout():
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ s = request.environ.get('beaker.session')
+ s.delete()
diff --git a/pyload/webui/app/cnl.py b/pyload/webui/app/cnl.py
new file mode 100644
index 000000000..635d4030c
--- /dev/null
+++ b/pyload/webui/app/cnl.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from os.path import join
+import re
+from urllib import unquote
+from base64 import standard_b64decode
+from binascii import unhexlify
+
+from bottle import route, request, HTTPError
+
+from pyload.webui import PYLOAD, DL_ROOT, JS
+
+
+try:
+ from Crypto.Cipher import AES
+except Exception:
+ pass
+
+
+def local_check(function):
+
+
+ def _view(*args, **kwargs):
+ if request.environ.get("REMOTE_ADDR", "0") in ("127.0.0.1", "localhost") \
+ or request.environ.get("HTTP_HOST", "0") in ("127.0.0.1:9666", "localhost:9666"):
+ return function(*args, **kwargs)
+ else:
+ return HTTPError(403, "Forbidden")
+
+ return _view
+
+
+@route('/flash')
+@route('/flash/<id>')
+@route('/flash', method='POST')
+@local_check
+def flash(id="0"):
+ return "JDownloader\r\n"
+
+
+@route('/flash/add', method='POST')
+@local_check
+def add(request):
+ package = request.POST.get('referer', None)
+ urls = filter(lambda x: x != "", request.POST['urls'].split("\n"))
+
+ if package:
+ PYLOAD.addPackage(package, urls, 0)
+ else:
+ PYLOAD.generateAndAddPackages(urls, 0)
+
+ return ""
+
+
+@route('/flash/addcrypted', method='POST')
+@local_check
+def addcrypted():
+ package = request.forms.get('referer', 'ClickNLoad Package')
+ dlc = request.forms['crypted'].replace(" ", "+")
+
+ dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc")
+ with open(dlc_path, "wb") as dlc_file:
+ dlc_file.write(dlc)
+
+ try:
+ PYLOAD.addPackage(package, [dlc_path], 0)
+ except Exception:
+ return HTTPError()
+ else:
+ return "success\r\n"
+
+
+@route('/flash/addcrypted2', method='POST')
+@local_check
+def addcrypted2():
+ package = request.forms.get("source", None)
+ crypted = request.forms['crypted']
+ jk = request.forms['jk']
+
+ crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
+ if JS:
+ jk = "%s f()" % jk
+ jk = JS.eval(jk)
+
+ else:
+ try:
+ jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1]
+ except Exception:
+ # Test for some known js functions to decode
+ if jk.find("dec") > -1 and jk.find("org") > -1:
+ org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1]
+ jk = list(org)
+ jk.reverse()
+ jk = "".join(jk)
+ else:
+ print "Could not decrypt key, please install py-spidermonkey or ossp-js"
+
+ try:
+ Key = unhexlify(jk)
+ except Exception:
+ print "Could not decrypt key, please install py-spidermonkey or ossp-js"
+ return "failed"
+
+ IV = Key
+
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n")
+
+ result = filter(lambda x: x != "", result)
+
+ try:
+ if package:
+ PYLOAD.addPackage(package, result, 0)
+ else:
+ PYLOAD.generateAndAddPackages(result, 0)
+ except Exception:
+ return "failed can't add"
+ else:
+ return "success\r\n"
+
+
+@route('/flashgot_pyload')
+@route('/flashgot_pyload', method='POST')
+@route('/flashgot')
+@route('/flashgot', method='POST')
+@local_check
+def flashgot():
+ if request.environ['HTTP_REFERER'] not in ("http://localhost:9666/flashgot", "http://127.0.0.1:9666/flashgot"):
+ return HTTPError()
+
+ autostart = int(request.forms.get('autostart', 0))
+ package = request.forms.get('package', None)
+ urls = filter(lambda x: x != "", request.forms['urls'].split("\n"))
+ folder = request.forms.get('dir', None)
+
+ if package:
+ PYLOAD.addPackage(package, urls, autostart)
+ else:
+ PYLOAD.generateAndAddPackages(urls, autostart)
+
+ return ""
+
+
+@route('/crossdomain.xml')
+@local_check
+def crossdomain():
+ rep = "<?xml version=\"1.0\"?>\n"
+ rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
+ rep += "<cross-domain-policy>\n"
+ rep += "<allow-access-from domain=\"*\" />\n"
+ rep += "</cross-domain-policy>"
+ return rep
+
+
+@route('/flash/checkSupportForUrl')
+@local_check
+def checksupport():
+ url = request.GET.get("url")
+ res = PYLOAD.checkURLs([url])
+ supported = (not res[0][1] is None)
+
+ return str(supported).lower()
+
+
+@route('/jdcheck.js')
+@local_check
+def jdcheck():
+ rep = "jdownloader=true;\n"
+ rep += "var version='9.581;'"
+ return rep
diff --git a/pyload/webui/app/json.py b/pyload/webui/app/json.py
new file mode 100644
index 000000000..3b72cb7ce
--- /dev/null
+++ b/pyload/webui/app/json.py
@@ -0,0 +1,314 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from os.path import join
+from traceback import print_exc
+from shutil import copyfileobj
+
+from bottle import route, request, HTTPError
+
+from pyload.webui import PYLOAD
+from pyload.webui.app.utils import login_required, render_to_response, toDict
+from pyload.utils import decode, formatSize
+
+
+def format_time(seconds):
+ seconds = int(seconds)
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+
+def get_sort_key(item):
+ return item['order']
+
+
+@route('/json/status')
+@route('/json/status', method='POST')
+@login_required('LIST')
+def status():
+ try:
+ status = toDict(PYLOAD.statusServer())
+ status['captcha'] = PYLOAD.isCaptchaWaiting()
+ return status
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/links')
+@route('/json/links', method='POST')
+@login_required('LIST')
+def links():
+ try:
+ links = [toDict(x) for x in PYLOAD.statusDownloads()]
+ ids = []
+ for link in links:
+ ids.append(link['fid'])
+
+ if link['status'] == 12:
+ link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed']))
+ elif link['status'] == 5:
+ link['percent'] = 0
+ link['size'] = 0
+ link['bleft'] = 0
+ link['info'] = _("waiting %s") % link['format_wait']
+ else:
+ link['info'] = ""
+
+ data = {'links': links, 'ids': ids}
+ return data
+ except Exception, e:
+ print_exc()
+ return HTTPError()
+
+
+@route('/json/packages')
+@login_required('LIST')
+def packages():
+ print "/json/packages"
+ try:
+ data = PYLOAD.getQueue()
+
+ for package in data:
+ package['links'] = []
+ for file in PYLOAD.get_package_files(package['id']):
+ package['links'].append(PYLOAD.get_file_info(file))
+
+ return data
+
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/package/<id:int>')
+@login_required('LIST')
+def package(id):
+ try:
+ data = toDict(PYLOAD.getPackageData(id))
+ data['links'] = [toDict(x) for x in data['links']]
+
+ for pyfile in data['links']:
+ if pyfile['status'] == 0:
+ pyfile['icon'] = "status_finished.png"
+ elif pyfile['status'] in (2, 3):
+ pyfile['icon'] = "status_queue.png"
+ elif pyfile['status'] in (9, 1):
+ pyfile['icon'] = "status_offline.png"
+ elif pyfile['status'] == 5:
+ pyfile['icon'] = "status_waiting.png"
+ elif pyfile['status'] == 8:
+ pyfile['icon'] = "status_failed.png"
+ elif pyfile['status'] == 4:
+ pyfile['icon'] = "arrow_right.png"
+ elif pyfile['status'] in (11, 13):
+ pyfile['icon'] = "status_proc.png"
+ else:
+ pyfile['icon'] = "status_downloading.png"
+
+ tmp = data['links']
+ tmp.sort(key=get_sort_key)
+ data['links'] = tmp
+ return data
+
+ except Exception:
+ print_exc()
+ return HTTPError()
+
+
+@route('/json/package_order/<ids>')
+@login_required('ADD')
+def package_order(ids):
+ try:
+ pid, pos = ids.split("|")
+ PYLOAD.orderPackage(int(pid), int(pos))
+ return {"response": "success"}
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/abort_link/<id:int>')
+@login_required('DELETE')
+def abort_link(id):
+ try:
+ PYLOAD.stopDownloads([id])
+ return {"response": "success"}
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/link_order/<ids>')
+@login_required('ADD')
+def link_order(ids):
+ try:
+ pid, pos = ids.split("|")
+ PYLOAD.orderFile(int(pid), int(pos))
+ return {"response": "success"}
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/add_package')
+@route('/json/add_package', method='POST')
+@login_required('ADD')
+def add_package():
+ name = request.forms.get("add_name", "New Package").strip()
+ queue = int(request.forms['add_dest'])
+ links = decode(request.forms['add_links'])
+ links = links.split("\n")
+ pw = request.forms.get("add_password", "").strip("\n\r")
+
+ try:
+ f = request.files['add_file']
+
+ if not name or name == "New Package":
+ name = f.name
+
+ fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename)
+ with open(fpath, 'wb') as destination:
+ copyfileobj(f.file, destination)
+ links.insert(0, fpath)
+ except Exception:
+ pass
+
+ name = name.decode("utf8", "ignore")
+
+ links = map(lambda x: x.strip(), links)
+ links = filter(lambda x: x != "", links)
+
+ pack = PYLOAD.addPackage(name, links, queue)
+ if pw:
+ pw = pw.decode("utf8", "ignore")
+ data = {"password": pw}
+ PYLOAD.setPackageData(pack, data)
+
+
+@route('/json/move_package/<dest:int>/<id:int>')
+@login_required('MODIFY')
+def move_package(dest, id):
+ try:
+ PYLOAD.movePackage(dest, id)
+ return {"response": "success"}
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/edit_package', method='POST')
+@login_required('MODIFY')
+def edit_package():
+ try:
+ id = int(request.forms.get("pack_id"))
+ data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"),
+ "folder": request.forms.get("pack_folder").decode("utf8", "ignore"),
+ "password": request.forms.get("pack_pws").decode("utf8", "ignore")}
+
+ PYLOAD.setPackageData(id, data)
+ return {"response": "success"}
+
+ except Exception:
+ return HTTPError()
+
+
+@route('/json/set_captcha')
+@route('/json/set_captcha', method='POST')
+@login_required('ADD')
+def set_captcha():
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ try:
+ PYLOAD.setCaptchaResult(request.forms['cap_id'], request.forms['cap_result'])
+ except Exception:
+ pass
+
+ task = PYLOAD.getCaptchaTask()
+
+ if task.tid >= 0:
+ src = "data:image/%s;base64,%s" % (task.type, task.data)
+
+ return {'captcha': True, 'id': task.tid, 'src': src, 'result_type': task.resultType}
+ else:
+ return {'captcha': False}
+
+
+@route('/json/load_config/<category>/<section>')
+@login_required("SETTINGS")
+def load_config(category, section):
+ conf = None
+ if category == "general":
+ conf = PYLOAD.getConfigDict()
+ elif category == "plugin":
+ conf = PYLOAD.getPluginConfigDict()
+
+ for key, option in conf[section].iteritems():
+ if key in ("desc", "outline"):
+ continue
+
+ if ";" in option['type']:
+ option['list'] = option['type'].split(";")
+
+ option['value'] = decode(option['value'])
+
+ return render_to_response("settings_item.html", {"sorted_conf": lambda c: sorted(c.items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0),
+ "skey": section, "section": conf[section]})
+
+
+@route('/json/save_config/<category>', method='POST')
+@login_required("SETTINGS")
+def save_config(category):
+ for key, value in request.POST.iteritems():
+ try:
+ section, option = key.split("|")
+ except Exception:
+ continue
+
+ if category == "general": category = "core"
+
+ PYLOAD.setConfigValue(section, option, decode(value), category)
+
+
+@route('/json/add_account', method='POST')
+@login_required("ACCOUNTS")
+def add_account():
+ login = request.POST['account_login']
+ password = request.POST['account_password']
+ type = request.POST['account_type']
+
+ PYLOAD.updateAccount(type, login, password)
+
+
+@route('/json/update_accounts', method='POST')
+@login_required("ACCOUNTS")
+def update_accounts():
+ deleted = [] #: dont update deleted accs or they will be created again
+
+ for name, value in request.POST.iteritems():
+ value = value.strip()
+ if not value:
+ continue
+
+ tmp, user = name.split(";")
+ plugin, action = tmp.split("|")
+
+ if (plugin, user) in deleted:
+ continue
+
+ if action == "password":
+ PYLOAD.updateAccount(plugin, user, value)
+ elif action == "time" and "-" in value:
+ PYLOAD.updateAccount(plugin, user, options={"time": [value]})
+ elif action == "limitdl" and value.isdigit():
+ PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]})
+ elif action == "delete":
+ deleted.append((plugin, user))
+ PYLOAD.removeAccount(plugin, user)
+
+
+@route('/json/change_password', method='POST')
+def change_password():
+ user = request.POST['user_login']
+ oldpw = request.POST['login_current_password']
+ newpw = request.POST['login_new_password']
+
+ if not PYLOAD.changePassword(user, oldpw, newpw):
+ print "Wrong password"
+ return HTTPError()
diff --git a/pyload/webui/app/pyloadweb.py b/pyload/webui/app/pyloadweb.py
new file mode 100644
index 000000000..7f2317bd1
--- /dev/null
+++ b/pyload/webui/app/pyloadweb.py
@@ -0,0 +1,530 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from datetime import datetime
+from operator import itemgetter, attrgetter
+
+import time
+import os
+import sys
+from os import listdir
+from os.path import isdir, isfile, join, abspath
+from sys import getfilesystemencoding
+from urllib import unquote
+
+from bottle import route, static_file, request, response, redirect, error
+
+from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, THEME, SETUP, env
+
+from pyload.webui.app.utils import render_to_response, parse_permissions, parse_userdata, \
+ login_required, get_permission, set_permission, permlist, toDict, set_session
+
+from pyload.webui.filters import relpath, unquotepath
+
+from pyload.utils import formatSize, fs_join, fs_encode, fs_decode
+
+# Helper
+
+
+def pre_processor():
+ s = request.environ.get('beaker.session')
+ user = parse_userdata(s)
+ perms = parse_permissions(s)
+ status = {}
+ captcha = False
+ update = False
+ plugins = False
+ if user['is_authenticated']:
+ status = PYLOAD.statusServer()
+ info = PYLOAD.getInfoByPlugin("UpdateManager")
+ captcha = PYLOAD.isCaptchaWaiting()
+
+ # check if update check is available
+ if info:
+ if info['pyload'] == "True":
+ update = info['version']
+ if info['plugins'] == "True":
+ plugins = True
+
+ return {"user": user,
+ 'status': status,
+ 'captcha': captcha,
+ 'perms': perms,
+ 'url': request.url,
+ 'update': update,
+ 'plugins': plugins}
+
+
+def base(messages):
+ return render_to_response('base.html', {'messages': messages}, [pre_processor])
+
+
+# Views
+@error(403)
+def error403(code):
+ return "The parameter you passed has the wrong format"
+
+
+@error(404)
+def error404(code):
+ return "Sorry, this page does not exist"
+
+
+@error(500)
+def error500(error):
+ traceback = error.traceback
+ if traceback:
+ print traceback
+ return base(["An Error occured, please enable debug mode to get more details.", error,
+ traceback.replace("\n", "<br>") if traceback else "No Traceback"])
+
+
+@route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>')
+def server_min(theme, file):
+ filename = join(THEME_DIR, THEME, theme, file)
+ if not isfile(filename):
+ file = file.replace(".min.", ".")
+ if file.endswith(".js"):
+ return server_js(theme, file)
+ else:
+ return server_static(theme, file)
+
+
+@route('/<theme>/<file:re:.+\.js>')
+def server_js(theme, file):
+ response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
+
+ if "/render/" in file or ".render." in file or True:
+ response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime(time.time() + 24 * 7 * 60 * 60))
+ response.headers['Cache-control'] = "public"
+
+ path = "/".join((theme, file))
+ return env.get_template(path).render()
+ else:
+ return server_static(theme, file)
+
+
+@route('/<theme>/<file:path>')
+def server_static(theme, file):
+ response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime(time.time() + 24 * 7 * 60 * 60))
+ response.headers['Cache-control'] = "public"
+
+ return static_file(file, root=join(THEME_DIR, THEME, theme))
+
+
+@route('/favicon.ico')
+def favicon():
+ return static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources"))
+
+
+@route('/login', method="GET")
+def login():
+ if not PYLOAD and SETUP:
+ redirect("/setup")
+ else:
+ return render_to_response("login.html", proc=[pre_processor])
+
+
+@route('/nopermission')
+def nopermission():
+ return base([_("You dont have permission to access this page.")])
+
+
+@route('/login', method='POST')
+def login_post():
+ user = request.forms.get("username")
+ password = request.forms.get("password")
+
+ info = PYLOAD.checkAuth(user, password)
+
+ if not info:
+ return render_to_response("login.html", {"errors": True}, [pre_processor])
+
+ set_session(request, info)
+ return redirect("/")
+
+
+@route('/logout')
+def logout():
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return render_to_response("logout.html", proc=[pre_processor])
+
+
+@route('/')
+@route('/home')
+@login_required("LIST")
+def home():
+ try:
+ res = [toDict(x) for x in PYLOAD.statusDownloads()]
+ except Exception:
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return redirect("/login")
+
+ for link in res:
+ if link['status'] == 12:
+ link['information'] = "%s kB @ %s kB/s" % (link['size'] - link['bleft'], link['speed'])
+
+ return render_to_response("home.html", {"res": res}, [pre_processor])
+
+
+@route('/queue')
+@login_required("LIST")
+def queue():
+ queue = PYLOAD.getQueue()
+
+ queue.sort(key=attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor])
+
+
+@route('/collector')
+@login_required('LIST')
+def collector():
+ queue = PYLOAD.getCollector()
+
+ queue.sort(key=attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor])
+
+
+@route('/downloads')
+@login_required('DOWNLOAD')
+def downloads():
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ if not isdir(root):
+ return base([_('Download directory not found.')])
+ data = {
+ 'folder': [],
+ 'files': []
+ }
+
+ items = listdir(fs_encode(root))
+
+ for item in sorted([fs_decode(x) for x in items]):
+ if isdir(fs_join(root, item)):
+ folder = {
+ 'name': item,
+ 'path': item,
+ 'files': []
+ }
+ files = listdir(fs_join(root, item))
+ for file in sorted([fs_decode(x) for x in files]):
+ try:
+ if isfile(fs_join(root, item, file)):
+ folder['files'].append(file)
+ except Exception:
+ pass
+
+ data['folder'].append(folder)
+ elif isfile(join(root, item)):
+ data['files'].append(item)
+
+ return render_to_response('downloads.html', {'files': data}, [pre_processor])
+
+
+@route('/downloads/get/<path:path>')
+@login_required("DOWNLOAD")
+def get_download(path):
+ path = unquote(path).decode("utf8")
+ #@TODO some files can not be downloaded
+
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ path = path.replace("..", "")
+ return static_file(fs_encode(path), fs_encode(root))
+
+
+@route('/settings')
+@login_required('SETTINGS')
+def config():
+ conf = PYLOAD.getConfig()
+ plugin = PYLOAD.getPluginConfig()
+ conf_menu = []
+ plugin_menu = []
+
+ for entry in sorted(conf.keys()):
+ conf_menu.append((entry, conf[entry].description))
+
+ last_name = None
+ for entry in sorted(plugin.keys()):
+ desc = plugin[entry].description
+ name, none, type = desc.partition("_")
+
+ if type in PYLOAD.core.pluginManager.TYPES:
+ if name == last_name or len([a for a, b in plugin.iteritems() if b.description.startswith(name + "_")]) > 1:
+ desc = name + " (" + type.title() + ")"
+ else:
+ desc = name
+ last_name = name
+ plugin_menu.append((entry, desc))
+
+ accs = PYLOAD.getAccounts(False)
+
+ for data in accs:
+ if data.trafficleft == -1:
+ data.trafficleft = _("unlimited")
+ elif not data.trafficleft:
+ data.trafficleft = _("not available")
+ else:
+ data.trafficleft = formatSize(data.trafficleft)
+
+ if data.validuntil == -1:
+ data.validuntil = _("unlimited")
+ elif not data.validuntil:
+ data.validuntil = _("not available")
+ else:
+ t = time.localtime(data.validuntil)
+ data.validuntil = time.strftime("%d.%m.%Y - %H:%M:%S", t)
+
+ try:
+ data.options['time'] = data.options['time'][0]
+ except Exception:
+ data.options['time'] = "0:00-0:00"
+
+ if "limitDL" in data.options:
+ data.options['limitdl'] = data.options['limitDL'][0]
+ else:
+ data.options['limitdl'] = "0"
+
+ return render_to_response('settings.html',
+ {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs},
+ 'types': PYLOAD.getAccountTypes()},
+ [pre_processor])
+
+
+@route('/filechooser')
+@route('/pathchooser')
+@route('/filechooser/<file:path>')
+@route('/pathchooser/<path:path>')
+@login_required('STATUS')
+def path(file="", path=""):
+ type = "file" if file else "folder"
+
+ path = os.path.normpath(unquotepath(path))
+
+ if os.path.isfile(path):
+ oldfile = path
+ path = os.path.dirname(path)
+ else:
+ oldfile = ''
+
+ abs = False
+
+ if os.path.isdir(path):
+ if os.path.isabs(path):
+ cwd = os.path.abspath(path)
+ abs = True
+ else:
+ cwd = relpath(path)
+ else:
+ cwd = os.getcwd()
+
+ try:
+ cwd = cwd.encode("utf8")
+ except Exception:
+ pass
+
+ cwd = os.path.normpath(os.path.abspath(cwd))
+ parentdir = os.path.dirname(cwd)
+ if not abs:
+ if os.path.abspath(cwd) == "/":
+ cwd = relpath(cwd)
+ else:
+ cwd = relpath(cwd) + os.path.sep
+ parentdir = relpath(parentdir) + os.path.sep
+
+ if os.path.abspath(cwd) == "/":
+ parentdir = ""
+
+ try:
+ folders = os.listdir(cwd)
+ except Exception:
+ folders = []
+
+ files = []
+
+ for f in folders:
+ try:
+ f = f.decode(getfilesystemencoding())
+ data = {'name': f, 'fullpath': join(cwd, f)}
+ data['sort'] = data['fullpath'].lower()
+ data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f))))
+ data['ext'] = os.path.splitext(f)[1]
+ except Exception:
+ continue
+
+ data['type'] = 'dir' if os.path.isdir(join(cwd, f)) else 'file'
+
+ if os.path.isfile(join(cwd, f)):
+ data['size'] = os.path.getsize(join(cwd, f))
+
+ power = 0
+ while (data['size'] / 1024) > 0.3:
+ power += 1
+ data['size'] /= 1024.
+ units = ('', 'K', 'M', 'G', 'T')
+ data['unit'] = units[power] + 'Byte'
+ else:
+ data['size'] = ''
+
+ files.append(data)
+
+ files = sorted(files, key=itemgetter('type', 'sort'))
+
+ return render_to_response('pathchooser.html',
+ {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile,
+ 'absolute': abs}, [])
+
+
+@route('/logs')
+@route('/logs', method='POST')
+@route('/logs/<item>')
+@route('/logs/<item>', method='POST')
+@login_required('LOGS')
+def logs(item=-1):
+ s = request.environ.get('beaker.session')
+
+ perpage = s.get('perpage', 34)
+ reversed = s.get('reversed', False)
+
+ warning = ""
+ conf = PYLOAD.getConfigValue("log", "file_log")
+ color_template = PYLOAD.getConfigValue("log", "color_template") if PYLOAD.getConfigValue("log", "color_console") else ""
+ if not conf:
+ warning = "Warning: File log is disabled, see settings page."
+
+ perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all'))
+ fro = None
+
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ try:
+ fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
+ except Exception:
+ pass
+ try:
+ perpage = int(request.forms['perpage'])
+ s['perpage'] = perpage
+
+ reversed = bool(request.forms.get('reversed', False))
+ s['reversed'] = reversed
+ except Exception:
+ pass
+
+ s.save()
+
+ try:
+ item = int(item)
+ except Exception:
+ pass
+
+ log = PYLOAD.getLog()
+ if not perpage:
+ item = 1
+
+ if item < 1 or type(item) is not int:
+ item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1
+
+ if type(fro) is datetime: #: we will search for datetime
+ item = -1
+
+ data = []
+ counter = 0
+ perpagecheck = 0
+ for l in log:
+ counter += 1
+
+ if counter >= item:
+ try:
+ date, time, level, message = l.decode("utf8", "ignore").split(" ", 3)
+ dtime = datetime.strptime(date + ' ' + time, '%Y-%m-%d %H:%M:%S')
+ except Exception:
+ dtime = None
+ date = '?'
+ time = ' '
+ level = '?'
+ message = l
+ if item == -1 and dtime is not None and fro <= dtime:
+ item = counter #: found our datetime
+ if item >= 0:
+ data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message})
+ perpagecheck += 1
+ if fro is None and dtime is not None: #: if fro not set set it to first showed line
+ fro = dtime
+ if perpagecheck >= perpage > 0:
+ break
+
+ if fro is None: #: still not set, empty log?
+ fro = datetime.now()
+ if reversed:
+ data.reverse()
+ return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'),
+ 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p),
+ 'iprev': 1 if item - perpage < 1 else item - perpage,
+ 'inext': (item + perpage) if item + perpage < len(log) else item,
+ 'color_template': color_template.title()},
+ [pre_processor])
+
+
+@route('/admin')
+@route('/admin', method='POST')
+@login_required("ADMIN")
+def admin():
+ # convert to dict
+ user = dict((name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems())
+ perms = permlist()
+
+ for data in user.itervalues():
+ data['perms'] = {}
+ get_permission(data['perms'], data['permission'])
+ data['perms']['admin'] = data['role'] is 0
+
+ s = request.environ.get('beaker.session')
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ for name in user:
+ if request.POST.get("%s|admin" % name, False):
+ user[name]['role'] = 0
+ user[name]['perms']['admin'] = True
+ elif name != s['name']:
+ user[name]['role'] = 1
+ user[name]['perms']['admin'] = False
+
+ # set all perms to false
+ for perm in perms:
+ user[name]['perms'][perm] = False
+
+ for perm in request.POST.getall("%s|perms" % name):
+ user[name]['perms'][perm] = True
+
+ user[name]['permission'] = set_permission(user[name]['perms'])
+
+ PYLOAD.setUserPermission(name, user[name]['permission'], user[name]['role'])
+
+ return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor])
+
+
+@route('/setup')
+def setup():
+ return base([_("Run pyload.py -s to access the setup.")])
+
+
+@route('/info')
+def info():
+ conf = PYLOAD.getConfigDict()
+ extra = os.uname() if hasattr(os, "uname") else tuple()
+
+ data = {"python" : sys.version,
+ "os" : " ".join((os.name, sys.platform) + extra),
+ "version" : PYLOAD.getServerVersion(),
+ "folder" : abspath(PYLOAD_DIR), "config": abspath(""),
+ "download" : abspath(conf['general']['download_folder']['value']),
+ "freespace": formatSize(PYLOAD.freeSpace()),
+ "remote" : conf['remote']['port']['value'],
+ "webif" : conf['webui']['port']['value'],
+ "language" : conf['general']['language']['value']}
+
+ return render_to_response("info.html", data, [pre_processor])
diff --git a/pyload/webui/app/utils.py b/pyload/webui/app/utils.py
new file mode 100644
index 000000000..2753b7feb
--- /dev/null
+++ b/pyload/webui/app/utils.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, vuolter
+
+from os.path import join
+
+from bottle import request, HTTPError, redirect, ServerAdapter
+
+from pyload.webui import env, THEME
+
+from pyload.api import has_permission, PERMS, ROLE
+
+
+def render_to_response(file, args={}, proc=[]):
+ for p in proc:
+ args.update(p())
+ path = "tml/" + file
+ return env.get_template(path).render(**args)
+
+
+def parse_permissions(session):
+ perms = dict((x, False) for x in dir(PERMS) if not x.startswith("_"))
+ perms['ADMIN'] = False
+ perms['is_admin'] = False
+
+ if not session.get("authenticated", False):
+ return perms
+
+ if session.get("role") == ROLE.ADMIN:
+ for k in perms.iterkeys():
+ perms[k] = True
+
+ elif session.get("perms"):
+ p = session.get("perms")
+ get_permission(perms, p)
+
+ return perms
+
+
+def permlist():
+ return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"]
+
+
+def get_permission(perms, p):
+ """Returns a dict with permission key
+
+ :param perms: dictionary
+ :param p: bits
+ """
+ for name in permlist():
+ perms[name] = has_permission(p, getattr(PERMS, name))
+
+
+def set_permission(perms):
+ """generates permission bits from dictionary
+
+ :param perms: dict
+ """
+ permission = 0
+ for name in dir(PERMS):
+ if name.startswith("_"):
+ continue
+
+ if name in perms and perms[name]:
+ permission |= getattr(PERMS, name)
+
+ return permission
+
+
+def set_session(request, info):
+ s = request.environ.get('beaker.session')
+ s['authenticated'] = True
+ s['user_id'] = info['id']
+ s['name'] = info['name']
+ s['role'] = info['role']
+ s['perms'] = info['permission']
+ s['template'] = info['template']
+ s.save()
+
+ return s
+
+
+def parse_userdata(session):
+ return {"name" : session.get("name", "Anonymous"),
+ "is_admin" : session.get("role", 1) == 0,
+ "is_authenticated": session.get("authenticated", False)}
+
+
+def login_required(perm=None):
+
+
+ def _dec(func):
+
+
+ def _view(*args, **kwargs):
+ s = request.environ.get('beaker.session')
+ if s.get("name", None) and s.get("authenticated", False):
+ if perm:
+ perms = parse_permissions(s)
+ if perm not in perms or not perms[perm]:
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ return HTTPError(403, "Forbidden")
+ else:
+ return redirect("/nopermission")
+
+ return func(*args, **kwargs)
+ else:
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ return HTTPError(403, "Forbidden")
+ else:
+ return redirect("/login")
+
+ return _view
+
+ return _dec
+
+
+def toDict(obj):
+ return {att: getattr(obj, att) for att in obj.__slots__}
+
+
+class CherryPyWSGI(ServerAdapter):
+
+ def run(self, handler):
+ from wsgiserver import CherryPyWSGIServer
+
+ server = CherryPyWSGIServer((self.host, self.port), handler)
+ server.start()
diff --git a/pyload/webui/filters.py b/pyload/webui/filters.py
new file mode 100644
index 000000000..e11944c94
--- /dev/null
+++ b/pyload/webui/filters.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import os
+from os.path import abspath, commonprefix, join
+
+quotechar = "::/"
+
+try:
+ from os.path import relpath
+except Exception:
+ from posixpath import curdir, sep, pardir
+
+
+ def relpath(path, start=curdir):
+ """Return a relative version of a path"""
+ if not path:
+ raise ValueError("no path specified")
+ start_list = abspath(start).split(sep)
+ path_list = abspath(path).split(sep)
+ # Work out how much of the filepath is shared by start and path.
+ i = len(commonprefix([start_list, path_list]))
+ rel_list = [pardir] * (len(start_list) - i) + path_list[i:]
+ if not rel_list:
+ return curdir
+ return join(*rel_list)
+
+
+def quotepath(path):
+ try:
+ return path.replace("../", quotechar)
+ except AttributeError:
+ return path
+ except Exception:
+ return ""
+
+
+def unquotepath(path):
+ try:
+ return path.replace(quotechar, "../")
+ except AttributeError:
+ return path
+ except Exception:
+ return ""
+
+
+def path_make_absolute(path):
+ p = os.path.abspath(path)
+ if p[-1] == os.path.sep:
+ return p
+ else:
+ return p + os.path.sep
+
+
+def path_make_relative(path):
+ p = relpath(path)
+ if p[-1] == os.path.sep:
+ return p
+ else:
+ return p + os.path.sep
+
+
+def truncate(value, n):
+ if (n - len(value)) < 3:
+ return value[:n] + "..."
+ return value
+
+
+def date(date, format):
+ return date
diff --git a/pyload/webui/middlewares.py b/pyload/webui/middlewares.py
new file mode 100644
index 000000000..c3f4952db
--- /dev/null
+++ b/pyload/webui/middlewares.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+import gzip
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+class StripPathMiddleware(object):
+
+ def __init__(self, app):
+ self.app = app
+
+
+ def __call__(self, e, h):
+ e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
+ return self.app(e, h)
+
+
+class PrefixMiddleware(object):
+
+ def __init__(self, app, prefix="/pyload"):
+ self.app = app
+ self.prefix = prefix
+
+
+ def __call__(self, e, h):
+ path = e['PATH_INFO']
+ if path.startswith(self.prefix):
+ e['PATH_INFO'] = path.replace(self.prefix, "", 1)
+ return self.app(e, h)
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# WSGI middleware
+# Gzip-encodes the response.
+
+
+class GZipMiddleWare(object):
+
+ def __init__(self, application, compress_level=6):
+ self.application = application
+ self.compress_level = int(compress_level)
+
+
+ def __call__(self, environ, start_response):
+ if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
+ # nothing for us to do, so this middleware will
+ # be a no-op:
+ return self.application(environ, start_response)
+ response = GzipResponse(start_response, self.compress_level)
+ app_iter = self.application(environ,
+ response.gzip_start_response)
+ if app_iter is not None:
+ response.finish_response(app_iter)
+
+ return response.write()
+
+
+def header_value(headers, key):
+ for header, value in headers:
+ if key.lower() == header.lower():
+ return value
+
+
+def update_header(headers, key, value):
+ remove_header(headers, key)
+ headers.append((key, value))
+
+
+def remove_header(headers, key):
+ for header, value in headers:
+ if key.lower() == header.lower():
+ headers.remove((header, value))
+ break
+
+
+class GzipResponse(object):
+
+ def __init__(self, start_response, compress_level):
+ self.start_response = start_response
+ self.compress_level = compress_level
+ self.buffer = StringIO()
+ self.compressible = False
+ self.content_length = None
+ self.headers = ()
+
+
+ def gzip_start_response(self, status, headers, exc_info=None):
+ self.headers = headers
+ ct = header_value(headers, 'content-type')
+ ce = header_value(headers, 'content-encoding')
+ cl = header_value(headers, 'content-length')
+ if cl:
+ cl = int(cl)
+ else:
+ cl = 201
+ self.compressible = False
+ if ct and (ct.startswith('text/') or ct.startswith('application/')) and 'zip' not in ct and cl > 200:
+ self.compressible = True
+ if ce:
+ self.compressible = False
+ if self.compressible:
+ headers.append(('content-encoding', 'gzip'))
+ remove_header(headers, 'content-length')
+ self.headers = headers
+ self.status = status
+ return self.buffer.write
+
+
+ def write(self):
+ out = self.buffer
+ out.seek(0)
+ s = out.getvalue()
+ out.close()
+ return [s]
+
+
+ def finish_response(self, app_iter):
+ if self.compressible:
+ output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, fileobj=self.buffer)
+ else:
+ output = self.buffer
+ try:
+ for s in app_iter:
+ output.write(s)
+ if self.compressible:
+ output.close()
+ finally:
+ if hasattr(app_iter, 'close'):
+ try:
+ app_iter.close()
+ except Exception:
+ pass
+
+ content_length = self.buffer.tell()
+ update_header(self.headers, "Content-Length", str(content_length))
+ self.start_response(self.status, self.headers)
diff --git a/pyload/webui/servers/lighttpd_default.conf b/pyload/webui/servers/lighttpd_default.conf
new file mode 100644
index 000000000..9ccd264db
--- /dev/null
+++ b/pyload/webui/servers/lighttpd_default.conf
@@ -0,0 +1,154 @@
+# lighttpd configuration file
+#
+# use it as a base for lighttpd 1.0.0 and above
+#
+# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
+
+############ Options you really have to take care of ####################
+
+## modules to load
+# at least mod_access and mod_accesslog should be loaded
+# all other module should only be loaded if really neccesary
+# - saves some time
+# - saves memory
+server.modules = (
+ "mod_rewrite",
+ "mod_redirect",
+ "mod_alias",
+ "mod_access",
+# "mod_trigger_b4_dl",
+# "mod_auth",
+# "mod_status",
+# "mod_setenv",
+ "mod_fastcgi",
+# "mod_proxy",
+# "mod_simple_vhost",
+# "mod_evhost",
+# "mod_userdir",
+# "mod_cgi",
+# "mod_compress",
+# "mod_ssi",
+# "mod_usertrack",
+# "mod_expire",
+# "mod_secdownload",
+# "mod_rrdtool",
+# "mod_accesslog"
+)
+
+## A static document-root. For virtual hosting take a look at the
+## mod_simple_vhost module.
+server.document-root = "%(path)"
+
+## where to send error-messages to
+server.errorlog = "%(path)/error.log"
+
+# files to check for if .../ is requested
+index-file.names = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+## set the event-handler (read the performance section in the manual)
+# server.event-handler = "freebsd-kqueue" #: needed on OS X
+
+# mimetype mapping
+mimetype.assign = (
+ ".pdf" => "application/pdf",
+ ".sig" => "application/pgp-signature",
+ ".spl" => "application/futuresplash",
+ ".class" => "application/octet-stream",
+ ".ps" => "application/postscript",
+ ".torrent" => "application/x-bittorrent",
+ ".dvi" => "application/x-dvi",
+ ".gz" => "application/x-gzip",
+ ".pac" => "application/x-ns-proxy-autoconfig",
+ ".swf" => "application/x-shockwave-flash",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".tar" => "application/x-tar",
+ ".zip" => "application/zip",
+ ".mp3" => "audio/mpeg",
+ ".m3u" => "audio/x-mpegurl",
+ ".wma" => "audio/x-ms-wma",
+ ".wax" => "audio/x-ms-wax",
+ ".ogg" => "application/ogg",
+ ".wav" => "audio/x-wav",
+ ".gif" => "image/gif",
+ ".jar" => "application/x-java-archive",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".xbm" => "image/x-xbitmap",
+ ".xpm" => "image/x-xpixmap",
+ ".xwd" => "image/x-xwindowdump",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".js" => "text/javascript",
+ ".asc" => "text/plain",
+ ".c" => "text/plain",
+ ".cpp" => "text/plain",
+ ".log" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mov" => "video/quicktime",
+ ".qt" => "video/quicktime",
+ ".avi" => "video/x-msvideo",
+ ".asf" => "video/x-ms-asf",
+ ".asx" => "video/x-ms-asf",
+ ".wmv" => "video/x-ms-wmv",
+ ".bz2" => "application/x-bzip",
+ ".tbz" => "application/x-bzip-compressed-tar",
+ ".tar.bz2" => "application/x-bzip-compressed-tar",
+ # default mime type
+ "" => "application/octet-stream",
+)
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+# mimetype.use-xattr = "enable"
+
+#### accesslog module
+accesslog.filename = "%(path)/access.log"
+
+url.access-deny = ( "~", ".inc" )
+
+$HTTP['url'] =~ "\.pdf$" {
+ server.range-requests = "disable"
+}
+
+static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
+
+server.pid-file = "%(path)/lighttpd.pid"
+server.bind = "%(host)"
+server.port = %(port)
+
+# server.document-root = "/home/user/public_html"
+
+fastcgi.server = (
+ "/pyload.fcgi" => (
+ "main" => (
+ "host" => "127.0.0.1",
+ "port" => 9295,
+ "check-local" => "disable",
+ "docroot" => "/",
+ )
+ ),
+)
+
+alias.url = (
+ "/media/" => "%(media)/",
+ "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/",
+)
+
+url.rewrite-once = (
+ "^(/media.*)$" => "$1",
+ "^(/admin/media.*)$" => "$1",
+ "^/favicon\.ico$" => "/media/img/favicon.ico",
+ "^(/pyload.fcgi.*)$" => "$1",
+ "^(/.*)$" => "/pyload.fcgi$1",
+)
+
+%(ssl)
diff --git a/pyload/webui/servers/nginx_default.conf b/pyload/webui/servers/nginx_default.conf
new file mode 100644
index 000000000..c1cfcafca
--- /dev/null
+++ b/pyload/webui/servers/nginx_default.conf
@@ -0,0 +1,87 @@
+daemon off;
+pid %(path)/nginx.pid;
+worker_processes 2;
+
+error_log %(path)/error.log info;
+
+events {
+ worker_connections 1024;
+ use epoll;
+}
+
+http {
+ include /etc/nginx/conf/mime.types;
+ default_type application/octet-stream;
+
+ %(ssl)
+
+ log_format main
+ '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $bytes_sent '
+ '"$http_referer" "$http_user_agent" '
+ '"$gzip_ratio"';
+
+ error_log %(path)/error.log info;
+
+ client_header_timeout 10m;
+ client_body_timeout 10m;
+ send_timeout 10m;
+
+ client_body_temp_path %(path)/client_body_temp;
+ proxy_temp_path %(path)/proxy_temp;
+ fastcgi_temp_path %(path)/fastcgi_temp;
+
+
+ connection_pool_size 256;
+ client_header_buffer_size 1k;
+ large_client_header_buffers 4 2k;
+ request_pool_size 4k;
+
+ gzip on;
+ gzip_min_length 1100;
+ gzip_buffers 4 8k;
+ gzip_types text/plain;
+
+ output_buffers 1 32k;
+ postpone_output 1460;
+
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+
+ keepalive_timeout 75 20;
+
+ ignore_invalid_headers on;
+
+ server {
+ listen %(port);
+ server_name %(host);
+ # site_media - folder in uri for static files
+ location ^~ /media {
+ root %(media)/..;
+ }
+ location ^~ /admin/media {
+ root /usr/lib/python%(version)/site-packages/django/contrib;
+ }
+location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
+ access_log off;
+ expires 30d;
+}
+ location / {
+ # host and port to fastcgi server
+ fastcgi_pass 127.0.0.1:9295;
+ fastcgi_param PATH_INFO $fastcgi_script_name;
+ fastcgi_param REQUEST_METHOD $request_method;
+ fastcgi_param QUERY_STRING $query_string;
+ fastcgi_param CONTENT_TYPE $content_type;
+ fastcgi_param CONTENT_LENGTH $content_length;
+ fastcgi_param SERVER_NAME $server_name;
+ fastcgi_param SERVER_PORT $server_port;
+ fastcgi_param SERVER_PROTOCOL $server_protocol;
+ fastcgi_pass_header Authorization;
+ fastcgi_intercept_errors off;
+ }
+ access_log %(path)/access.log main;
+ error_log %(path)/error.log;
+ }
+ }
diff --git a/pyload/webui/themes/Dark/css/MooDialog.css b/pyload/webui/themes/Dark/css/MooDialog.css
new file mode 100644
index 000000000..3c070efa9
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/MooDialog.css
@@ -0,0 +1,94 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+ color:white;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background: url(../img/dark-bg.jpg);
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../lib/MooTools/MooDialog/css/dialog-close.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+ color:white;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+ color:white;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Dark/css/base.css b/pyload/webui/themes/Dark/css/base.css
new file mode 100644
index 000000000..80dc363e1
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/base.css
@@ -0,0 +1,962 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+
+
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+
+.dokuwiki div.plugin_translation ul li a:hover img {
+ opacity:1.0;
+ height:15px;
+}
+
+body {
+ margin:0;
+ padding:0;
+ background-image: url(../img/dark-bg.jpg);
+ color:white;
+ font-size:12px;
+ font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
+ font-family:sans-serif;
+ font-size:99, 96%;
+ font-size-adjust:none;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:normal;
+ line-height:normal;
+}
+hr {
+ border-width:0;
+ border-bottom:1px #aaa dotted;
+}
+img {
+ border:none;
+}
+form {
+ margin:0px;
+ padding:0px;
+ border:none;
+ display:inline;
+ background:transparent;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+a {
+ color:#3465a4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+
+option {
+ border:0 none #fff;
+}
+strong.highlight {
+ background-color:#fc9;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ height:1px;
+ color:#c0c0c0;
+ background-color:#c0c0c0;
+ border:none;
+ margin:.2em 0 .2em 0;
+}
+
+.invisible {
+ margin:0px;
+ border:0px;
+ padding:0px;
+ height:0px;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ padding:40px 40px 10px 40px;
+ font-size:127%;
+}
+div#content {
+ margin-top:-20px;
+ padding:0;
+ font-size:14px;
+ color:white;
+ line-height:1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ background:transparent none repeat scroll 0 0;
+ border-bottom:1px solid #aaa;
+ color:white;
+ font-weight:normal;
+ margin:0;
+ padding:0;
+ padding-bottom:0.17em;
+ padding-top:0.5em;
+}
+h1 {
+ font-size:188%;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ padding-bottom:0;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ float:right;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ white-space: nowrap;
+ border-radius:5px;
+ -moz-border-radius:5px;
+ border:1px solid grey;
+}
+ul#user-actions {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ -moz-border-radius:3px;
+ border-radius:3px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ text-decoration:none;
+ color:white;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+ /*text-decoration:underline;*/
+}
+/***************************/
+ul#page-actions2 {
+ float:left;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ border-radius:5px;
+ -moz-border-radius:5px;
+ border:1px solid grey;
+}
+ul#user-actions2 {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ text-decoration:none;
+ color:white;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
+ul#page-actions-more a:hover, ul#page-actions-more a:focus{
+ color: #4e7bb4;
+}
+/****************************/
+.hidden {
+ display:none;
+}
+
+a.logout {
+ background:transparent url(../img/user-actions-logout.png) 0px 1px no-repeat;
+}
+
+a.info {
+ background:transparent url(../img/user-info.png) 0px 1px no-repeat;
+}
+
+a.admin {
+ background:transparent url(../img/user-actions-admin.png) 0px 1px no-repeat;
+}
+a.profile {
+ background:transparent url(../img/user-actions-profile.png) 0px 1px no-repeat;
+}
+a.create, a.edit {
+ background:transparent url(../img/page-tools-edit.png) 0px 1px no-repeat;
+}
+a.source, a.show {
+ background:transparent url(../img/page-tools-source.png) 0px 1px no-repeat;
+}
+a.revisions {
+ background:transparent url(../img/page-tools-revisions.png) 0px 1px no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background:transparent url(../img/page-tools-subscribe.png) 0px 1px no-repeat;
+}
+a.backlink {
+ background:transparent url(../img/page-tools-backlinks.png) 0px 1px no-repeat;
+}
+a.play {
+ background:transparent url(../img/control_play.png) 0px 1px no-repeat;
+}
+.time {
+ background:transparent url(../img/status_None.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+.reconnect {
+ background:transparent url(../img/reconnect.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+a.play:hover {
+ background:transparent url(../img/control_play_blue.png) 0px 1px no-repeat;
+}
+a.cancel {
+ background:transparent url(../img/control_cancel.png) 0px 1px no-repeat;
+}
+a.cancel:hover {
+ background:transparent url(../img/control_cancel_blue.png) 0px 1px no-repeat;
+}
+a.pause {
+ background:transparent url(../img/control_pause.png) 0px 1px no-repeat;
+}
+a.pause:hover {
+ background:transparent url(../img/control_pause_blue.png) 0px 1px no-repeat;
+ font-weight: bold;
+}
+a.stop {
+ background:transparent url(../img/control_stop.png) 0px 1px no-repeat;
+}
+a.stop:hover {
+ background:transparent url(../img/control_stop_blue.png) 0px 1px no-repeat;
+}
+a.add {
+ background:transparent url(../img/control_add.png) 0px 1px no-repeat;
+}
+a.add:hover {
+ background:transparent url(../img/control_add_blue.png) 0px 1px no-repeat;
+}
+a.cog {
+ background:transparent url(../img/cog.png) 0px 1px no-repeat;
+}
+#head-panel {
+ background:#525252 url(../img/head_bg1.png) bottom left repeat-x;
+}
+#head-panel h1 {
+ display:none;
+ margin:0;
+ text-decoration:none;
+ padding-top:0.8em;
+ padding-left:3.3em;
+ font-size:2.6em;
+ color:#eeeeec;
+}
+#head-panel #head-logo {
+ float:left;
+ margin:5px 0 -15px 5px;
+ padding:0;
+ overflow:visible;
+}
+#head-menu {
+ background:transparent url(../img/tabs-border-bottom.png) 0 100% repeat-x;
+ width:100%;
+ float:left;
+ margin:0;
+ padding:0;
+ padding-top:0.8em;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ margin:0;
+ margin-left:0.3em;
+ font-size:14px;
+ margin-bottom:4px;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0px;
+}
+#head-menu ul li a img {
+ height:22px;
+ width:22px;
+ vertical-align:middle;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ float:left;
+ text-decoration:none;
+ color:white;
+ background: url(../img/dark-bg.jpg) 0 100% repeat-x;
+ padding:3px 7px 3px 7px;
+ border:2px solid #ccc;
+ border-bottom:0px solid transparent;
+ padding-bottom:3px;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ color:#3465a4;
+ background-image: url(../img/dark-bg.jpg);
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ outline:none;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ color:white;
+ background-image: url(../img/dark-bg.jpg);
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#3465a4;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 1em 0 0;
+ background-color:#222;
+ padding:7px 7px 5px 5px;
+ color:white;
+ white-space: nowrap;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright:6px;
+ -moz-border-radius-bottomleft:6px;
+ border-right:1px solid grey;
+ border-left:1px solid grey;
+ border-bottom:1px solid grey;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ border:2px solid #888;
+ background:#eee;
+ font-size:14px;
+ padding:2px;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+div#head-search-and-login form input:focus {
+ background:#fff;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ width:80px;
+ font-size:14px;
+}
+#pageinfo {
+ clear:both;
+ color:#888;
+ padding:0.6em 0;
+ margin:0;
+}
+#foot {
+ font-style:normal;
+ color:#888;
+ text-align:center;
+}
+#foot a {
+ color:#aaf;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ border:1px dotted #888;
+ background:#f0f0f0;
+ margin:1em 0 1em 1em;
+ float:right;
+ font-size:95%;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0;
+ padding:0;
+ margin-left:1em;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ font-size:90%;
+ border:1px dotted #888;
+ padding:0ex 1ex 1ex 1ex;
+ background:#f7f6f2;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ text-align:right;
+ font-weight:bold;
+}
+label.simple {
+ display:block;
+ text-align:left;
+ font-weight:normal;
+}
+label.block input.edit {
+ width:50%;
+}
+/*fieldset {
+ width:300px;
+ text-align:center;
+ padding:0.5em;
+ margin:auto;
+}
+*/
+div.editor {
+ margin:0 0 0 0;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ padding:0 2em;
+ margin:0 0 1em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ clear:both;
+ padding:5px 10px;
+ background-color:#eee;
+ border-bottom:2px solid #ccc;
+}
+#message p {
+ margin:5px 0;
+ padding:0;
+ font-weight:bold;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:bold;
+}
+.boxtext {
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ color:#000;
+ float:none;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ width:32px;
+ height:32px;
+ float:left;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ cursor:pointer
+}
+.dlsize {
+ float:left;
+ padding-right: 8px;
+}
+.dlspeed {
+ float:left;
+ padding-right: 8px;
+}
+.package {
+ margin-bottom: 10px;
+}
+.packagename {
+ font-weight: bold;
+}
+
+.child {
+ margin-left: 20px;
+}
+.child_status {
+ margin-right: 10px;
+}
+.child_secrow {
+ font-size: 10px;
+}
+
+.header, .header th {
+ text-align: left;
+ font-weight: normal;
+ background-color:#202020;
+ border-top:1px solid grey;
+ border-bottom:1px solid grey;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+.progress_bar {
+ background: #0C0;
+ height: 5px;
+
+}
+
+.queue {
+ border: none
+}
+
+.queue tr td {
+ border: none
+}
+
+.header, .header th{
+ text-align: left;
+ font-weight: normal;
+}
+
+
+.clearer
+{
+ clear: both;
+ height: 1px;
+}
+
+.left
+{
+ float: left;
+}
+
+.right
+{
+ float: right;
+}
+
+
+.setfield
+{
+ display: table-cell;
+}
+
+ul.tabs li a
+{
+ padding: 5px 16px 4px 15px;
+ border: none;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+
+#tabs span
+{
+ display: none;
+}
+
+#tabs span.selected
+{
+ display: inline;
+}
+
+#tabsback
+{
+ background-color: #525252;
+ margin: 2px 0 0;
+ padding: 6px 4px 1px 4px;
+
+ border-top-right-radius: 30px;
+ border-top-left-radius: 3px;
+ -moz-border-radius-topright: 30px;
+ -moz-border-radius-topleft: 3px;
+}
+ul.tabs
+{
+ list-style-type: none;
+ margin:0;
+ padding: 0 40px 0 0;
+}
+
+ul.tabs li
+{
+ display: inline;
+ margin-left: 8px;
+}
+
+ul.tabs li a
+{
+ color: white;
+ background-color: #202020;
+ border: 1px solid grey;
+ border-bottom:none;
+ margin: 0;
+ text-decoration: none;
+
+ outline: 0;
+
+ padding: 5px 16px 4px 15px;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+ul.tabs li a.selected, ul.tabs li a:hover
+{
+ color: #3465a4;
+ background-color: white;
+
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ -moz-border-radius-bottomleft: 0;
+}
+
+ul.tabs li a:hover
+{
+ background-color: #202020;
+}
+
+ul.tabs li a.selected
+{
+ font-weight: bold;
+ background-color: #525252;
+ padding-bottom: 5px;
+ color: white;
+}
+
+
+#tabs-body {
+ position: relative;
+ overflow: hidden;
+}
+
+
+span.tabContent
+{
+ border: 2px solid #525252;
+ margin: 0;
+ padding: 0;
+ padding-bottom: 10px;
+}
+
+#tabs-body > span {
+ display: none;
+}
+
+#tabs-body > span.active {
+ display: block;
+}
+
+.hide
+{
+ display: none;
+}
+
+.settable
+{
+ color:white;
+ margin: 20px;
+ border: none;
+}
+.settable td
+{
+ border: none;
+ margin: 0;
+ padding: 5px;
+}
+
+.settable th{
+ padding-bottom: 8px;
+}
+
+.settable.wide td , .settable.wide th {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+.settable input {
+background-color:#202020;
+color:white;
+}
+.settable select {
+background-color:#202020;
+color:white;
+}
+
+ul.nav {
+ margin: -30px 0 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+}
+
+
+ul.nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+ul.nav > li a {
+ background: #202020;
+ -moz-border-radius: 4px 4px 4px 4px;
+ border: 1px solid grey;
+ border-bottom: medium none;
+ color: white;
+}
+
+ul.nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ border: 1px solid #AAA;
+ background: #202020;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+ cursor: pointer;
+}
+
+ul.nav .open {
+ display: block;
+}
+
+ul.nav .close {
+ display: none;
+}
+
+ul.nav ul li {
+ float: none;
+ padding: 0;
+}
+
+ul.nav ul li a {
+ width: 130px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ font-weight: normal;
+}
+
+ul.nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+ul.nav ul ul {
+ left: 137px;
+ top: 0;
+}
+
+.purr-wrapper{
+ margin:10px;
+}
+
+/*Purr alert styles*/
+
+.purr-alert{
+ margin-bottom:10px;
+ padding:10px;
+ background:#000;
+ font-size:13px;
+ font-weight:bold;
+ color:#FFF;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+ /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
+ width:300px;
+}
+.purr-alert.error{
+ color:#F55;
+ padding-left:30px;
+ background:url(../img/error.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.success{
+ color:#5F5;
+ padding-left:30px;
+ background:url(../img/success.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.notice{
+ color:#99F;
+ padding-left:30px;
+ background:url(../img/notice.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+
+table.system {
+ border: none;
+ margin-left: 10px;
+}
+
+table.system td {
+ border: none
+}
+
+table.system tr > td:first-child {
+ font-weight: bold;
+ padding-right: 10px;
+}
+
+#foot {
+color:white;
+}
+
+#login_table {
+margin-left:auto;
+margin-right:auto;
+}
+
+#login_table td {
+padding:5px;
+border:1px solid grey;
+}
+
+#login_table input[type=text], #login_table input[type=password] {
+width:120px;
+background-color:transparent;
+-moz-opacity: 0.10;
+color:white;
+border: 1px solid grey;
+-moz-border-radius: 2px;
+-webkit-border-radius: 2px;
+border-radius: 18px;
+padding-left:5px;
+padding-right:5px;
+}
+
+#login_table input[type=text]:focus, #login_table input[type=password]:focus {
+border:1px solid #3465a4;
+}
+
+#login_table input[type=submit] {
+background-color:transparent;
+color:white;
+border: 1px solid grey;
+-moz-border-radius: 2px;
+-webkit-border-radius: 2px;
+border-radius: 18px;
+}
+
+#login_table input[type=submit]:hover {
+border:1px solid #3465a4;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/Dark/css/log.css b/pyload/webui/themes/Dark/css/log.css
new file mode 100644
index 000000000..3362e9381
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/log.css
@@ -0,0 +1,75 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+ background-color:#202020;
+ color:white;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+ color: white;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #202020;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/Dark/css/pathchooser.css b/pyload/webui/themes/Dark/css/pathchooser.css
new file mode 100644
index 000000000..204812aa1
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #F0F0F0;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/pyload/webui/themes/Dark/css/window.css b/pyload/webui/themes/Dark/css/window.css
new file mode 100644
index 000000000..1bcc58e6c
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/window.css
@@ -0,0 +1,92 @@
+/* ----------- stylized ----------- */
+.window_table td {
+border:none;
+text-align:left;
+}
+#add_box {
+background-image: url(../img/dark-bg.jpg);
+color:white;
+}
+#pack_box {
+background-image: url(../img/dark-bg.jpg);
+color:white;
+}
+.window_box h1{
+ font-size:14px;
+ font-weight:bold;
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:white;
+ margin-bottom:20px;
+ border-bottom:solid 1px #b7ddf2;
+ padding-bottom:10px;
+}
+.window_box label{ /*Linke Seite*/
+ display:block;
+ font-weight:bold;
+ text-align:right;
+ width:240px;
+ float:left;
+ color:white;
+}
+.window_box .small{
+ color:grey;
+ display:block;
+ font-size:11px;
+ font-weight:normal;
+ text-align:right;
+ width:240px;
+}
+.window_box select, .window_box input{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+ background-color:#202020;
+ color:white;
+}
+.window_box .cont{
+ float:left;
+ font-size:12px;
+ padding: 0px 10px 15px 0px;
+ width:300px;
+ margin:0px 0px 0px 10px;
+ color:white;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+ color:white;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+ background-color:#202020;
+ color:white;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background:#666666 url(../img/button.png) no-repeat;
+ text-align:center;
+ line-height:31px;
+ color:#FFFFFF;
+ font-size:11px;
+ font-weight:bold;
+ border: 0px;
+}
+
+.styled_button {
+ margin-left: 15px;
+ cursor: pointer;
+}
diff --git a/module/web/media/default/img/add_folder.png b/pyload/webui/themes/Dark/img/add_folder.png
index 8acbc411b..8acbc411b 100644
--- a/module/web/media/default/img/add_folder.png
+++ b/pyload/webui/themes/Dark/img/add_folder.png
Binary files differ
diff --git a/module/web/media/default/img/ajax-loader.gif b/pyload/webui/themes/Dark/img/ajax-loader.gif
index 2fd8e0737..2fd8e0737 100644
--- a/module/web/media/default/img/ajax-loader.gif
+++ b/pyload/webui/themes/Dark/img/ajax-loader.gif
Binary files differ
diff --git a/module/web/media/default/img/arrow_refresh.png b/pyload/webui/themes/Dark/img/arrow_refresh.png
index 0de26566d..0de26566d 100644
--- a/module/web/media/default/img/arrow_refresh.png
+++ b/pyload/webui/themes/Dark/img/arrow_refresh.png
Binary files differ
diff --git a/module/web/media/default/img/arrow_right.png b/pyload/webui/themes/Dark/img/arrow_right.png
index b1a181923..b1a181923 100644
--- a/module/web/media/default/img/arrow_right.png
+++ b/pyload/webui/themes/Dark/img/arrow_right.png
Binary files differ
diff --git a/module/web/media/default/img/big_button.gif b/pyload/webui/themes/Dark/img/big_button.gif
index 7680490ea..7680490ea 100644
--- a/module/web/media/default/img/big_button.gif
+++ b/pyload/webui/themes/Dark/img/big_button.gif
Binary files differ
diff --git a/module/web/media/default/img/big_button_over.gif b/pyload/webui/themes/Dark/img/big_button_over.gif
index 2e3ee10d2..2e3ee10d2 100644
--- a/module/web/media/default/img/big_button_over.gif
+++ b/pyload/webui/themes/Dark/img/big_button_over.gif
Binary files differ
diff --git a/module/web/media/default/img/body.png b/pyload/webui/themes/Dark/img/body.png
index 7ff1043e0..7ff1043e0 100644
--- a/module/web/media/default/img/body.png
+++ b/pyload/webui/themes/Dark/img/body.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/img/button.png b/pyload/webui/themes/Dark/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/Dark/img/button.png
Binary files differ
diff --git a/module/web/media/default/img/closebtn.gif b/pyload/webui/themes/Dark/img/closebtn.gif
index 3e27e6030..3e27e6030 100644
--- a/module/web/media/default/img/closebtn.gif
+++ b/pyload/webui/themes/Dark/img/closebtn.gif
Binary files differ
diff --git a/module/web/media/default/img/cog.png b/pyload/webui/themes/Dark/img/cog.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/cog.png
+++ b/pyload/webui/themes/Dark/img/cog.png
Binary files differ
diff --git a/module/web/media/default/img/control_add.png b/pyload/webui/themes/Dark/img/control_add.png
index d39886893..d39886893 100644
--- a/module/web/media/default/img/control_add.png
+++ b/pyload/webui/themes/Dark/img/control_add.png
Binary files differ
diff --git a/module/web/media/default/img/control_add_blue.png b/pyload/webui/themes/Dark/img/control_add_blue.png
index d11b7f41d..d11b7f41d 100644
--- a/module/web/media/default/img/control_add_blue.png
+++ b/pyload/webui/themes/Dark/img/control_add_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel.png b/pyload/webui/themes/Dark/img/control_cancel.png
index 7b9bc3fba..7b9bc3fba 100644
--- a/module/web/media/default/img/control_cancel.png
+++ b/pyload/webui/themes/Dark/img/control_cancel.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel_blue.png b/pyload/webui/themes/Dark/img/control_cancel_blue.png
index 0c5c96ce3..0c5c96ce3 100644
--- a/module/web/media/default/img/control_cancel_blue.png
+++ b/pyload/webui/themes/Dark/img/control_cancel_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause.png b/pyload/webui/themes/Dark/img/control_pause.png
index 2d9ce9c4e..2d9ce9c4e 100644
--- a/module/web/media/default/img/control_pause.png
+++ b/pyload/webui/themes/Dark/img/control_pause.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause_blue.png b/pyload/webui/themes/Dark/img/control_pause_blue.png
index ec61099b0..ec61099b0 100644
--- a/module/web/media/default/img/control_pause_blue.png
+++ b/pyload/webui/themes/Dark/img/control_pause_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_play.png b/pyload/webui/themes/Dark/img/control_play.png
index 0846555d0..0846555d0 100644
--- a/module/web/media/default/img/control_play.png
+++ b/pyload/webui/themes/Dark/img/control_play.png
Binary files differ
diff --git a/module/web/media/default/img/control_play_blue.png b/pyload/webui/themes/Dark/img/control_play_blue.png
index f8c8ec683..f8c8ec683 100644
--- a/module/web/media/default/img/control_play_blue.png
+++ b/pyload/webui/themes/Dark/img/control_play_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop.png b/pyload/webui/themes/Dark/img/control_stop.png
index 893bb60e5..893bb60e5 100644
--- a/module/web/media/default/img/control_stop.png
+++ b/pyload/webui/themes/Dark/img/control_stop.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop_blue.png b/pyload/webui/themes/Dark/img/control_stop_blue.png
index e6f75d232..e6f75d232 100644
--- a/module/web/media/default/img/control_stop_blue.png
+++ b/pyload/webui/themes/Dark/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/img/dark-bg.jpg b/pyload/webui/themes/Dark/img/dark-bg.jpg
new file mode 100644
index 000000000..637fa6b93
--- /dev/null
+++ b/pyload/webui/themes/Dark/img/dark-bg.jpg
Binary files differ
diff --git a/module/web/media/default/img/delete.png b/pyload/webui/themes/Dark/img/delete.png
index 08f249365..08f249365 100644
--- a/module/web/media/default/img/delete.png
+++ b/pyload/webui/themes/Dark/img/delete.png
Binary files differ
diff --git a/module/web/media/default/img/drag_corner.gif b/pyload/webui/themes/Dark/img/drag_corner.gif
index befb1adf1..befb1adf1 100644
--- a/module/web/media/default/img/drag_corner.gif
+++ b/pyload/webui/themes/Dark/img/drag_corner.gif
Binary files differ
diff --git a/module/web/media/default/img/error.png b/pyload/webui/themes/Dark/img/error.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/error.png
+++ b/pyload/webui/themes/Dark/img/error.png
Binary files differ
diff --git a/module/web/media/default/img/folder.png b/pyload/webui/themes/Dark/img/folder.png
index 784e8fa48..784e8fa48 100644
--- a/module/web/media/default/img/folder.png
+++ b/pyload/webui/themes/Dark/img/folder.png
Binary files differ
diff --git a/module/web/media/default/img/full.png b/pyload/webui/themes/Dark/img/full.png
index fea52af76..fea52af76 100644
--- a/module/web/media/default/img/full.png
+++ b/pyload/webui/themes/Dark/img/full.png
Binary files differ
diff --git a/module/web/media/default/img/head-login.png b/pyload/webui/themes/Dark/img/head-login.png
index b59b7cbbf..b59b7cbbf 100644
--- a/module/web/media/default/img/head-login.png
+++ b/pyload/webui/themes/Dark/img/head-login.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-collector.png b/pyload/webui/themes/Dark/img/head-menu-collector.png
index 861be40bc..861be40bc 100644
--- a/module/web/media/default/img/head-menu-collector.png
+++ b/pyload/webui/themes/Dark/img/head-menu-collector.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-config.png b/pyload/webui/themes/Dark/img/head-menu-config.png
index bbf43d4f3..bbf43d4f3 100644
--- a/module/web/media/default/img/head-menu-config.png
+++ b/pyload/webui/themes/Dark/img/head-menu-config.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-development.png b/pyload/webui/themes/Dark/img/head-menu-development.png
index fad150fe1..fad150fe1 100644
--- a/module/web/media/default/img/head-menu-development.png
+++ b/pyload/webui/themes/Dark/img/head-menu-development.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-download.png b/pyload/webui/themes/Dark/img/head-menu-download.png
index 98c5da9db..98c5da9db 100644
--- a/module/web/media/default/img/head-menu-download.png
+++ b/pyload/webui/themes/Dark/img/head-menu-download.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-home.png b/pyload/webui/themes/Dark/img/head-menu-home.png
index 9d62109aa..9d62109aa 100644
--- a/module/web/media/default/img/head-menu-home.png
+++ b/pyload/webui/themes/Dark/img/head-menu-home.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-index.png b/pyload/webui/themes/Dark/img/head-menu-index.png
index 44d631064..44d631064 100644
--- a/module/web/media/default/img/head-menu-index.png
+++ b/pyload/webui/themes/Dark/img/head-menu-index.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-news.png b/pyload/webui/themes/Dark/img/head-menu-news.png
index 43950ebc9..43950ebc9 100644
--- a/module/web/media/default/img/head-menu-news.png
+++ b/pyload/webui/themes/Dark/img/head-menu-news.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-queue.png b/pyload/webui/themes/Dark/img/head-menu-queue.png
index be98793ce..be98793ce 100644
--- a/module/web/media/default/img/head-menu-queue.png
+++ b/pyload/webui/themes/Dark/img/head-menu-queue.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-recent.png b/pyload/webui/themes/Dark/img/head-menu-recent.png
index fc9b0497f..fc9b0497f 100644
--- a/module/web/media/default/img/head-menu-recent.png
+++ b/pyload/webui/themes/Dark/img/head-menu-recent.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-wiki.png b/pyload/webui/themes/Dark/img/head-menu-wiki.png
index 07cf0102d..07cf0102d 100644
--- a/module/web/media/default/img/head-menu-wiki.png
+++ b/pyload/webui/themes/Dark/img/head-menu-wiki.png
Binary files differ
diff --git a/module/web/media/default/img/head-search-noshadow.png b/pyload/webui/themes/Dark/img/head-search-noshadow.png
index aafdae015..aafdae015 100644
--- a/module/web/media/default/img/head-search-noshadow.png
+++ b/pyload/webui/themes/Dark/img/head-search-noshadow.png
Binary files differ
diff --git a/module/web/media/default/img/head_bg1.png b/pyload/webui/themes/Dark/img/head_bg1.png
index f2848c3cc..f2848c3cc 100644
--- a/module/web/media/default/img/head_bg1.png
+++ b/pyload/webui/themes/Dark/img/head_bg1.png
Binary files differ
diff --git a/module/web/media/default/img/images.png b/pyload/webui/themes/Dark/img/images.png
index 184860d1e..184860d1e 100644
--- a/module/web/media/default/img/images.png
+++ b/pyload/webui/themes/Dark/img/images.png
Binary files differ
diff --git a/module/web/media/default/img/notice.png b/pyload/webui/themes/Dark/img/notice.png
index 12cd1aef9..12cd1aef9 100644
--- a/module/web/media/default/img/notice.png
+++ b/pyload/webui/themes/Dark/img/notice.png
Binary files differ
diff --git a/module/web/media/default/img/package_go.png b/pyload/webui/themes/Dark/img/package_go.png
index aace63ad6..aace63ad6 100644
--- a/module/web/media/default/img/package_go.png
+++ b/pyload/webui/themes/Dark/img/package_go.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-backlinks.png b/pyload/webui/themes/Dark/img/page-tools-backlinks.png
index 3eb6a9ce3..3eb6a9ce3 100644
--- a/module/web/media/default/img/page-tools-backlinks.png
+++ b/pyload/webui/themes/Dark/img/page-tools-backlinks.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-edit.png b/pyload/webui/themes/Dark/img/page-tools-edit.png
index 188e1c12b..188e1c12b 100644
--- a/module/web/media/default/img/page-tools-edit.png
+++ b/pyload/webui/themes/Dark/img/page-tools-edit.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-revisions.png b/pyload/webui/themes/Dark/img/page-tools-revisions.png
index 5c3b8587f..5c3b8587f 100644
--- a/module/web/media/default/img/page-tools-revisions.png
+++ b/pyload/webui/themes/Dark/img/page-tools-revisions.png
Binary files differ
diff --git a/module/web/media/default/img/parseUri.png b/pyload/webui/themes/Dark/img/parseUri.png
index 937bded9d..937bded9d 100644
--- a/module/web/media/default/img/parseUri.png
+++ b/pyload/webui/themes/Dark/img/parseUri.png
Binary files differ
diff --git a/module/web/media/default/img/pencil.png b/pyload/webui/themes/Dark/img/pencil.png
index 0bfecd50e..0bfecd50e 100644
--- a/module/web/media/default/img/pencil.png
+++ b/pyload/webui/themes/Dark/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/img/pyload-logo.png b/pyload/webui/themes/Dark/img/pyload-logo.png
new file mode 100644
index 000000000..e878afee5
--- /dev/null
+++ b/pyload/webui/themes/Dark/img/pyload-logo.png
Binary files differ
diff --git a/module/web/media/default/img/reconnect.png b/pyload/webui/themes/Dark/img/reconnect.png
index 49b269145..49b269145 100644
--- a/module/web/media/default/img/reconnect.png
+++ b/pyload/webui/themes/Dark/img/reconnect.png
Binary files differ
diff --git a/module/web/media/default/img/status_None.png b/pyload/webui/themes/Dark/img/status_None.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_None.png
+++ b/pyload/webui/themes/Dark/img/status_None.png
Binary files differ
diff --git a/module/web/media/default/img/status_downloading.png b/pyload/webui/themes/Dark/img/status_downloading.png
index fb4ebc850..fb4ebc850 100644
--- a/module/web/media/default/img/status_downloading.png
+++ b/pyload/webui/themes/Dark/img/status_downloading.png
Binary files differ
diff --git a/module/web/media/default/img/status_failed.png b/pyload/webui/themes/Dark/img/status_failed.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/status_failed.png
+++ b/pyload/webui/themes/Dark/img/status_failed.png
Binary files differ
diff --git a/module/web/media/default/img/status_finished.png b/pyload/webui/themes/Dark/img/status_finished.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/status_finished.png
+++ b/pyload/webui/themes/Dark/img/status_finished.png
Binary files differ
diff --git a/module/web/media/default/img/status_offline.png b/pyload/webui/themes/Dark/img/status_offline.png
index 0cfd58596..0cfd58596 100644
--- a/module/web/media/default/img/status_offline.png
+++ b/pyload/webui/themes/Dark/img/status_offline.png
Binary files differ
diff --git a/module/web/media/default/img/status_proc.png b/pyload/webui/themes/Dark/img/status_proc.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/status_proc.png
+++ b/pyload/webui/themes/Dark/img/status_proc.png
Binary files differ
diff --git a/module/web/media/default/img/status_queue.png b/pyload/webui/themes/Dark/img/status_queue.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_queue.png
+++ b/pyload/webui/themes/Dark/img/status_queue.png
Binary files differ
diff --git a/module/web/media/default/img/status_waiting.png b/pyload/webui/themes/Dark/img/status_waiting.png
index 2842cc338..2842cc338 100644
--- a/module/web/media/default/img/status_waiting.png
+++ b/pyload/webui/themes/Dark/img/status_waiting.png
Binary files differ
diff --git a/module/web/media/default/img/success.png b/pyload/webui/themes/Dark/img/success.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/success.png
+++ b/pyload/webui/themes/Dark/img/success.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/img/tab-background.png b/pyload/webui/themes/Dark/img/tab-background.png
new file mode 100644
index 000000000..ee96b8407
--- /dev/null
+++ b/pyload/webui/themes/Dark/img/tab-background.png
Binary files differ
diff --git a/module/web/media/default/img/tabs-border-bottom.png b/pyload/webui/themes/Dark/img/tabs-border-bottom.png
index 02440f428..02440f428 100644
--- a/module/web/media/default/img/tabs-border-bottom.png
+++ b/pyload/webui/themes/Dark/img/tabs-border-bottom.png
Binary files differ
diff --git a/module/web/media/default/img/user-actions-logout.png b/pyload/webui/themes/Dark/img/user-actions-logout.png
index 0010931e2..0010931e2 100644
--- a/module/web/media/default/img/user-actions-logout.png
+++ b/pyload/webui/themes/Dark/img/user-actions-logout.png
Binary files differ
diff --git a/module/web/media/default/img/user-actions-profile.png b/pyload/webui/themes/Dark/img/user-actions-profile.png
index 46573fff6..46573fff6 100644
--- a/module/web/media/default/img/user-actions-profile.png
+++ b/pyload/webui/themes/Dark/img/user-actions-profile.png
Binary files differ
diff --git a/module/web/media/default/img/user-info.png b/pyload/webui/themes/Dark/img/user-info.png
index 6e643100f..6e643100f 100644
--- a/module/web/media/default/img/user-info.png
+++ b/pyload/webui/themes/Dark/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/js/admin.coffee b/pyload/webui/themes/Dark/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/Dark/js/admin.min.js b/pyload/webui/themes/Dark/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/Dark/js/base.coffee b/pyload/webui/themes/Dark/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/Dark/js/base.min.js b/pyload/webui/themes/Dark/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/Dark/js/package.js b/pyload/webui/themes/Dark/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/js/settings.coffee b/pyload/webui/themes/Dark/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/Dark/js/settings.min.js b/pyload/webui/themes/Dark/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+name: MooDialog.Confirm
+description: Creates an Confirm Dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
+...
+*/
+
+
+MooDialog.Confirm = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ cancelText: 'Cancel',
+ focus: true,
+ textPClass: 'MooDialogConfirm'
+ },
+
+ initialize: function(msg, fn, fn1, options){
+ this.parent(options);
+ var emptyFn = function(){},
+ self = this;
+
+ var buttons = [
+ {fn: fn || emptyFn, txt: this.options.okText},
+ {fn: fn1 || emptyFn, txt: this.options.cancelText}
+ ].map(function(button){
+ return new Element('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: button.txt
+ });
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(buttons)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if(this.options.focus) this.addEvent('show', function(){
+ buttons[1].focus();
+ });
+
+ }
+});
+
+
+Element.implement({
+
+ confirmLinkClick: function(msg, options){
+ this.addEvent('click', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ location.href = this.get('href');
+ }.bind(this), null, options)
+ });
+ return this;
+ },
+
+ confirmFormSubmit: function(msg, options){
+ this.addEvent('submit', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ this.submit();
+ }.bind(this), null, options)
+ }.bind(this));
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+name: MooDialog.Fx
+description: Overwrite the default events so the Dialogs are using Fx on open and close
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Fx.Tween, Overlay]
+provides: MooDialog.Fx
+...
+*/
+
+
+MooDialog.implement('options', {
+
+ duration: 400,
+ closeOnOverlayClick: true,
+
+ onInitialize: function(wrapper){
+ this.fx = new Fx.Tween(wrapper, {
+ property: 'opacity',
+ duration: this.options.duration
+ }).set(0);
+ this.overlay = new Overlay(this.options.inject, {
+ duration: this.options.duration
+ });
+ if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ this.fx.start(1).chain(function(){
+ this.fireEvent('show');
+ }.bind(this));
+ },
+
+ onBeforeClose: function(wrapper){
+ this.overlay.close();
+ this.fx.start(0).chain(function(){
+ this.fireEvent('hide');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+name: Overlay
+
+authors:
+ - David Walsh (http://davidwalsh.name)
+
+license:
+ - MIT-style license
+
+requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
+
+provides:
+ - Overlay
+...
+*/
+
+var Overlay = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ id: 'overlay',
+ color: '#000',
+ duration: 500,
+ opacity: 0.5,
+ zIndex: 5000/*,
+ onClick: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ initialize: function(container, options){
+ this.setOptions(options);
+ this.container = document.id(container);
+
+ this.bound = {
+ 'window': {
+ resize: this.resize.bind(this),
+ scroll: this.scroll.bind(this)
+ },
+ overlayClick: this.overlayClick.bind(this),
+ tweenStart: this.tweenStart.bind(this),
+ tweenComplete: this.tweenComplete.bind(this)
+ };
+
+ this.build().attach();
+ },
+
+ build: function(){
+ this.overlay = new Element('div', {
+ id: this.options.id,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ return this;
+ }.protect(),
+
+ attach: function(){
+ window.addEvents(this.bound.window);
+ this.overlay.addEvent('click', this.bound.overlayClick);
+ this.tween.addEvents({
+ onStart: this.bound.tweenStart,
+ onComplete: this.bound.tweenComplete
+ });
+ return this;
+ },
+
+ detach: function(){
+ var args = Array.prototype.slice.call(arguments);
+ args.each(function(item){
+ if(item == 'window') window.removeEvents(this.bound.window);
+ if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
+ }, this);
+ return this;
+ },
+
+ overlayClick: function(){
+ this.fireEvent('click');
+ return this;
+ },
+
+ tweenStart: function(){
+ this.overlay.setStyles({
+ width: '100%',
+ height: this.container.getScrollSize().y,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ return this;
+ },
+
+ resize: function(){
+ this.fireEvent('resize');
+ this.overlay.setStyle('height', this.container.getScrollSize().y);
+ return this;
+ },
+
+ scroll: function(){
+ this.fireEvent('scroll');
+ if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ z-index: 50000;
+
+ background: #eef5f8;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .content {
+ height: 100px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/module/web/media/img/dialog-close.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png
index 81ebb88b2..81ebb88b2 100644
--- a/module/web/media/img/dialog-close.png
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/module/web/media/img/dialog-question.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png
index b0af3db5b..b0af3db5b 100644
--- a/module/web/media/img/dialog-question.png
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios'){
+ UA[1] = 'chrome';
+ }
+
+ platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.name == 'ie'){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ window = this.Window = document = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e){
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
+ return node.hasAttribute(attribute);
+ } : function(node, attribute){
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector){
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll){
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e){
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError){}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ }
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return classes(this.className).contains(className);
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function(){
+
+var _keys = {};
+var normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1, message: 2 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e){}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',
+ withCredentials: false,*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (!ready) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ compensateScroll: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+ this.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(offsetParent);
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ this.mouse.start = null;
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+/*
+---
+
+script: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+/*
+---
+
+script: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(function(){
+
+var Swiff = this.Swiff = new Class({
+
+ Implements: Options,
+
+ options: {
+ id: null,
+ height: 1,
+ width: 1,
+ container: null,
+ properties: {},
+ params: {
+ quality: 'high',
+ allowScriptAccess: 'always',
+ wMode: 'window',
+ swLiveConnect: true
+ },
+ callBacks: {},
+ vars: {}
+ },
+
+ toElement: function(){
+ return this.object;
+ },
+
+ initialize: function(path, options){
+ this.instance = 'Swiff_' + String.uniqueID();
+
+ this.setOptions(options);
+ options = this.options;
+ var id = this.id = options.id || this.instance;
+ var container = document.id(options.container);
+
+ Swiff.CallBacks[this.instance] = {};
+
+ var params = options.params, vars = options.vars, callBacks = options.callBacks;
+ var properties = Object.append({height: options.height, width: options.width}, options.properties);
+
+ var self = this;
+
+ for (var callBack in callBacks){
+ Swiff.CallBacks[this.instance][callBack] = (function(option){
+ return function(){
+ return option.apply(self.object, arguments);
+ };
+ })(callBacks[callBack]);
+ vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+ }
+
+ params.flashVars = Object.toQueryString(vars);
+ if ('ActiveXObject' in window){
+ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+ params.movie = path;
+ } else {
+ properties.type = 'application/x-shockwave-flash';
+ }
+ properties.data = path;
+
+ var build = '<object id="' + id + '"';
+ for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+ build += '>';
+ for (var param in params){
+ if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+ }
+ build += '</object>';
+ this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+ },
+
+ replaces: function(element){
+ element = document.id(element, true);
+ element.parentNode.replaceChild(this.toElement(), element);
+ return this;
+ },
+
+ inject: function(element){
+ document.id(element, true).appendChild(this.toElement());
+ return this;
+ },
+
+ remote: function(){
+ return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
+ }
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+ var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+ return eval(rs);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/Dark/tml/admin.html b/pyload/webui/themes/Dark/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/base.html b/pyload/webui/themes/Dark/tml/base.html
new file mode 100644
index 000000000..cc2c93db9
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/base.html
@@ -0,0 +1,177 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a>
+{% if user.is_authenticated %}
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>{% endif %}
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div id="window_popup" style="display: none;">
+ {% include '/tml/window.html' %}
+ {% include '/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/Dark/tml/captcha.html b/pyload/webui/themes/Dark/tml/captcha.html
new file mode 100644
index 000000000..3bfa6cbcf
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/Dark/tml/downloads.html b/pyload/webui/themes/Dark/tml/downloads.html
new file mode 100644
index 000000000..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/folder.html b/pyload/webui/themes/Dark/tml/folder.html
new file mode 100644
index 000000000..21f3f4a03
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Dark/tml/home.html b/pyload/webui/themes/Dark/tml/home.html
new file mode 100644
index 000000000..6ce60de0b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/img/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/info.html b/pyload/webui/themes/Dark/tml/info.html
new file mode 100644
index 000000000..62a8df571
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/info.html
@@ -0,0 +1,76 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+
+ <ul id="twitter_update_list"></ul>
+ <script type="text/javascript" src="http://twitter.com/javascripts/blogger.min.js"></script>
+ <script type="text/javascript" src="http://api.twitter.com/1/statuses/user_timeline.json?screen_name=pyLoad&include_rts=true&count=5&callback=twitterCallback2"></script>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Webinterface Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/login.html b/pyload/webui/themes/Dark/tml/login.html
new file mode 100644
index 000000000..004bc506b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/login.html
@@ -0,0 +1,37 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+{% if errors %}
+<p style="color:red;">{{_("Your username and password didn't match. Please try again.")}}</p>
+{% endif %}
+ <table id="login_table">
+ <tr>
+ <td>{{_("Username")}}</td>
+ <td><input type="text" size="20" name="username" /></td>
+ </tr>
+ <tr>
+ <td>{{_("Password")}}</td>
+ <td><input type="password" size="20" name="password" /></td>
+ </tr>
+<tr>
+<td>&nbsp;</td>
+ <td><input type="submit" value="Login" class="button" /></td>
+</tr>
+</table>
+ </fieldset>
+ </div>
+</form>
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/logout.html b/pyload/webui/themes/Dark/tml/logout.html
new file mode 100644
index 000000000..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/logs.html b/pyload/webui/themes/Dark/tml/logs.html
new file mode 100644
index 000000000..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/css/log.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/pathchooser.html b/pyload/webui/themes/Dark/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html>
diff --git a/pyload/webui/themes/Dark/tml/queue.html b/pyload/webui/themes/Dark/tml/queue.html
new file mode 100644
index 000000000..6e7cb6754
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 4px; border: 1px solid grey; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #525252;"></div>
+ <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/settings.html b/pyload/webui/themes/Dark/tml/settings.html
new file mode 100644
index 000000000..7df0b2f23
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li style="color:white;" id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:grey;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" value="{{account.password}}" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.time}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.limitdl}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Dark/tml/settings_item.html b/pyload/webui/themes/Dark/tml/settings_item.html
new file mode 100644
index 000000000..c7e60865e
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/settings_item.html
@@ -0,0 +1,48 @@
+<table class="settable">
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in sorted_conf(section) %}
+ {% if okey not in ("desc","outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:white;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+</table>
diff --git a/pyload/webui/themes/Dark/tml/window.html b/pyload/webui/themes/Dark/tml/window.html
new file mode 100644
index 000000000..8f5efae36
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/window.html
@@ -0,0 +1,52 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+<table class="window_table">
+<tr>
+ <td>{{_("Queue")}}</td>
+ <td><input type="radio" name="add_dest" id="add_dest" value="1" checked="checked" /></td>
+</tr>
+<tr>
+ <td>{{_("Collector")}}</td>
+ <td><input type="radio" name="add_dest" id="add_dest2" value="0" /></td>
+</tr>
+</table>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
diff --git a/pyload/webui/themes/Default/css/base.css b/pyload/webui/themes/Default/css/base.css
new file mode 100644
index 000000000..f5078bfbb
--- /dev/null
+++ b/pyload/webui/themes/Default/css/base.css
@@ -0,0 +1,902 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+
+
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+
+.dokuwiki div.plugin_translation ul li a:hover img {
+ opacity:1.0;
+ height:15px;
+}
+
+body {
+ margin:0;
+ padding:0;
+ background-color:white;
+ color:black;
+ font-size:12px;
+ font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
+ font-family:sans-serif;
+ font-size:99, 96%;
+ font-size-adjust:none;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:normal;
+ line-height:normal;
+}
+hr {
+ border-width:0;
+ border-bottom:1px #aaa dotted;
+}
+img {
+ border:none;
+}
+form {
+ margin:0px;
+ padding:0px;
+ border:none;
+ display:inline;
+ background:transparent;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+a {
+ color:#3465a4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+
+option {
+ border:0 none #fff;
+}
+strong.highlight {
+ background-color:#fc9;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ height:1px;
+ color:#c0c0c0;
+ background-color:#c0c0c0;
+ border:none;
+ margin:.2em 0 .2em 0;
+}
+
+.invisible {
+ margin:0px;
+ border:0px;
+ padding:0px;
+ height:0px;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ padding:40px 40px 10px 40px;
+ font-size:127%;
+}
+div#content {
+ margin-top:-20px;
+ padding:0;
+ font-size:14px;
+ color:black;
+ line-height:1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ background:transparent none repeat scroll 0 0;
+ border-bottom:1px solid #aaa;
+ color:black;
+ font-weight:normal;
+ margin:0;
+ padding:0;
+ padding-bottom:0.17em;
+ padding-top:0.5em;
+}
+h1 {
+ font-size:188%;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ padding-bottom:0;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ float:right;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ white-space: nowrap;
+ border-radius:5px;
+ -moz-border-radius:5px;
+}
+ul#user-actions {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ -moz-border-radius:3px;
+ border-radius:3px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ text-decoration:none;
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+ /*text-decoration:underline;*/
+}
+/***************************/
+ul#page-actions2 {
+ float:left;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ border-radius:5px;
+ -moz-border-radius:5px;
+}
+ul#user-actions2 {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ text-decoration:none;
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
+ul#page-actions-more a:hover, ul#page-actions-more a:focus{
+ color: #4e7bb4;
+}
+/****************************/
+.hidden {
+ display:none;
+}
+
+a.logout {
+ background:transparent url(../img/user-actions-logout.png) 0px 1px no-repeat;
+}
+
+a.info {
+ background:transparent url(../img/user-info.png) 0px 1px no-repeat;
+}
+
+a.admin {
+ background:transparent url(../img/user-actions-admin.png) 0px 1px no-repeat;
+}
+a.profile {
+ background:transparent url(../img/user-actions-profile.png) 0px 1px no-repeat;
+}
+a.create, a.edit {
+ background:transparent url(../img/page-tools-edit.png) 0px 1px no-repeat;
+}
+a.source, a.show {
+ background:transparent url(../img/page-tools-source.png) 0px 1px no-repeat;
+}
+a.revisions {
+ background:transparent url(../img/page-tools-revisions.png) 0px 1px no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background:transparent url(../img/page-tools-subscribe.png) 0px 1px no-repeat;
+}
+a.backlink {
+ background:transparent url(../img/page-tools-backlinks.png) 0px 1px no-repeat;
+}
+a.play {
+ background:transparent url(../img/control_play.png) 0px 1px no-repeat;
+}
+.time {
+ background:transparent url(../img/status_None.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+.reconnect {
+ background:transparent url(../img/reconnect.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+a.play:hover {
+ background:transparent url(../img/control_play_blue.png) 0px 1px no-repeat;
+}
+a.cancel {
+ background:transparent url(../img/control_cancel.png) 0px 1px no-repeat;
+}
+a.cancel:hover {
+ background:transparent url(../img/control_cancel_blue.png) 0px 1px no-repeat;
+}
+a.pause {
+ background:transparent url(../img/control_pause.png) 0px 1px no-repeat;
+}
+a.pause:hover {
+ background:transparent url(../img/control_pause_blue.png) 0px 1px no-repeat;
+ font-weight: bold;
+}
+a.stop {
+ background:transparent url(../img/control_stop.png) 0px 1px no-repeat;
+}
+a.stop:hover {
+ background:transparent url(../img/control_stop_blue.png) 0px 1px no-repeat;
+}
+a.add {
+ background:transparent url(../img/control_add.png) 0px 1px no-repeat;
+}
+a.add:hover {
+ background:transparent url(../img/control_add_blue.png) 0px 1px no-repeat;
+}
+a.cog {
+ background:transparent url(../img/cog.png) 0px 1px no-repeat;
+}
+#head-panel {
+ background:#525252 url(../img/head_bg1.png) bottom left repeat-x;
+}
+#head-panel h1 {
+ display:none;
+ margin:0;
+ text-decoration:none;
+ padding-top:0.8em;
+ padding-left:3.3em;
+ font-size:2.6em;
+ color:#eeeeec;
+}
+#head-panel #head-logo {
+ float:left;
+ margin:5px 0 -15px 5px;
+ padding:0;
+ overflow:visible;
+}
+#head-menu {
+ background:transparent url(../img/tabs-border-bottom.png) 0 100% repeat-x;
+ width:100%;
+ float:left;
+ margin:0;
+ padding:0;
+ padding-top:0.8em;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ margin:0;
+ margin-left:0.3em;
+ font-size:14px;
+ margin-bottom:4px;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0px;
+}
+#head-menu ul li a img {
+ height:22px;
+ width:22px;
+ vertical-align:middle;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ float:left;
+ text-decoration:none;
+ color:#555;
+ background:#eaeaea url(../img/tab-background.png) 0 100% repeat-x;
+ padding:3px 7px 3px 7px;
+ border:2px solid #ccc;
+ border-bottom:0px solid transparent;
+ padding-bottom:3px;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ color:#111;
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ outline:none;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ color:#3566A5;
+ background:#fff;
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 1em 0 0;
+ background-color:#222;
+ padding:7px 7px 5px 5px;
+ color:white;
+ white-space: nowrap;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright:6px;
+ -moz-border-radius-bottomleft:6px;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ border:2px solid #888;
+ background:#eee;
+ font-size:14px;
+ padding:2px;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+div#head-search-and-login form input:focus {
+ background:#fff;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ width:80px;
+ font-size:14px;
+}
+#pageinfo {
+ clear:both;
+ color:#888;
+ padding:0.6em 0;
+ margin:0;
+}
+#foot {
+ font-style:normal;
+ color:#888;
+ text-align:center;
+}
+#foot a {
+ color:#aaf;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ border:1px dotted #888;
+ background:#f0f0f0;
+ margin:1em 0 1em 1em;
+ float:right;
+ font-size:95%;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0;
+ padding:0;
+ margin-left:1em;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ font-size:90%;
+ border:1px dotted #888;
+ padding:0ex 1ex 1ex 1ex;
+ background:#f7f6f2;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ text-align:right;
+ font-weight:bold;
+}
+label.simple {
+ display:block;
+ text-align:left;
+ font-weight:normal;
+}
+label.block input.edit {
+ width:50%;
+}
+/*fieldset {
+ width:300px;
+ text-align:center;
+ padding:0.5em;
+ margin:auto;
+}
+*/
+div.editor {
+ margin:0 0 0 0;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ padding:0 2em;
+ margin:0 0 1em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ clear:both;
+ padding:5px 10px;
+ background-color:#eee;
+ border-bottom:2px solid #ccc;
+}
+#message p {
+ margin:5px 0;
+ padding:0;
+ font-weight:bold;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:bold;
+}
+.boxtext {
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ color:#000;
+ float:none;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ width:32px;
+ height:32px;
+ float:left;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ cursor:pointer
+}
+.dlsize {
+ float:left;
+ padding-right: 8px;
+}
+.dlspeed {
+ float:left;
+ padding-right: 8px;
+}
+.package {
+ margin-bottom: 10px;
+}
+.packagename {
+ font-weight: bold;
+}
+
+.child {
+ margin-left: 20px;
+}
+.child_status {
+ margin-right: 10px;
+}
+.child_secrow {
+ font-size: 10px;
+}
+
+.header, .header th {
+ text-align: left;
+ font-weight: normal;
+ background-color:#ececec;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+.progress_bar {
+ background: #0C0;
+ height: 5px;
+
+}
+
+.queue {
+ border: none
+}
+
+.queue tr td {
+ border: none
+}
+
+.header, .header th{
+ text-align: left;
+ font-weight: normal;
+}
+
+
+.clearer
+{
+ clear: both;
+ height: 1px;
+}
+
+.left
+{
+ float: left;
+}
+
+.right
+{
+ float: right;
+}
+
+
+.setfield
+{
+ display: table-cell;
+}
+
+ul.tabs li a
+{
+ padding: 5px 16px 4px 15px;
+ border: none;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+
+#tabs span
+{
+ display: none;
+}
+
+#tabs span.selected
+{
+ display: inline;
+}
+
+#tabsback
+{
+ background-color: #525252;
+ margin: 2px 0 0;
+ padding: 6px 4px 1px 4px;
+
+ border-top-right-radius: 30px;
+ border-top-left-radius: 3px;
+ -moz-border-radius-topright: 30px;
+ -moz-border-radius-topleft: 3px;
+}
+ul.tabs
+{
+ list-style-type: none;
+ margin:0;
+ padding: 0 40px 0 0;
+}
+
+ul.tabs li
+{
+ display: inline;
+ margin-left: 8px;
+}
+
+
+ul.tabs li a
+{
+ color: #42454a;
+ background-color: #eaeaea;
+ border: 1px none #c9c3ba;
+ margin: 0;
+ text-decoration: none;
+
+ outline: 0;
+
+ padding: 5px 16px 4px 15px;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+ul.tabs li a.selected, ul.tabs li a:hover
+{
+ color: #000;
+ background-color: white;
+
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ -moz-border-radius-bottomleft: 0;
+}
+
+ul.tabs li a:hover
+{
+ background-color: #f1f4ee;
+}
+
+ul.tabs li a.selected
+{
+ font-weight: bold;
+ background-color: #525252;
+ padding-bottom: 5px;
+ color: white;
+}
+
+
+#tabs-body {
+ position: relative;
+ overflow: hidden;
+}
+
+
+span.tabContent
+{
+ border: 2px solid #525252;
+ margin: 0;
+ padding: 0;
+ padding-bottom: 10px;
+}
+
+#tabs-body > span {
+ display: none;
+}
+
+#tabs-body > span.active {
+ display: block;
+}
+
+.hide
+{
+ display: none;
+}
+
+.settable
+{
+ margin: 20px;
+ border: none;
+}
+.settable td
+{
+ border: none;
+ margin: 0;
+ padding: 5px;
+}
+
+.settable th{
+ padding-bottom: 8px;
+}
+
+.settable.wide td , .settable.wide th {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+
+/*settings navbar*/
+ul.nav {
+ margin: -30px 0 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+}
+
+
+ul.nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+ul.nav > li a {
+ background: white;
+ -moz-border-radius: 4px 4px 4px 4px;
+ border: 1px solid #C9C3BA;
+ border-bottom: medium none;
+ color: black;
+}
+
+ul.nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+ cursor: pointer;
+}
+
+ul.nav .open {
+ display: block;
+}
+
+ul.nav .close {
+ display: none;
+}
+
+ul.nav ul li {
+ float: none;
+ padding: 0;
+}
+
+ul.nav ul li a {
+ width: 130px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ font-weight: normal;
+}
+
+ul.nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+ul.nav ul ul {
+ left: 137px;
+ top: 0;
+}
+
+.purr-wrapper{
+ margin:10px;
+}
+
+/*Purr alert styles*/
+
+.purr-alert{
+ margin-bottom:10px;
+ padding:10px;
+ background:#000;
+ font-size:13px;
+ font-weight:bold;
+ color:#FFF;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+ /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
+ width:300px;
+}
+.purr-alert.error{
+ color:#F55;
+ padding-left:30px;
+ background:url(../img/error.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.success{
+ color:#5F5;
+ padding-left:30px;
+ background:url(../img/success.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.notice{
+ color:#99F;
+ padding-left:30px;
+ background:url(../img/notice.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+
+table.system {
+ border: none;
+ margin-left: 10px;
+}
+
+table.system td {
+ border: none
+}
+
+table.system tr > td:first-child {
+ font-weight: bold;
+ padding-right: 10px;
+}
diff --git a/pyload/webui/themes/Default/css/log.css b/pyload/webui/themes/Default/css/log.css
new file mode 100644
index 000000000..33e67397d
--- /dev/null
+++ b/pyload/webui/themes/Default/css/log.css
@@ -0,0 +1,71 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #EEE;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+}
diff --git a/pyload/webui/themes/Default/css/pathchooser.css b/pyload/webui/themes/Default/css/pathchooser.css
new file mode 100644
index 000000000..204812aa1
--- /dev/null
+++ b/pyload/webui/themes/Default/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #F0F0F0;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/pyload/webui/themes/Default/css/window.css b/pyload/webui/themes/Default/css/window.css
new file mode 100644
index 000000000..b469b4259
--- /dev/null
+++ b/pyload/webui/themes/Default/css/window.css
@@ -0,0 +1,73 @@
+/* ----------- stylized ----------- */
+.window_box h1{
+ font-size:14px;
+ font-weight:bold;
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:#666666;
+ margin-bottom:20px;
+ border-bottom:solid 1px #b7ddf2;
+ padding-bottom:10px;
+}
+.window_box label{
+ display:block;
+ font-weight:bold;
+ text-align:right;
+ width:240px;
+ float:left;
+}
+.window_box .small{
+ color:#666666;
+ display:block;
+ font-size:11px;
+ font-weight:normal;
+ text-align:right;
+ width:240px;
+}
+.window_box select, .window_box input{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box .cont{
+ float:left;
+ font-size:12px;
+ padding: 0px 10px 15px 0px;
+ width:300px;
+ margin:0px 0px 0px 10px;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background:#666666 url(../img/button.png) no-repeat;
+ text-align:center;
+ line-height:31px;
+ color:#FFFFFF;
+ font-size:11px;
+ font-weight:bold;
+ border: 0px;
+}
+
+.styled_button {
+ margin-left: 15px;
+ cursor: pointer;
+}
diff --git a/pyload/webui/themes/Default/img/add_folder.png b/pyload/webui/themes/Default/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/Default/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/ajax-loader.gif b/pyload/webui/themes/Default/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/Default/img/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/Default/img/arrow_refresh.png b/pyload/webui/themes/Default/img/arrow_refresh.png
new file mode 100644
index 000000000..0de26566d
--- /dev/null
+++ b/pyload/webui/themes/Default/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/arrow_right.png b/pyload/webui/themes/Default/img/arrow_right.png
new file mode 100644
index 000000000..b1a181923
--- /dev/null
+++ b/pyload/webui/themes/Default/img/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/big_button.gif b/pyload/webui/themes/Default/img/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/Default/img/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/Default/img/big_button_over.gif b/pyload/webui/themes/Default/img/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/Default/img/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/Default/img/body.png b/pyload/webui/themes/Default/img/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/Default/img/body.png
Binary files differ
diff --git a/module/web/media/default/img/button.png b/pyload/webui/themes/Default/img/button.png
index 890160614..890160614 100644
--- a/module/web/media/default/img/button.png
+++ b/pyload/webui/themes/Default/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/closebtn.gif b/pyload/webui/themes/Default/img/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/Default/img/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/Default/img/cog.png b/pyload/webui/themes/Default/img/cog.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/Default/img/cog.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_add.png b/pyload/webui/themes/Default/img/control_add.png
new file mode 100644
index 000000000..d39886893
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_add_blue.png b/pyload/webui/themes/Default/img/control_add_blue.png
new file mode 100644
index 000000000..d11b7f41d
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_cancel.png b/pyload/webui/themes/Default/img/control_cancel.png
new file mode 100644
index 000000000..7b9bc3fba
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_cancel_blue.png b/pyload/webui/themes/Default/img/control_cancel_blue.png
new file mode 100644
index 000000000..0c5c96ce3
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_pause.png b/pyload/webui/themes/Default/img/control_pause.png
new file mode 100644
index 000000000..2d9ce9c4e
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_pause_blue.png b/pyload/webui/themes/Default/img/control_pause_blue.png
new file mode 100644
index 000000000..ec61099b0
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_play.png b/pyload/webui/themes/Default/img/control_play.png
new file mode 100644
index 000000000..0846555d0
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_play_blue.png b/pyload/webui/themes/Default/img/control_play_blue.png
new file mode 100644
index 000000000..f8c8ec683
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_stop.png b/pyload/webui/themes/Default/img/control_stop.png
new file mode 100644
index 000000000..893bb60e5
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/control_stop_blue.png b/pyload/webui/themes/Default/img/control_stop_blue.png
new file mode 100644
index 000000000..e6f75d232
--- /dev/null
+++ b/pyload/webui/themes/Default/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/delete.png b/pyload/webui/themes/Default/img/delete.png
new file mode 100644
index 000000000..08f249365
--- /dev/null
+++ b/pyload/webui/themes/Default/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/drag_corner.gif b/pyload/webui/themes/Default/img/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/Default/img/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/Default/img/error.png b/pyload/webui/themes/Default/img/error.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/Default/img/error.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/folder.png b/pyload/webui/themes/Default/img/folder.png
new file mode 100644
index 000000000..784e8fa48
--- /dev/null
+++ b/pyload/webui/themes/Default/img/folder.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/full.png b/pyload/webui/themes/Default/img/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/Default/img/full.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-login.png b/pyload/webui/themes/Default/img/head-login.png
new file mode 100644
index 000000000..b59b7cbbf
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-collector.png b/pyload/webui/themes/Default/img/head-menu-collector.png
new file mode 100644
index 000000000..861be40bc
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-config.png b/pyload/webui/themes/Default/img/head-menu-config.png
new file mode 100644
index 000000000..bbf43d4f3
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-development.png b/pyload/webui/themes/Default/img/head-menu-development.png
new file mode 100644
index 000000000..fad150fe1
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-download.png b/pyload/webui/themes/Default/img/head-menu-download.png
new file mode 100644
index 000000000..98c5da9db
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-home.png b/pyload/webui/themes/Default/img/head-menu-home.png
new file mode 100644
index 000000000..9d62109aa
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-index.png b/pyload/webui/themes/Default/img/head-menu-index.png
new file mode 100644
index 000000000..44d631064
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-news.png b/pyload/webui/themes/Default/img/head-menu-news.png
new file mode 100644
index 000000000..43950ebc9
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-queue.png b/pyload/webui/themes/Default/img/head-menu-queue.png
new file mode 100644
index 000000000..be98793ce
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-recent.png b/pyload/webui/themes/Default/img/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-menu-wiki.png b/pyload/webui/themes/Default/img/head-menu-wiki.png
new file mode 100644
index 000000000..07cf0102d
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head-search-noshadow.png b/pyload/webui/themes/Default/img/head-search-noshadow.png
new file mode 100644
index 000000000..aafdae015
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/head_bg1.png b/pyload/webui/themes/Default/img/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/Default/img/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/images.png b/pyload/webui/themes/Default/img/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/Default/img/images.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/notice.png b/pyload/webui/themes/Default/img/notice.png
new file mode 100644
index 000000000..12cd1aef9
--- /dev/null
+++ b/pyload/webui/themes/Default/img/notice.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/package_go.png b/pyload/webui/themes/Default/img/package_go.png
new file mode 100644
index 000000000..aace63ad6
--- /dev/null
+++ b/pyload/webui/themes/Default/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/page-tools-backlinks.png b/pyload/webui/themes/Default/img/page-tools-backlinks.png
new file mode 100644
index 000000000..3eb6a9ce3
--- /dev/null
+++ b/pyload/webui/themes/Default/img/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/page-tools-edit.png b/pyload/webui/themes/Default/img/page-tools-edit.png
new file mode 100644
index 000000000..188e1c12b
--- /dev/null
+++ b/pyload/webui/themes/Default/img/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/page-tools-revisions.png b/pyload/webui/themes/Default/img/page-tools-revisions.png
new file mode 100644
index 000000000..5c3b8587f
--- /dev/null
+++ b/pyload/webui/themes/Default/img/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/parseUri.png b/pyload/webui/themes/Default/img/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/Default/img/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/pencil.png b/pyload/webui/themes/Default/img/pencil.png
new file mode 100644
index 000000000..0bfecd50e
--- /dev/null
+++ b/pyload/webui/themes/Default/img/pencil.png
Binary files differ
diff --git a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png b/pyload/webui/themes/Default/img/pyload-logo.png
index 2443cd8b1..2443cd8b1 100644
--- a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png
+++ b/pyload/webui/themes/Default/img/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/reconnect.png b/pyload/webui/themes/Default/img/reconnect.png
new file mode 100644
index 000000000..49b269145
--- /dev/null
+++ b/pyload/webui/themes/Default/img/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_None.png b/pyload/webui/themes/Default/img/status_None.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_downloading.png b/pyload/webui/themes/Default/img/status_downloading.png
new file mode 100644
index 000000000..fb4ebc850
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_failed.png b/pyload/webui/themes/Default/img/status_failed.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_finished.png b/pyload/webui/themes/Default/img/status_finished.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_offline.png b/pyload/webui/themes/Default/img/status_offline.png
new file mode 100644
index 000000000..0cfd58596
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_proc.png b/pyload/webui/themes/Default/img/status_proc.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_queue.png b/pyload/webui/themes/Default/img/status_queue.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/status_waiting.png b/pyload/webui/themes/Default/img/status_waiting.png
new file mode 100644
index 000000000..2842cc338
--- /dev/null
+++ b/pyload/webui/themes/Default/img/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/success.png b/pyload/webui/themes/Default/img/success.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/Default/img/success.png
Binary files differ
diff --git a/module/web/media/default/img/tab-background.png b/pyload/webui/themes/Default/img/tab-background.png
index 29a5d1991..29a5d1991 100644
--- a/module/web/media/default/img/tab-background.png
+++ b/pyload/webui/themes/Default/img/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/tabs-border-bottom.png b/pyload/webui/themes/Default/img/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/Default/img/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/user-actions-logout.png b/pyload/webui/themes/Default/img/user-actions-logout.png
new file mode 100644
index 000000000..0010931e2
--- /dev/null
+++ b/pyload/webui/themes/Default/img/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/user-actions-profile.png b/pyload/webui/themes/Default/img/user-actions-profile.png
new file mode 100644
index 000000000..46573fff6
--- /dev/null
+++ b/pyload/webui/themes/Default/img/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/Default/img/user-info.png b/pyload/webui/themes/Default/img/user-info.png
new file mode 100644
index 000000000..6e643100f
--- /dev/null
+++ b/pyload/webui/themes/Default/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/Default/js/admin.coffee b/pyload/webui/themes/Default/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Default/js/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/Default/js/admin.min.js b/pyload/webui/themes/Default/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Default/js/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/Default/js/base.coffee b/pyload/webui/themes/Default/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Default/js/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/Default/js/base.min.js b/pyload/webui/themes/Default/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Default/js/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/Default/js/package.js b/pyload/webui/themes/Default/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Default/js/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/Default/js/settings.coffee b/pyload/webui/themes/Default/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Default/js/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/Default/js/settings.min.js b/pyload/webui/themes/Default/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Default/js/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+name: MooDialog.Confirm
+description: Creates an Confirm Dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
+...
+*/
+
+
+MooDialog.Confirm = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ cancelText: 'Cancel',
+ focus: true,
+ textPClass: 'MooDialogConfirm'
+ },
+
+ initialize: function(msg, fn, fn1, options){
+ this.parent(options);
+ var emptyFn = function(){},
+ self = this;
+
+ var buttons = [
+ {fn: fn || emptyFn, txt: this.options.okText},
+ {fn: fn1 || emptyFn, txt: this.options.cancelText}
+ ].map(function(button){
+ return new Element('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: button.txt
+ });
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(buttons)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if(this.options.focus) this.addEvent('show', function(){
+ buttons[1].focus();
+ });
+
+ }
+});
+
+
+Element.implement({
+
+ confirmLinkClick: function(msg, options){
+ this.addEvent('click', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ location.href = this.get('href');
+ }.bind(this), null, options)
+ });
+ return this;
+ },
+
+ confirmFormSubmit: function(msg, options){
+ this.addEvent('submit', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ this.submit();
+ }.bind(this), null, options)
+ }.bind(this));
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+name: MooDialog.Fx
+description: Overwrite the default events so the Dialogs are using Fx on open and close
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Fx.Tween, Overlay]
+provides: MooDialog.Fx
+...
+*/
+
+
+MooDialog.implement('options', {
+
+ duration: 400,
+ closeOnOverlayClick: true,
+
+ onInitialize: function(wrapper){
+ this.fx = new Fx.Tween(wrapper, {
+ property: 'opacity',
+ duration: this.options.duration
+ }).set(0);
+ this.overlay = new Overlay(this.options.inject, {
+ duration: this.options.duration
+ });
+ if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ this.fx.start(1).chain(function(){
+ this.fireEvent('show');
+ }.bind(this));
+ },
+
+ onBeforeClose: function(wrapper){
+ this.overlay.close();
+ this.fx.start(0).chain(function(){
+ this.fireEvent('hide');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+name: Overlay
+
+authors:
+ - David Walsh (http://davidwalsh.name)
+
+license:
+ - MIT-style license
+
+requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
+
+provides:
+ - Overlay
+...
+*/
+
+var Overlay = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ id: 'overlay',
+ color: '#000',
+ duration: 500,
+ opacity: 0.5,
+ zIndex: 5000/*,
+ onClick: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ initialize: function(container, options){
+ this.setOptions(options);
+ this.container = document.id(container);
+
+ this.bound = {
+ 'window': {
+ resize: this.resize.bind(this),
+ scroll: this.scroll.bind(this)
+ },
+ overlayClick: this.overlayClick.bind(this),
+ tweenStart: this.tweenStart.bind(this),
+ tweenComplete: this.tweenComplete.bind(this)
+ };
+
+ this.build().attach();
+ },
+
+ build: function(){
+ this.overlay = new Element('div', {
+ id: this.options.id,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ return this;
+ }.protect(),
+
+ attach: function(){
+ window.addEvents(this.bound.window);
+ this.overlay.addEvent('click', this.bound.overlayClick);
+ this.tween.addEvents({
+ onStart: this.bound.tweenStart,
+ onComplete: this.bound.tweenComplete
+ });
+ return this;
+ },
+
+ detach: function(){
+ var args = Array.prototype.slice.call(arguments);
+ args.each(function(item){
+ if(item == 'window') window.removeEvents(this.bound.window);
+ if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
+ }, this);
+ return this;
+ },
+
+ overlayClick: function(){
+ this.fireEvent('click');
+ return this;
+ },
+
+ tweenStart: function(){
+ this.overlay.setStyles({
+ width: '100%',
+ height: this.container.getScrollSize().y,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ return this;
+ },
+
+ resize: function(){
+ this.fireEvent('resize');
+ this.overlay.setStyle('height', this.container.getScrollSize().y);
+ return this;
+ },
+
+ scroll: function(){
+ this.fireEvent('scroll');
+ if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ z-index: 50000;
+
+ background: #eef5f8;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .content {
+ height: 100px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios'){
+ UA[1] = 'chrome';
+ }
+
+ platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.name == 'ie'){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ window = this.Window = document = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e){
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
+ return node.hasAttribute(attribute);
+ } : function(node, attribute){
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector){
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll){
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e){
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError){}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ }
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return classes(this.className).contains(className);
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function(){
+
+var _keys = {};
+var normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1, message: 2 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e){}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',
+ withCredentials: false,*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (!ready) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ compensateScroll: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+ this.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(offsetParent);
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ this.mouse.start = null;
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+/*
+---
+
+script: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+/*
+---
+
+script: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(function(){
+
+var Swiff = this.Swiff = new Class({
+
+ Implements: Options,
+
+ options: {
+ id: null,
+ height: 1,
+ width: 1,
+ container: null,
+ properties: {},
+ params: {
+ quality: 'high',
+ allowScriptAccess: 'always',
+ wMode: 'window',
+ swLiveConnect: true
+ },
+ callBacks: {},
+ vars: {}
+ },
+
+ toElement: function(){
+ return this.object;
+ },
+
+ initialize: function(path, options){
+ this.instance = 'Swiff_' + String.uniqueID();
+
+ this.setOptions(options);
+ options = this.options;
+ var id = this.id = options.id || this.instance;
+ var container = document.id(options.container);
+
+ Swiff.CallBacks[this.instance] = {};
+
+ var params = options.params, vars = options.vars, callBacks = options.callBacks;
+ var properties = Object.append({height: options.height, width: options.width}, options.properties);
+
+ var self = this;
+
+ for (var callBack in callBacks){
+ Swiff.CallBacks[this.instance][callBack] = (function(option){
+ return function(){
+ return option.apply(self.object, arguments);
+ };
+ })(callBacks[callBack]);
+ vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+ }
+
+ params.flashVars = Object.toQueryString(vars);
+ if ('ActiveXObject' in window){
+ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+ params.movie = path;
+ } else {
+ properties.type = 'application/x-shockwave-flash';
+ }
+ properties.data = path;
+
+ var build = '<object id="' + id + '"';
+ for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+ build += '>';
+ for (var param in params){
+ if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+ }
+ build += '</object>';
+ this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+ },
+
+ replaces: function(element){
+ element = document.id(element, true);
+ element.parentNode.replaceChild(this.toElement(), element);
+ return this;
+ },
+
+ inject: function(element){
+ document.id(element, true).appendChild(this.toElement());
+ return this;
+ },
+
+ remote: function(){
+ return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
+ }
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+ var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+ return eval(rs);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/Default/tml/admin.html b/pyload/webui/themes/Default/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/base.html b/pyload/webui/themes/Default/tml/base.html
new file mode 100644
index 000000000..116fb9b51
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/base.html
@@ -0,0 +1,177 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/lib/MooTools/MooDialog/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("New pyLoad version %s available!") % update}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a>
+
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div style="display: none;">
+ {% include '/tml/window.html' %}
+ {% include '/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/Default/tml/captcha.html b/pyload/webui/themes/Default/tml/captcha.html
new file mode 100644
index 000000000..3bfa6cbcf
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/Default/tml/downloads.html b/pyload/webui/themes/Default/tml/downloads.html
new file mode 100644
index 000000000..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/filemanager.html b/pyload/webui/themes/Default/tml/filemanager.html
new file mode 100644
index 000000000..102d53e7c
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/filemanager.html
@@ -0,0 +1,76 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var fmUI = new FilemanagerUI("url",1);
+});
+</script>
+{% endblock %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+
+{% block subtitle %}
+{{_("FileManager")}}
+{% endblock %}
+
+{% macro display_file(file) %}
+ <li class="file">
+ <input type="hidden" name="path" class="path" value="{{ file.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ file.name }}" />
+ <span>
+ <b>{{ file.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ </span>
+ </span>
+ </li>
+{%- endmacro %}
+
+{% macro display_folder(fld, open = false) -%}
+ <li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
+ <span>
+ <b>{{ fld.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ {% if (fld.folders|length + fld.files|length) > 0 %}
+ {% if open %}
+ <ul>
+ {% else %}
+ <ul style="display:none">
+ {% endif %}
+ {% for child in fld.folders %}
+ {{ display_folder(child) }}
+ {% endfor %}
+ {% for child in fld.files %}
+ {{ display_file(child) }}
+ {% endfor %}
+ </ul>
+ {% else %}
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+ {% endif %}
+ </li>
+{%- endmacro %}
+
+{% block content %}
+
+<div style="clear:both"><!-- --></div>
+
+<ul id="directories-list">
+{{ display_folder(root, true) }}
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/folder.html b/pyload/webui/themes/Default/tml/folder.html
new file mode 100644
index 000000000..21f3f4a03
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Default/tml/home.html b/pyload/webui/themes/Default/tml/home.html
new file mode 100644
index 000000000..6ce60de0b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/img/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/info.html b/pyload/webui/themes/Default/tml/info.html
new file mode 100644
index 000000000..e62a2d6e8
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript">
+ window.addEvent("domready", function() {
+ var ul = new Element('ul#twitter_update_list');
+ var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]');
+ var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
+ $("twitter").adopt(ul, script1, script2);
+ });
+ </script>
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+ <div id="twitter"></div>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Webinterface Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/login.html b/pyload/webui/themes/Default/tml/login.html
new file mode 100644
index 000000000..74bbb70a4
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span>{{_("Username")}}</span>
+ <input type="text" size="20" name="username" />
+ </label>
+ <br />
+ <label>
+ <span>{{_("Password")}}</span>
+ <input type="password" size="20" name="password" />
+ </label>
+ <br />
+ <input type="submit" value="Login" class="button" />
+ </fieldset>
+ </div>
+</form>
+
+{% if errors %}
+<p>{{_("Your username and password didn't match. Please try again.")}}</p>
+ {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/logout.html b/pyload/webui/themes/Default/tml/logout.html
new file mode 100644
index 000000000..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/logs.html b/pyload/webui/themes/Default/tml/logs.html
new file mode 100644
index 000000000..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/css/log.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/pathchooser.html b/pyload/webui/themes/Default/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html>
diff --git a/pyload/webui/themes/Default/tml/queue.html b/pyload/webui/themes/Default/tml/queue.html
new file mode 100644
index 000000000..0affa54e0
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
+ <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/settings.html b/pyload/webui/themes/Default/tml/settings.html
new file mode 100644
index 000000000..e96dcf22a
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:#424242;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.options['time']}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.options['limitdl']}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Default/tml/settings_item.html b/pyload/webui/themes/Default/tml/settings_item.html
new file mode 100644
index 000000000..83a008619
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/settings_item.html
@@ -0,0 +1,48 @@
+<table class="settable">
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in sorted_conf(section) %}
+ {% if okey not in ("desc", "outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:#424242;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+</table>
diff --git a/pyload/webui/themes/Default/tml/window.html b/pyload/webui/themes/Default/tml/window.html
new file mode 100644
index 000000000..023441f69
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/window.html
@@ -0,0 +1,46 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+ {{_("Queue")}}
+ <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
+ {{_("Collector")}}
+ <input type="radio" name="add_dest" id="add_dest2" value="0"/>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
diff --git a/pyload/webui/themes/Flat/css/MooDialog.css b/pyload/webui/themes/Flat/css/MooDialog.css
new file mode 100644
index 000000000..e7fb7c23b
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/MooDialog.css
@@ -0,0 +1,85 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background-color: #E44424;
+ color: black;
+ opacity:0.9;
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../img/control_cancel.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Flat/css/base.css b/pyload/webui/themes/Flat/css/base.css
new file mode 100644
index 000000000..fd73d0a19
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/base.css
@@ -0,0 +1,866 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ border:none !important;
+ color:#FFFFFF !important;
+ margin:0.1em 0.2em;
+ padding:0 0.2em;
+ text-decoration:none;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ border:none !important;
+ color:#FFFFFF !important;
+ margin:0.1em 0.2em;
+ padding:0 0.2em;
+ text-decoration:none;
+}
+.dokuwiki div.plugin_translation ul li a:hover img {
+ height:15px;
+ opacity:1;
+}
+body {
+ background-color:white;
+ color:black;
+ font-family:'Open Sans', sans-serif;
+ font-size:12px;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:300;
+ line-height:normal;
+ margin:0;
+ padding:0;
+}
+hr {
+ border-bottom-color:#AAAAAA;
+ border-bottom-style:dotted;
+}
+img {
+ border:none;
+}
+form {
+ background-color:transparent;
+ border:none;
+ display:inline;
+ margin:0;
+ padding:0;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ border-collapse:collapse;
+ margin:0.5em 0;
+}
+td {
+ border:1pt solid #ADB9CC;
+ padding:0.25em;
+}
+a {
+ color:#3465A4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+option {
+ border:0 none #FFFFFF;
+}
+strong.highlight {
+ background-color:#FFCC99;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ background-color:#C0C0C0;
+ border:none;
+ color:#C0C0C0;
+ margin:0.2em 0;
+}
+.invisible {
+ border:0;
+ height:0;
+ margin:0;
+ padding:0;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ font-size:127%;
+ padding:40px 40px 10px;
+}
+div#content {
+ color:black;
+ font-size:14px;
+ line-height:1.5em;
+ margin-top:-20px;
+ padding:0;
+}
+h1, h2, h3, h4, h5, h6 {
+ background-attachment:scroll;
+ background-color:transparent;
+ background-image:none;
+ background-position:0 0;
+ background-repeat:repeat repeat;
+ color:black;
+ font-family:'Open Sans', sans-serif;
+ font-weight:normal;
+ margin:0;
+ padding:0.5em 0 0.17em;
+}
+h1 {
+ font-family:'Open Sans', sans-serif;
+ font-weight:300;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ padding-bottom:0;
+ margin-left:-25px;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom-style:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ background-color:#ECECEC;
+ color:black;
+ float:right;
+ list-style-type:none;
+ margin:10px 10px 0;
+ padding:6px;
+ white-space:nowrap;
+}
+ul#user-actions {
+ background-color:#67BCDB;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:10px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+ text-decoration:none;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+}
+ul#page-actions2 {
+ background-color:#ECECEC;
+ color:black;
+ float:left;
+ list-style-type:none;
+ margin:10px 10px 0;
+ padding:6px;
+}
+ul#user-actions2 {
+ background-color:#ECECEC;
+ border-bottom-left-radius:3px;
+ border-bottom-right-radius:3px;
+ border-top-left-radius:3px;
+ border-top-right-radius:3px;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:5px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+ text-decoration:none;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, ul#page-actions-more a:hover, ul#page-actions-more a:focus {
+ color:#4E7BB4;
+}
+.hidden {
+ display:none;
+}
+a.logout {
+ background-color:transparent;
+ background-image:url(../img/user-actions-logout.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.info {
+ background-color:transparent;
+ background-image:url(../img/user-info.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.admin {
+ background-color:transparent;
+ background-image:url(../img/user-actions-admin.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.profile {
+ background-color:transparent;
+ background-image:url(../img/user-actions-profile.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.create, a.edit {
+ background-color:transparent;
+ background-image:url(../img/page-tools-edit.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.source, a.show {
+ background-color:transparent;
+ background-image:url(../img/page-tools-source.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.revisions {
+ background-color:transparent;
+ background-image:url(../img/page-tools-revisions.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background-color:transparent;
+ background-image:url(../img/page-tools-subscribe.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.backlink {
+ background-color:transparent;
+ background-image:url(../img/page-tools-backlinks.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.play {
+ background-color:transparent;
+ background-image:url(../img/control_play.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+.time {
+ background-color:transparent;
+ background-image:url(../img/status_None.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+}
+.reconnect {
+ background-color:transparent;
+ background-image:url(../img/reconnect.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+}
+a.play:hover {
+ background-color:transparent;
+ background-image:url(../img/control_play_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cancel {
+ background-color:transparent;
+ background-image:url(../img/control_cancel.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cancel:hover {
+ background-color:transparent;
+ background-image:url(../img/control_cancel_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.pause {
+ background-color:transparent;
+ background-image:url(../img/control_pause.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.pause:hover {
+ background-color:transparent;
+ background-image:url(../img/control_pause_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ font-weight:bold;
+}
+a.stop {
+ background-color:transparent;
+ background-image:url(../img/control_stop.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.stop:hover {
+ background-color:transparent;
+ background-image:url(../img/control_stop_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.add {
+ background-color:transparent;
+ background-image:url(../img/control_add.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.add:hover {
+ background-color:transparent;
+ background-image:url(../img/control_add_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cog {
+ background-color:transparent;
+ background-image:url(../img/cog.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+#head-panel {
+ background-color:#67BCDB;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+}
+#head-panel h1 {
+ color:#EEEEEC;
+ display:none;
+ font-size:2.6em;
+ margin:0;
+ padding-left:3.3em;
+ padding-top:0.8em;
+ text-decoration:none;
+}
+#head-panel #head-logo {
+ float:left;
+ overflow:visible;
+ padding:11px 8px 0;
+}
+#head-menu {
+ float:left;
+ margin:0;
+ padding:1em 0 0;
+ width:100%;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ font-size:14px;
+ margin:0 0 4px 0.3em;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0;
+}
+#head-menu ul li a img {
+ height:22px;
+ padding-right:4px;
+ vertical-align:middle;
+ width:22px;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ background-color:#FFFFFF;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+ border-color:#CCCCCC #CCCCCC transparent;
+ color:#555555;
+ padding:7px 15px 8px;
+ text-decoration:none;
+ opacity:0.7;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ border-bottom-color:transparent;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-bottom-style:none;
+ border-bottom-width:0;
+ outline:none;
+ padding-bottom:7px;
+ opacity:1;
+ background-color: #FF9009;
+ transition: background 0.2s ease-out, opacity 0.5s ease-in;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ border-bottom-color:transparent;
+ color:#3566A5;
+ padding:7px 15px 8px;
+ opacity:1;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111111;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 12px 0 0;
+ padding:7px 0px 7px 7px;
+ white-space:nowrap;
+ background-color:#FFF056;
+ opacity:0.9;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ background-color:#EEEEEE;
+ border:2px solid #888888;
+ border-bottom-left-radius:3px;
+ border-bottom-right-radius:3px;
+ border-top-left-radius:3px;
+ border-top-right-radius:3px;
+ font-size:14px;
+ padding:2px;
+}
+div#head-search-and-login form input:focus {
+ background-color:#FFFFFF;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ font-size:14px;
+ width:80px;
+}
+#pageinfo {
+ clear:both;
+ color:#888888;
+ margin:0;
+ padding:0.6em 0;
+}
+#foot {
+ color:#888888;
+ font-style:normal;
+ text-align:center;
+}
+#foot a {
+ color:#AAAAFF;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ background-color:#F0F0F0;
+ border:1px dotted #888888;
+ float:right;
+ font-size:95%;
+ margin:1em 0 1em 1em;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0 0 0 1em;
+ padding:0;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ background-color:#F7F6F2;
+ border:1px dotted #888888;
+ font-size:90%;
+ padding:0 1ex 1ex;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ font-weight:bold;
+ text-align:right;
+}
+label.simple {
+ display:block;
+ font-weight:normal;
+ text-align:left;
+}
+label.block input.edit {
+ width:50%;
+}
+div.editor {
+ margin:0;
+}
+table {
+ border-collapse:collapse;
+ margin:0.5em 0;
+}
+td {
+ border:1pt solid #ADB9CC;
+ padding:0.25em;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ margin:0 0 1em;
+ padding:0 2em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ background-color:#EEEEEE;
+ border-bottom-color:#CCCCCC;
+ border-bottom-style:solid;
+ border-bottom-width:2px;
+ clear:both;
+ padding:5px 10px;
+}
+#message p {
+ font-weight:bold;
+ margin:5px 0;
+ padding:0;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:400;
+}
+.boxtext {
+ color:#000000;
+ float:none;
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ cursor:pointer;
+ float:left;
+ height:32px;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ width:32px;
+}
+.dlsize {
+ float:left;
+ padding-right:8px;
+}
+.dlspeed {
+ float:left;
+ padding-right:8px;
+}
+.package {
+ margin-bottom:10px;
+}
+.packagename {
+ font-weight:300;
+}
+.child {
+ margin-left:20px;
+}
+.child_status {
+ margin-right:10px;
+}
+.child_secrow {
+ font-size:10px;
+}
+.header, .header th {
+ background-color:#ECECEC;
+ font-weight:300;
+ text-align:left;
+}
+.progress_bar {
+ background-color:#00CC00;
+ height:5px;
+}
+.queue {
+ border:none;
+}
+.queue tr td {
+ border:none;
+}
+.header, .header th {
+ font-weight:normal;
+ text-align:left;
+}
+.clearer {
+ clear:both;
+ height:1px;
+}
+.left {
+ float:left;
+}
+.right {
+ float:right;
+}
+.setfield {
+ display:table-cell;
+}
+ul.tabs li a {
+ border:none;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ font-weight:bold;
+ padding:5px 16px 4px 15px;
+}
+#tabs span {
+ display:none;
+}
+#tabs span.selected {
+ display:inline;
+}
+#tabsback {
+ background-color:#525252;
+ border-top-left-radius:3px;
+ border-top-right-radius:30px;
+ margin:2px 0 0;
+ padding:6px 4px 1px;
+}
+ul.tabs {
+ list-style-type:none;
+ margin:0;
+ padding:0 40px 0 0;
+}
+ul.tabs li {
+ display:inline;
+ margin-left:8px;
+}
+ul.tabs li a {
+ background-color:#EAEAEA;
+ border:1px none #C9C3BA;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ color:#42454A;
+ font-weight:bold;
+ margin:0;
+ outline:0;
+ padding:5px 16px 4px 15px;
+ text-decoration:none;
+}
+ul.tabs li a.selected, ul.tabs li a:hover {
+ background-color:white;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ color:#000000;
+}
+ul.tabs li a:hover {
+ background-color:#F1F4EE;
+}
+ul.tabs li a.selected {
+ background-color:#525252;
+ color:white;
+ font-weight:bold;
+ padding-bottom:5px;
+}
+#tabs-body {
+ overflow:hidden;
+ position:relative;
+}
+span.tabContent {
+ border:2px solid #525252;
+ margin:0;
+ padding:0 0 10px;
+}
+#tabs-body > span {
+ display:none;
+}
+#tabs-body > span.active {
+ display:block;
+}
+.hide {
+ display:none;
+}
+.settable {
+ border:none;
+ margin:20px;
+}
+.settable td {
+ border:none;
+ margin:0;
+ padding:5px;
+}
+.settable th {
+ padding-bottom:8px;
+}
+.settable.wide td, .settable.wide th {
+ padding-left:15px;
+ padding-right:15px;
+}
+ul.nav {
+ list-style:none;
+ margin:-30px 0 0;
+ padding:0;
+ position:absolute;
+}
+ul.nav li {
+ float:left;
+ padding:5px;
+ position:relative;
+}
+ul.nav > li a {
+ background-color:white;
+ border-left-color:#C9C3BA;
+ border-right-color:#C9C3BA;
+ border-style:solid solid none;
+ border-top-color:#C9C3BA;
+ border-width:1px 1px medium;
+ color:black;
+}
+ul.nav ul {
+ -webkit-box-shadow:#AAAAAA 1px 1px 5px;
+ background-color:#F1F1F1;
+ border:1px solid #AAAAAA;
+ box-shadow:#AAAAAA 1px 1px 5px;
+ cursor:pointer;
+ left:10px;
+ list-style:none;
+ margin:0;
+ padding:0;
+ position:absolute;
+ top:26px;
+}
+ul.nav .open {
+ display:block;
+}
+ul.nav .close {
+ display:none;
+}
+ul.nav ul li {
+ float:none;
+ padding:0;
+}
+ul.nav ul li a {
+ background-color:#F1F1F1;
+ display:block;
+ font-weight:normal;
+ padding:3px;
+ width:130px;
+}
+ul.nav ul li a:hover {
+ background-color:#CDCDCD;
+}
+ul.nav ul ul {
+ left:137px;
+ top:0;
+}
+.purr-wrapper {
+ margin:10px;
+}
+.purr-alert {
+ background-color:#000000;
+ border-bottom-left-radius:5px;
+ border-bottom-right-radius:5px;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ color:#FFFFFF;
+ font-size:13px;
+ font-weight:bold;
+ margin-bottom:10px;
+ padding:10px;
+ width:300px;
+}
+.purr-alert.error {
+ background-color:#000000;
+ background-image:url(../img/error.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#FF5555;
+ padding-left:30px;
+ width:280px;
+}
+.purr-alert.success {
+ background-color:#000000;
+ background-image:url(../img/success.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#55FF55;
+ padding-left:30px;
+ width:280px;
+}
+.purr-alert.notice {
+ background-color:#000000;
+ background-image:url(../img/notice.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#9999FF;
+ padding-left:30px;
+ width:280px;
+}
+table.system {
+ border:none;
+ margin-left:10px;
+}
+table.system td {
+ border:none;
+}
+table.system tr > td:first-child {
+ font-weight:bold;
+ padding-right:10px;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/Flat/css/log.css b/pyload/webui/themes/Flat/css/log.css
new file mode 100644
index 000000000..c2f21b506
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/log.css
@@ -0,0 +1,72 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #EEE;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/Flat/css/pathchooser.css b/pyload/webui/themes/Flat/css/pathchooser.css
new file mode 100644
index 000000000..204812aa1
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #F0F0F0;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/pyload/webui/themes/Flat/css/window.css b/pyload/webui/themes/Flat/css/window.css
new file mode 100644
index 000000000..40c1aadca
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/window.css
@@ -0,0 +1,70 @@
+/* ----------- stylized ----------- */
+.window_box h1{
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:#FFFFFF;
+ margin-bottom:20px;
+ border-bottom:solid 1px #b7ddf2;
+ padding-bottom:10px;
+}
+.window_box label{
+ display:block;
+ font-weight:bold;
+ text-align:right;
+ width:240px;
+ float:left;
+}
+.window_box .small{
+ color:#FFFFFF;
+ display:block;
+ font-size:11px;
+ font-weight:normal;
+ text-align:right;
+ width:240px;
+}
+.window_box select, .window_box input{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box .cont{
+ float:left;
+ font-size:12px;
+ padding: 0px 10px 15px 0px;
+ width:300px;
+ margin:0px 0px 0px 10px;
+ color:#FFFFFF;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background:url(../img/button.png) no-repeat;
+ text-align:center;
+ line-height:31px;
+ color:#FFFFFF;
+ font-size:11px;
+ font-weight:bold;
+ border: 0px;
+}
+
+.styled_button {
+ margin-left: 15px;
+ cursor: pointer;
+}
diff --git a/pyload/webui/themes/Flat/img/add_folder.png b/pyload/webui/themes/Flat/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/ajax-loader.gif b/pyload/webui/themes/Flat/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/arrow_refresh.png b/pyload/webui/themes/Flat/img/arrow_refresh.png
new file mode 100644
index 000000000..b1b6fa4dc
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/arrow_right.png b/pyload/webui/themes/Flat/img/arrow_right.png
new file mode 100644
index 000000000..68f379fc7
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/big_button.gif b/pyload/webui/themes/Flat/img/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/big_button_over.gif b/pyload/webui/themes/Flat/img/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/body.png b/pyload/webui/themes/Flat/img/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/body.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/button.png b/pyload/webui/themes/Flat/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/closebtn.gif b/pyload/webui/themes/Flat/img/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/cog.png b/pyload/webui/themes/Flat/img/cog.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/cog.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_add.png b/pyload/webui/themes/Flat/img/control_add.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_add_blue.png b/pyload/webui/themes/Flat/img/control_add_blue.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_cancel.png b/pyload/webui/themes/Flat/img/control_cancel.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_cancel_blue.png b/pyload/webui/themes/Flat/img/control_cancel_blue.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_pause.png b/pyload/webui/themes/Flat/img/control_pause.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_pause_blue.png b/pyload/webui/themes/Flat/img/control_pause_blue.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_play.png b/pyload/webui/themes/Flat/img/control_play.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_play_blue.png b/pyload/webui/themes/Flat/img/control_play_blue.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_stop.png b/pyload/webui/themes/Flat/img/control_stop.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/control_stop_blue.png b/pyload/webui/themes/Flat/img/control_stop_blue.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/delete.png b/pyload/webui/themes/Flat/img/delete.png
new file mode 100644
index 000000000..4539cff12
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/drag_corner.gif b/pyload/webui/themes/Flat/img/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/error.png b/pyload/webui/themes/Flat/img/error.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/error.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/folder.png b/pyload/webui/themes/Flat/img/folder.png
new file mode 100644
index 000000000..0b067dd3c
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/folder.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/full.png b/pyload/webui/themes/Flat/img/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/full.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-login.png b/pyload/webui/themes/Flat/img/head-login.png
new file mode 100644
index 000000000..6b57515bc
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-collector.png b/pyload/webui/themes/Flat/img/head-menu-collector.png
new file mode 100644
index 000000000..bbcbe6406
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-config.png b/pyload/webui/themes/Flat/img/head-menu-config.png
new file mode 100644
index 000000000..93e8f83ac
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-development.png b/pyload/webui/themes/Flat/img/head-menu-development.png
new file mode 100644
index 000000000..33d8b062f
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-download.png b/pyload/webui/themes/Flat/img/head-menu-download.png
new file mode 100644
index 000000000..3691deebc
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-home.png b/pyload/webui/themes/Flat/img/head-menu-home.png
new file mode 100644
index 000000000..b77bef5eb
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-index.png b/pyload/webui/themes/Flat/img/head-menu-index.png
new file mode 100644
index 000000000..8bc6e9604
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-news.png b/pyload/webui/themes/Flat/img/head-menu-news.png
new file mode 100644
index 000000000..44e79a9a9
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-queue.png b/pyload/webui/themes/Flat/img/head-menu-queue.png
new file mode 100644
index 000000000..e4fa41ad8
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-recent.png b/pyload/webui/themes/Flat/img/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-menu-wiki.png b/pyload/webui/themes/Flat/img/head-menu-wiki.png
new file mode 100644
index 000000000..61b0e54ea
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head-search-noshadow.png b/pyload/webui/themes/Flat/img/head-search-noshadow.png
new file mode 100644
index 000000000..16d39bd06
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/head_bg1.png b/pyload/webui/themes/Flat/img/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/images.png b/pyload/webui/themes/Flat/img/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/images.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/notice.png b/pyload/webui/themes/Flat/img/notice.png
new file mode 100644
index 000000000..a52b067cb
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/notice.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/package_go.png b/pyload/webui/themes/Flat/img/package_go.png
new file mode 100644
index 000000000..80b2c42ee
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/page-tools-backlinks.png b/pyload/webui/themes/Flat/img/page-tools-backlinks.png
new file mode 100644
index 000000000..fb8f55b38
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/page-tools-edit.png b/pyload/webui/themes/Flat/img/page-tools-edit.png
new file mode 100644
index 000000000..67177cf89
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/page-tools-revisions.png b/pyload/webui/themes/Flat/img/page-tools-revisions.png
new file mode 100644
index 000000000..088fe0087
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/parseUri.png b/pyload/webui/themes/Flat/img/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/pencil.png b/pyload/webui/themes/Flat/img/pencil.png
new file mode 100644
index 000000000..e39c93cd8
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/pyload-logo.png b/pyload/webui/themes/Flat/img/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/reconnect.png b/pyload/webui/themes/Flat/img/reconnect.png
new file mode 100644
index 000000000..cd35c9325
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_None.png b/pyload/webui/themes/Flat/img/status_None.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_downloading.png b/pyload/webui/themes/Flat/img/status_downloading.png
new file mode 100644
index 000000000..db8ad8cd6
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_failed.png b/pyload/webui/themes/Flat/img/status_failed.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_finished.png b/pyload/webui/themes/Flat/img/status_finished.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_offline.png b/pyload/webui/themes/Flat/img/status_offline.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_proc.png b/pyload/webui/themes/Flat/img/status_proc.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_queue.png b/pyload/webui/themes/Flat/img/status_queue.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/status_waiting.png b/pyload/webui/themes/Flat/img/status_waiting.png
new file mode 100644
index 000000000..fd038175e
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/success.png b/pyload/webui/themes/Flat/img/success.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/success.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/tab-background.png b/pyload/webui/themes/Flat/img/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/tabs-border-bottom.png b/pyload/webui/themes/Flat/img/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/user-actions-logout.png b/pyload/webui/themes/Flat/img/user-actions-logout.png
new file mode 100644
index 000000000..d4ef360e8
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/user-actions-profile.png b/pyload/webui/themes/Flat/img/user-actions-profile.png
new file mode 100644
index 000000000..9ec410b13
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/user-info.png b/pyload/webui/themes/Flat/img/user-info.png
new file mode 100644
index 000000000..345ed52e4
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/js/admin.coffee b/pyload/webui/themes/Flat/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/Flat/js/admin.min.js b/pyload/webui/themes/Flat/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/Flat/js/base.coffee b/pyload/webui/themes/Flat/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/Flat/js/base.min.js b/pyload/webui/themes/Flat/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/Flat/js/filemanager.js b/pyload/webui/themes/Flat/js/filemanager.js
new file mode 100644
index 000000000..ab9b8b492
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/filemanager.js
@@ -0,0 +1,291 @@
+var load, rename_box, confirm_box;
+
+document.addEvent("domready", function() {
+ load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ load.set("opacity", 0);
+
+ rename_box = new Fx.Tween($('rename_box'));
+ confirm_box = new Fx.Tween($('confirm_box'));
+ $('rename_reset').addEvent('click', function() {
+ hide_rename_box()
+ });
+ $('delete_reset').addEvent('click', function() {
+ hide_confirm_box()
+ });
+
+ /*$('filemanager_actions_list').getChildren("li").each(function(action) {
+ var action_name = action.className;
+ if(functions[action.className] != undefined)
+ {
+ action.addEvent('click', functions[action.className]);
+ }
+ });*/
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+function show_rename_box() {
+ bg_show();
+ $("rename_box").setStyle('display', 'block');
+ rename_box.start('opacity', 1)
+}
+
+function hide_rename_box() {
+ bg_hide();
+ rename_box.start('opacity', 0).chain(function() {
+ $('rename_box').setStyle('display', 'none');
+ });
+}
+
+function show_confirm_box() {
+ bg_show();
+ $("confirm_box").setStyle('display', 'block');
+ confirm_box.start('opacity', 1)
+}
+
+function hide_confirm_box() {
+ bg_hide();
+ confirm_box.start('opacity', 0).chain(function() {
+ $('confirm_box').setStyle('display', 'none');
+ });
+}
+
+var FilemanagerUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.directories = [];
+ this.files = [];
+ this.parseChildren();
+ },
+
+ parseChildren: function() {
+ $("directories-list").getChildren("li.folder").each(function(ele) {
+ var path = ele.getElements("input.path")[0].get("value");
+ var name = ele.getElements("input.name")[0].get("value");
+ this.directories.push(new Item(this, path, name, ele))
+ }.bind(this));
+
+ $("directories-list").getChildren("li.file").each(function(ele) {
+ var path = ele.getElements("input.path")[0].get("value");
+ var name = ele.getElements("input.name")[0].get("value");
+ this.files.push(new Item(this, path, name, ele))
+ }.bind(this));
+ }
+});
+
+var Item = new Class({
+ initialize: function(ui, path, name, ele) {
+ this.ui = ui;
+ this.path = path;
+ this.name = name;
+ this.ele = ele;
+ this.directories = [];
+ this.files = [];
+ this.actions = new Array();
+ this.actions["delete"] = this.del;
+ this.actions["rename"] = this.rename;
+ this.actions["mkdir"] = this.mkdir;
+ this.parseElement();
+
+ var pname = this.ele.getElements("span")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+ },
+
+ parseElement: function() {
+ this.ele.getChildren('span span.buttons img').each(function(img) {
+ img.addEvent('click', this.actions[img.className].bind(this));
+ }, this);
+
+ //click on the directory name must open the directory itself
+ this.ele.getElements('b')[0].addEvent('click', this.toggle.bind(this));
+
+ //iterate over child directories
+ var uls = this.ele.getElements('ul');
+ if(uls.length > 0)
+ {
+ uls[0].getChildren("li.folder").each(function(fld) {
+ var path = fld.getElements("input.path")[0].get("value");
+ var name = fld.getElements("input.name")[0].get("value");
+ this.directories.push(new Item(this, path, name, fld));
+ }.bind(this));
+ uls[0].getChildren("li.file").each(function(fld) {
+ var path = fld.getElements("input.path")[0].get("value");
+ var name = fld.getElements("input.name")[0].get("value");
+ this.files.push(new Item(this, path, name, fld));
+ }.bind(this));
+ }
+ },
+
+ reorderElements: function() {
+ //TODO sort the main ul again (to keep data ordered after renaming something)
+ },
+
+ del: function(event) {
+ $("confirm_form").removeEvents("submit");
+ $("confirm_form").addEvent("submit", this.deleteDirectory.bind(this));
+
+ $$("#confirm_form p").set('html', '{{_(("Are you sure you want to delete the selected item?"))}}');
+
+ show_confirm_box();
+ event.stop();
+ },
+
+ deleteDirectory: function(event) {
+ hide_confirm_box();
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/delete",
+ data: {'path': this.path, 'name': this.name},
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ new Fx.Tween(this.ele).start('opacity', 0);
+ var ul = this.ele.parentNode;
+ this.ele.dispose();
+ //if this was the only child, add a "empty folder" div
+ if(!ul.getChildren('li')[0])
+ {
+ var div = new Element("div", { 'html': '{{ _("Folder is empty") }}' });
+ div.replaces(ul);
+ }
+
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+
+ event.stop();
+ },
+
+ rename: function(event) {
+ $("rename_form").removeEvents("submit");
+ $("rename_form").addEvent("submit", this.renameDirectory.bind(this));
+
+ $("path").set("value", this.path);
+ $("old_name").set("value", this.name);
+ $("new_name").set("value", this.name);
+
+ show_rename_box();
+ event.stop();
+ },
+
+ renameDirectory: function(event) {
+ hide_rename_box();
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/rename",
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ this.name = $("new_name").get("value");
+ this.ele.getElements("b")[0].set('html', $("new_name").get("value"));
+ this.reorderElements();
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send($("rename_form").toQueryString());
+
+ event.stop();
+ },
+
+ mkdir: function(event) {
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/mkdir",
+ data: {'path': this.path + "/" + this.name, 'name': '{{_("New folder")}}'},
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ new Request.HTML({
+ method: 'POST',
+ url: "/filemanager/get_dir",
+ data: {'path': data.path, 'name': data.name},
+ onSuccess: function(li) {
+ //add node as first child of ul
+ var ul = this.ele.getChildren('ul')[0];
+ if(!ul)
+ {
+ //remove the "Folder Empty" div
+ this.ele.getChildren('div').dispose();
+
+ //create new ul to contain subfolder
+ ul = new Element("ul");
+ ul.inject(this.ele, 'bottom');
+ }
+ li[0].inject(ul, 'top');
+
+ //add directory as a subdirectory of the current item
+ this.directories.push(new Item(this.ui, data.path, data.name, ul.firstChild));
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+
+ event.stop();
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('ul');
+ if(child == null)
+ child = this.ele.getElement('div');
+
+ if(child != null)
+ {
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ child.reveal();
+ }
+ }
+ }
+});
diff --git a/pyload/webui/themes/Flat/js/package.js b/pyload/webui/themes/Flat/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/js/settings.coffee b/pyload/webui/themes/Flat/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/Flat/js/settings.min.js b/pyload/webui/themes/Flat/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+name: MooDialog.Confirm
+description: Creates an Confirm Dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
+...
+*/
+
+
+MooDialog.Confirm = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ cancelText: 'Cancel',
+ focus: true,
+ textPClass: 'MooDialogConfirm'
+ },
+
+ initialize: function(msg, fn, fn1, options){
+ this.parent(options);
+ var emptyFn = function(){},
+ self = this;
+
+ var buttons = [
+ {fn: fn || emptyFn, txt: this.options.okText},
+ {fn: fn1 || emptyFn, txt: this.options.cancelText}
+ ].map(function(button){
+ return new Element('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: button.txt
+ });
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(buttons)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if(this.options.focus) this.addEvent('show', function(){
+ buttons[1].focus();
+ });
+
+ }
+});
+
+
+Element.implement({
+
+ confirmLinkClick: function(msg, options){
+ this.addEvent('click', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ location.href = this.get('href');
+ }.bind(this), null, options)
+ });
+ return this;
+ },
+
+ confirmFormSubmit: function(msg, options){
+ this.addEvent('submit', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ this.submit();
+ }.bind(this), null, options)
+ }.bind(this));
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+name: MooDialog.Fx
+description: Overwrite the default events so the Dialogs are using Fx on open and close
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Fx.Tween, Overlay]
+provides: MooDialog.Fx
+...
+*/
+
+
+MooDialog.implement('options', {
+
+ duration: 400,
+ closeOnOverlayClick: true,
+
+ onInitialize: function(wrapper){
+ this.fx = new Fx.Tween(wrapper, {
+ property: 'opacity',
+ duration: this.options.duration
+ }).set(0);
+ this.overlay = new Overlay(this.options.inject, {
+ duration: this.options.duration
+ });
+ if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ this.fx.start(1).chain(function(){
+ this.fireEvent('show');
+ }.bind(this));
+ },
+
+ onBeforeClose: function(wrapper){
+ this.overlay.close();
+ this.fx.start(0).chain(function(){
+ this.fireEvent('hide');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+name: Overlay
+
+authors:
+ - David Walsh (http://davidwalsh.name)
+
+license:
+ - MIT-style license
+
+requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
+
+provides:
+ - Overlay
+...
+*/
+
+var Overlay = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ id: 'overlay',
+ color: '#000',
+ duration: 500,
+ opacity: 0.5,
+ zIndex: 5000/*,
+ onClick: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ initialize: function(container, options){
+ this.setOptions(options);
+ this.container = document.id(container);
+
+ this.bound = {
+ 'window': {
+ resize: this.resize.bind(this),
+ scroll: this.scroll.bind(this)
+ },
+ overlayClick: this.overlayClick.bind(this),
+ tweenStart: this.tweenStart.bind(this),
+ tweenComplete: this.tweenComplete.bind(this)
+ };
+
+ this.build().attach();
+ },
+
+ build: function(){
+ this.overlay = new Element('div', {
+ id: this.options.id,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ return this;
+ }.protect(),
+
+ attach: function(){
+ window.addEvents(this.bound.window);
+ this.overlay.addEvent('click', this.bound.overlayClick);
+ this.tween.addEvents({
+ onStart: this.bound.tweenStart,
+ onComplete: this.bound.tweenComplete
+ });
+ return this;
+ },
+
+ detach: function(){
+ var args = Array.prototype.slice.call(arguments);
+ args.each(function(item){
+ if(item == 'window') window.removeEvents(this.bound.window);
+ if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
+ }, this);
+ return this;
+ },
+
+ overlayClick: function(){
+ this.fireEvent('click');
+ return this;
+ },
+
+ tweenStart: function(){
+ this.overlay.setStyles({
+ width: '100%',
+ height: this.container.getScrollSize().y,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ return this;
+ },
+
+ resize: function(){
+ this.fireEvent('resize');
+ this.overlay.setStyle('height', this.container.getScrollSize().y);
+ return this;
+ },
+
+ scroll: function(){
+ this.fireEvent('scroll');
+ if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ z-index: 50000;
+
+ background: #eef5f8;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .content {
+ height: 100px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios'){
+ UA[1] = 'chrome';
+ }
+
+ platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.name == 'ie'){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ window = this.Window = document = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e){
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
+ return node.hasAttribute(attribute);
+ } : function(node, attribute){
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector){
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll){
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e){
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError){}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ }
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return classes(this.className).contains(className);
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function(){
+
+var _keys = {};
+var normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1, message: 2 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e){}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',
+ withCredentials: false,*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (!ready) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ compensateScroll: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+ this.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(offsetParent);
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ this.mouse.start = null;
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+/*
+---
+
+script: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+/*
+---
+
+script: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(function(){
+
+var Swiff = this.Swiff = new Class({
+
+ Implements: Options,
+
+ options: {
+ id: null,
+ height: 1,
+ width: 1,
+ container: null,
+ properties: {},
+ params: {
+ quality: 'high',
+ allowScriptAccess: 'always',
+ wMode: 'window',
+ swLiveConnect: true
+ },
+ callBacks: {},
+ vars: {}
+ },
+
+ toElement: function(){
+ return this.object;
+ },
+
+ initialize: function(path, options){
+ this.instance = 'Swiff_' + String.uniqueID();
+
+ this.setOptions(options);
+ options = this.options;
+ var id = this.id = options.id || this.instance;
+ var container = document.id(options.container);
+
+ Swiff.CallBacks[this.instance] = {};
+
+ var params = options.params, vars = options.vars, callBacks = options.callBacks;
+ var properties = Object.append({height: options.height, width: options.width}, options.properties);
+
+ var self = this;
+
+ for (var callBack in callBacks){
+ Swiff.CallBacks[this.instance][callBack] = (function(option){
+ return function(){
+ return option.apply(self.object, arguments);
+ };
+ })(callBacks[callBack]);
+ vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+ }
+
+ params.flashVars = Object.toQueryString(vars);
+ if ('ActiveXObject' in window){
+ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+ params.movie = path;
+ } else {
+ properties.type = 'application/x-shockwave-flash';
+ }
+ properties.data = path;
+
+ var build = '<object id="' + id + '"';
+ for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+ build += '>';
+ for (var param in params){
+ if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+ }
+ build += '</object>';
+ this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+ },
+
+ replaces: function(element){
+ element = document.id(element, true);
+ element.parentNode.replaceChild(this.toElement(), element);
+ return this;
+ },
+
+ inject: function(element){
+ document.id(element, true).appendChild(this.toElement());
+ return this;
+ },
+
+ remote: function(){
+ return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
+ }
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+ var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+ return eval(rs);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/Flat/tml/admin.html b/pyload/webui/themes/Flat/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/base.html b/pyload/webui/themes/Flat/tml/base.html
new file mode 100644
index 000000000..06061e705
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/base.html
@@ -0,0 +1,180 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Web UI")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: 300; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a>
+
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('filemanager') }}>
+ <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Settings")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Web UI")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the web ui.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div style="display: none;">
+ {% include '/tml/window.html' %}
+ {% include '/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/Flat/tml/captcha.html b/pyload/webui/themes/Flat/tml/captcha.html
new file mode 100644
index 000000000..3bfa6cbcf
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/Flat/tml/downloads.html b/pyload/webui/themes/Flat/tml/downloads.html
new file mode 100644
index 000000000..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/filemanager.html b/pyload/webui/themes/Flat/tml/filemanager.html
new file mode 100644
index 000000000..b4872b084
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/filemanager.html
@@ -0,0 +1,78 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/js/filemanager.min.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var fmUI = new FilemanagerUI("url",1);
+});
+</script>
+{% endblock %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+
+{% block subtitle %}
+{{_("FileManager")}}
+{% endblock %}
+
+{% macro display_file(file) %}
+ <li class="file">
+ <input type="hidden" name="path" class="path" value="{{ file.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ file.name }}" />
+ <span>
+ <b>{{ file.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ </span>
+ </span>
+ </li>
+{%- endmacro %}
+
+{% macro display_folder(fld, open = false) -%}
+ <li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
+ <span>
+ <b>{{ fld.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ {% if (fld.folders|length + fld.files|length) > 0 %}
+ {% if open %}
+ <ul>
+ {% else %}
+ <ul style="display:none">
+ {% endif %}
+ {% for child in fld.folders %}
+ {{ display_folder(child) }}
+ {% endfor %}
+ {% for child in fld.files %}
+ {{ display_file(child) }}
+ {% endfor %}
+ </ul>
+ {% else %}
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+ {% endif %}
+ </li>
+{%- endmacro %}
+
+{% block content %}
+
+<div style="clear:both"><!-- --></div>
+
+<ul id="directories-list">
+{{ display_folder(root, true) }}
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/folder.html b/pyload/webui/themes/Flat/tml/folder.html
new file mode 100644
index 000000000..21f3f4a03
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Flat/tml/home.html b/pyload/webui/themes/Flat/tml/home.html
new file mode 100644
index 000000000..cc0bf330c
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/home.html
@@ -0,0 +1,266 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/img/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li>
+ <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/info.html b/pyload/webui/themes/Flat/tml/info.html
new file mode 100644
index 000000000..57e688e09
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript">
+ window.addEvent("domready", function() {
+ var ul = new Element('ul#twitter_update_list');
+ var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]');
+ var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
+ $("twitter").adopt(ul, script1, script2);
+ });
+ </script>
+{% endblock %}
+
+{% block title %}{{ _("Info") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Info") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+ <div id="twitter"></div>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Web UI Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/login.html b/pyload/webui/themes/Flat/tml/login.html
new file mode 100644
index 000000000..74bbb70a4
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span>{{_("Username")}}</span>
+ <input type="text" size="20" name="username" />
+ </label>
+ <br />
+ <label>
+ <span>{{_("Password")}}</span>
+ <input type="password" size="20" name="password" />
+ </label>
+ <br />
+ <input type="submit" value="Login" class="button" />
+ </fieldset>
+ </div>
+</form>
+
+{% if errors %}
+<p>{{_("Your username and password didn't match. Please try again.")}}</p>
+ {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/logout.html b/pyload/webui/themes/Flat/tml/logout.html
new file mode 100644
index 000000000..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/logs.html b/pyload/webui/themes/Flat/tml/logs.html
new file mode 100644
index 000000000..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/css/log.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/pathchooser.html b/pyload/webui/themes/Flat/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html>
diff --git a/pyload/webui/themes/Flat/tml/queue.html b/pyload/webui/themes/Flat/tml/queue.html
new file mode 100644
index 000000000..8038a085b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: 300;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: 300;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 0px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
+ <label style="font-size: 0.8em; font-weight: 300; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: 300; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/settings.html b/pyload/webui/themes/Flat/tml/settings.html
new file mode 100644
index 000000000..47f69b188
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Settings") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Settings") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:#424242;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" value="{{account.password}}" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.time}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.limitdl}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/settings_item.html b/pyload/webui/themes/Flat/tml/settings_item.html
new file mode 100644
index 000000000..71c212304
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/settings_item.html
@@ -0,0 +1,48 @@
+<table class="settable">
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in sorted_conf(section) %}
+ {% if okey not in ("desc","outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:#424242;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+</table>
diff --git a/pyload/webui/themes/Flat/tml/window.html b/pyload/webui/themes/Flat/tml/window.html
new file mode 100644
index 000000000..023441f69
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/window.html
@@ -0,0 +1,46 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+ {{_("Queue")}}
+ <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
+ {{_("Collector")}}
+ <input type="radio" name="add_dest" id="add_dest2" value="0"/>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
diff --git a/pyload/webui/themes/Next/css/MooDialog.css b/pyload/webui/themes/Next/css/MooDialog.css
new file mode 100644
index 000000000..ad2583b4b
--- /dev/null
+++ b/pyload/webui/themes/Next/css/MooDialog.css
@@ -0,0 +1,92 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background: #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../lib/MooTools/MooDialog/css/dialog-close.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
+
diff --git a/pyload/webui/themes/Next/css/log.css b/pyload/webui/themes/Next/css/log.css
new file mode 100644
index 000000000..30e026821
--- /dev/null
+++ b/pyload/webui/themes/Next/css/log.css
@@ -0,0 +1,112 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #EEE;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+td.MixedDEBUG, td.LineDEBUG, td.loglevelLineDEBUG
+{
+ color: darkcyan;
+}
+td.MixedWARNING, td.LineWARNING, td.loglevelLineWARNING
+{
+ color: #660;
+}
+td.MixedERROR, td.LineERROR, td.loglevelLineERROR
+{
+ color: red;
+}
+td.MixedCRITICAL, td.LineCRITICAL, td.loglevelLineCRITICAL
+{
+ color: purple;
+}
+td.loglevelMixedDEBUG span, td.loglevelLabelDEBUG span
+{
+ font-weight: bold;
+ color: white;
+ background-color: darkcyan;
+}
+td.loglevelMixedWARNING span, td.loglevelLabelWARNING span
+{
+ font-weight: bold;
+ color: white;
+ background-color: #660;
+}
+td.loglevelMixedERROR span, td.loglevelLabelERROR span
+{
+ font-weight: bold;
+ color: white;
+ background-color: red;
+}
+td.loglevelMixedCRITICAL span, td.loglevelLabelCRITICAL span
+{
+ font-weight: bold;
+ color: white;
+ background-color: purple;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/css/pathchooser.css b/pyload/webui/themes/Next/css/pathchooser.css
new file mode 100644
index 000000000..bfd84b19b
--- /dev/null
+++ b/pyload/webui/themes/Next/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #fff;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/module/web/media/default/css/window.css b/pyload/webui/themes/Next/css/window.css
index 12829868b..12829868b 100644
--- a/module/web/media/default/css/window.css
+++ b/pyload/webui/themes/Next/css/window.css
diff --git a/pyload/webui/themes/Next/img/add_folder.png b/pyload/webui/themes/Next/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/Next/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/ajax-loader.gif b/pyload/webui/themes/Next/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/Next/img/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/Next/img/arrow_refresh.png b/pyload/webui/themes/Next/img/arrow_refresh.png
new file mode 100644
index 000000000..0de26566d
--- /dev/null
+++ b/pyload/webui/themes/Next/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/button.png b/pyload/webui/themes/Next/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/Next/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/control_cancel.png b/pyload/webui/themes/Next/img/control_cancel.png
new file mode 100644
index 000000000..7b9bc3fba
--- /dev/null
+++ b/pyload/webui/themes/Next/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/delete.png b/pyload/webui/themes/Next/img/delete.png
new file mode 100644
index 000000000..08f249365
--- /dev/null
+++ b/pyload/webui/themes/Next/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/package_go.png b/pyload/webui/themes/Next/img/package_go.png
new file mode 100644
index 000000000..aace63ad6
--- /dev/null
+++ b/pyload/webui/themes/Next/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/pencil.png b/pyload/webui/themes/Next/img/pencil.png
new file mode 100644
index 000000000..0bfecd50e
--- /dev/null
+++ b/pyload/webui/themes/Next/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/pyload-logo.png b/pyload/webui/themes/Next/img/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/Next/img/pyload-logo.png
Binary files differ
diff --git a/module/web/media/js/admin.coffee b/pyload/webui/themes/Next/js/admin.coffee
index 82b0dd3ec..82b0dd3ec 100644
--- a/module/web/media/js/admin.coffee
+++ b/pyload/webui/themes/Next/js/admin.coffee
diff --git a/module/web/media/js/admin.js b/pyload/webui/themes/Next/js/admin.js
index d34d310a0..d34d310a0 100644
--- a/module/web/media/js/admin.js
+++ b/pyload/webui/themes/Next/js/admin.js
diff --git a/module/web/media/js/base.coffee b/pyload/webui/themes/Next/js/base.coffee
index 3b5d33e82..3b5d33e82 100644
--- a/module/web/media/js/base.coffee
+++ b/pyload/webui/themes/Next/js/base.coffee
diff --git a/module/web/media/js/base.js b/pyload/webui/themes/Next/js/base.js
index c68b1047a..c68b1047a 100644
--- a/module/web/media/js/base.js
+++ b/pyload/webui/themes/Next/js/base.js
diff --git a/module/web/templates/default/filemanager_ui.js b/pyload/webui/themes/Next/js/filemanager.js
index ed64ab69d..ed64ab69d 100644
--- a/module/web/templates/default/filemanager_ui.js
+++ b/pyload/webui/themes/Next/js/filemanager.js
diff --git a/pyload/webui/themes/Next/js/package.js b/pyload/webui/themes/Next/js/package.js
new file mode 100644
index 000000000..384207882
--- /dev/null
+++ b/pyload/webui/themes/Next/js/package.js
@@ -0,0 +1,397 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('span');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[3].addEvent('click', this.deletePackage.bind(this));
+ imgs[4].addEvent('click', this.restartPackage.bind(this));
+ imgs[5].addEvent('click', this.editPackage.bind(this));
+ imgs[6].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({"id": this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ "style": {
+ "margin-left": 0
+ }
+ });
+
+ if (link.icon == 'arrow_right.png'){
+ link.icon = 'glyphicon glyphicon-arrow-right';
+ }
+ if (link.icon == 'status_downloading.png'){
+ link.icon = 'glyphicon glyphicon-cloud-download';
+ }
+ if (link.icon == 'status_failed.png'){
+ link.icon = 'glyphicon glyphicon-exclamation-sign';
+ }
+ if (link.icon == 'status_finished.png'){
+ link.icon = 'glyphicon glyphicon-ok';
+ }
+ if (link.statusmsg == 'queued'){
+ link.icon = 'glyphicon glyphicon-time';
+ }
+ if (link.icon == 'status_offline.png'){
+ link.icon = 'glyphicon glyphicon-ban-circle';
+ }
+
+
+ var html = "<span style='' class='child_status'><span style='margin-right: 2px;' class='{icon} sorthandle'></span></span>\n".substitute({"icon": link.icon});
+ html += "<span style='font-size: 18px; text-weight:bold'>{name}</span><br /><div class='child_secrow' style='margin-left: 21px; margin-bottom: 7px;'>".substitute({"name": link.name});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'>{statusmsg}</span>{error}&nbsp;".substitute({"statusmsg": link.statusmsg, "error":link.error});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'>{format_size}</span>".substitute({"format_size": link.format_size});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'> {plugin}</span>&nbsp;&nbsp;".substitute({"plugin": link.plugin});
+ html += "<span class='glyphicon glyphicon-trash' title='{{_("Delete Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span>&nbsp;&nbsp;";
+ html += "<span class='glyphicon glyphicon-repeat' title='{{_("Restart Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span></div>";
+
+ var div = new Element("div", {
+ "id": "file_" + link.id,
+ "class": "child",
+ "html": html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow span');
+ imgs[3].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[4].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements(".glyphicon");
+ imgs[0].set("class", "glyphicon glyphicon-time");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({"id": this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
diff --git a/module/web/media/js/settings.coffee b/pyload/webui/themes/Next/js/settings.coffee
index 9205233e3..9205233e3 100644
--- a/module/web/media/js/settings.coffee
+++ b/pyload/webui/themes/Next/js/settings.coffee
diff --git a/pyload/webui/themes/Next/js/settings.js b/pyload/webui/themes/Next/js/settings.js
new file mode 100644
index 000000000..be694d365
--- /dev/null
+++ b/pyload/webui/themes/Next/js/settings.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("show").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css
new file mode 100644
index 000000000..bb663496d
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css
@@ -0,0 +1,476 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+}
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-default .badge,
+.btn-primary .badge,
+.btn-success .badge,
+.btn-info .badge,
+.btn-warning .badge,
+.btn-danger .badge {
+ text-shadow: none;
+}
+.btn:active,
+.btn.active {
+ background-image: none;
+}
+.btn-default {
+ text-shadow: 0 1px 0 #fff;
+ background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
+ background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #dbdbdb;
+ border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus {
+ background-color: #e0e0e0;
+ background-position: 0 -15px;
+}
+.btn-default:active,
+.btn-default.active {
+ background-color: #e0e0e0;
+ border-color: #dbdbdb;
+}
+.btn-default.disabled,
+.btn-default:disabled,
+.btn-default[disabled] {
+ background-color: #e0e0e0;
+ background-image: none;
+}
+.btn-primary {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #245580;
+}
+.btn-primary:hover,
+.btn-primary:focus {
+ background-color: #265a88;
+ background-position: 0 -15px;
+}
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #265a88;
+ border-color: #245580;
+}
+.btn-primary.disabled,
+.btn-primary:disabled,
+.btn-primary[disabled] {
+ background-color: #265a88;
+ background-image: none;
+}
+.btn-success {
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
+ background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #3e8f3e;
+}
+.btn-success:hover,
+.btn-success:focus {
+ background-color: #419641;
+ background-position: 0 -15px;
+}
+.btn-success:active,
+.btn-success.active {
+ background-color: #419641;
+ border-color: #3e8f3e;
+}
+.btn-success.disabled,
+.btn-success:disabled,
+.btn-success[disabled] {
+ background-color: #419641;
+ background-image: none;
+}
+.btn-info {
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+ background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #28a4c9;
+}
+.btn-info:hover,
+.btn-info:focus {
+ background-color: #2aabd2;
+ background-position: 0 -15px;
+}
+.btn-info:active,
+.btn-info.active {
+ background-color: #2aabd2;
+ border-color: #28a4c9;
+}
+.btn-info.disabled,
+.btn-info:disabled,
+.btn-info[disabled] {
+ background-color: #2aabd2;
+ background-image: none;
+}
+.btn-warning {
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+ background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #e38d13;
+}
+.btn-warning:hover,
+.btn-warning:focus {
+ background-color: #eb9316;
+ background-position: 0 -15px;
+}
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #eb9316;
+ border-color: #e38d13;
+}
+.btn-warning.disabled,
+.btn-warning:disabled,
+.btn-warning[disabled] {
+ background-color: #eb9316;
+ background-image: none;
+}
+.btn-danger {
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+ background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #b92c28;
+}
+.btn-danger:hover,
+.btn-danger:focus {
+ background-color: #c12e2a;
+ background-position: 0 -15px;
+}
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #c12e2a;
+ border-color: #b92c28;
+}
+.btn-danger.disabled,
+.btn-danger:disabled,
+.btn-danger[disabled] {
+ background-color: #c12e2a;
+ background-image: none;
+}
+.thumbnail,
+.img-thumbnail {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ background-color: #e8e8e8;
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+ background-repeat: repeat-x;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ background-color: #2e6da4;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+}
+.navbar-default {
+ background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
+ background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .active > a {
+ background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+ background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
+ background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
+ background-repeat: repeat-x;
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+}
+.navbar-brand,
+.navbar-nav > li > a {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
+}
+.navbar-inverse {
+ background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
+ background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
+ background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .active > a {
+ background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+ background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
+ background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
+ background-repeat: repeat-x;
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+}
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
+}
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ border-radius: 0;
+}
+@media (max-width: 767px) {
+ .navbar .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #fff;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+ }
+}
+.alert {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+}
+.alert-success {
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+ background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #b2dba1;
+}
+.alert-info {
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+ background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #9acfea;
+}
+.alert-warning {
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+ background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #f5e79e;
+}
+.alert-danger {
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+ background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
+ background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #dca7a7;
+}
+.progress {
+ background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+ background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
+ background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-success {
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-info {
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-warning {
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-danger {
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-striped {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.list-group {
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ text-shadow: 0 -1px 0 #286090;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #2b669a;
+}
+.list-group-item.active .badge,
+.list-group-item.active:hover .badge,
+.list-group-item.active:focus .badge {
+ text-shadow: none;
+}
+.panel {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+}
+.panel-default > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-primary > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-success > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+ background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-info > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+ background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-warning > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+ background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-danger > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+ background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
+ background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+ background-repeat: repeat-x;
+}
+.well {
+ background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+ background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
+ background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #dcdcdc;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+}
+/*# sourceMappingURL=bootstrap-theme.css.map */
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map
new file mode 100644
index 000000000..5a12d6317
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAcA;;;;;;EAME,0CAAA;ECgDA,6FAAA;EACQ,qFAAA;EC5DT;AFgBC;;;;;;;;;;;;EC2CA,0DAAA;EACQ,kDAAA;EC7CT;AFVD;;;;;;EAiBI,mBAAA;EECH;AFiCC;;EAEE,wBAAA;EE/BH;AFoCD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EAgC2C,2BAAA;EAA2B,oBAAA;EEzBvE;AFLC;;EAEE,2BAAA;EACA,8BAAA;EEOH;AFJC;;EAEE,2BAAA;EACA,uBAAA;EEMH;AFHC;;;EAGE,2BAAA;EACA,wBAAA;EEKH;AFUD;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEgCD;AF9BC;;EAEE,2BAAA;EACA,8BAAA;EEgCH;AF7BC;;EAEE,2BAAA;EACA,uBAAA;EE+BH;AF5BC;;;EAGE,2BAAA;EACA,wBAAA;EE8BH;AFdD;EGrDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEyDD;AFvDC;;EAEE,2BAAA;EACA,8BAAA;EEyDH;AFtDC;;EAEE,2BAAA;EACA,uBAAA;EEwDH;AFrDC;;;EAGE,2BAAA;EACA,wBAAA;EEuDH;AFtCD;EGtDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEkFD;AFhFC;;EAEE,2BAAA;EACA,8BAAA;EEkFH;AF/EC;;EAEE,2BAAA;EACA,uBAAA;EEiFH;AF9EC;;;EAGE,2BAAA;EACA,wBAAA;EEgFH;AF9DD;EGvDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EE2GD;AFzGC;;EAEE,2BAAA;EACA,8BAAA;EE2GH;AFxGC;;EAEE,2BAAA;EACA,uBAAA;EE0GH;AFvGC;;;EAGE,2BAAA;EACA,wBAAA;EEyGH;AFtFD;EGxDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEoID;AFlIC;;EAEE,2BAAA;EACA,8BAAA;EEoIH;AFjIC;;EAEE,2BAAA;EACA,uBAAA;EEmIH;AFhIC;;;EAGE,2BAAA;EACA,wBAAA;EEkIH;AFxGD;;EChBE,oDAAA;EACQ,4CAAA;EC4HT;AFnGD;;EGzEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHwEF,2BAAA;EEyGD;AFvGD;;;EG9EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8EF,2BAAA;EE6GD;AFpGD;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ6GA,oBAAA;EC/CA,6FAAA;EACQ,qFAAA;EC0JT;AF/GD;;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECoKT;AF5GD;;EAEE,gDAAA;EE8GD;AF1GD;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EF+OD;AFlHD;;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0LT;AF5HD;;EAYI,2CAAA;EEoHH;AF/GD;;;EAGE,kBAAA;EEiHD;AF5FD;EAfI;;;IAGE,aAAA;IG3IF,0EAAA;IACA,qEAAA;IACA,+FAAA;IAAA,wEAAA;IACA,6BAAA;IACA,wHAAA;ID0PD;EACF;AFxGD;EACE,+CAAA;ECzGA,4FAAA;EACQ,oFAAA;ECoNT;AFhGD;EGpKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4GD;AFvGD;EGrKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoHD;AF9GD;EGtKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4HD;AFrHD;EGvKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoID;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AFlHD;EGzLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AFxHD;EG1LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqTH;AF9HD;EG3LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4TH;AFpID;EG5LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmUH;AF1ID;EG7LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0UH;AF7ID;EGhKI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDgTH;AFzID;EACE,oBAAA;EC5JA,oDAAA;EACQ,4CAAA;ECwST;AF1ID;;;EAGE,+BAAA;EGjNE,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH+MF,uBAAA;EEgJD;AFrJD;;;EAQI,mBAAA;EEkJH;AFxID;ECjLE,mDAAA;EACQ,2CAAA;EC4TT;AFlID;EG1OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED+WH;AFxID;EG3OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDsXH;AF9ID;EG5OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED6XH;AFpJD;EG7OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDoYH;AF1JD;EG9OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2YH;AFhKD;EG/OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkZH;AFhKD;EGtPI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHoPF,uBAAA;ECzMA,2FAAA;EACQ,mFAAA;ECgXT","file":"bootstrap-theme.css","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",".btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css
new file mode 100644
index 000000000..ac8dd5505
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css
new file mode 100644
index 000000000..c46af7dfb
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css
@@ -0,0 +1,6566 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+html {
+ font-family: sans-serif;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+body {
+ margin: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+ vertical-align: baseline;
+}
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+[hidden],
+template {
+ display: none;
+}
+a {
+ background-color: transparent;
+}
+a:active,
+a:hover {
+ outline: 0;
+}
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+b,
+strong {
+ font-weight: bold;
+}
+dfn {
+ font-style: italic;
+}
+h1 {
+ margin: .67em 0;
+ font-size: 2em;
+}
+mark {
+ color: #000;
+ background: #ff0;
+}
+small {
+ font-size: 80%;
+}
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+sup {
+ top: -.5em;
+}
+sub {
+ bottom: -.25em;
+}
+img {
+ border: 0;
+}
+svg:not(:root) {
+ overflow: hidden;
+}
+figure {
+ margin: 1em 40px;
+}
+hr {
+ height: 0;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+pre {
+ overflow: auto;
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+ margin: 0;
+ font: inherit;
+ color: inherit;
+}
+button {
+ overflow: visible;
+}
+button,
+select {
+ text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button;
+ cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+input {
+ line-height: normal;
+}
+input[type="checkbox"],
+input[type="radio"] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+fieldset {
+ padding: .35em .625em .75em;
+ margin: 0 2px;
+ border: 1px solid #c0c0c0;
+}
+legend {
+ padding: 0;
+ border: 0;
+}
+textarea {
+ overflow: auto;
+}
+optgroup {
+ font-weight: bold;
+}
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+td,
+th {
+ padding: 0;
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print {
+ *,
+ *:before,
+ *:after {
+ color: #000 !important;
+ text-shadow: none !important;
+ background: transparent !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+ }
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+ a[href^="#"]:after,
+ a[href^="javascript:"]:after {
+ content: "";
+ }
+ pre,
+ blockquote {
+ border: 1px solid #999;
+
+ page-break-inside: avoid;
+ }
+ thead {
+ display: table-header-group;
+ }
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+ img {
+ max-width: 100% !important;
+ }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+ select {
+ background: #fff !important;
+ }
+ .navbar {
+ display: none;
+ }
+ .btn > .caret,
+ .dropup > .btn > .caret {
+ border-top-color: #000 !important;
+ }
+ .label {
+ border: 1px solid #000;
+ }
+ .table {
+ border-collapse: collapse !important;
+ }
+ .table td,
+ .table th {
+ background-color: #fff !important;
+ }
+ .table-bordered th,
+ .table-bordered td {
+ border: 1px solid #ddd !important;
+ }
+}
+@font-face {
+ font-family: 'Glyphicons Halflings';
+
+ src: url('../fonts/glyphicons-halflings-regular.eot');
+ src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+.glyphicon {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.glyphicon-asterisk:before {
+ content: "\2a";
+}
+.glyphicon-plus:before {
+ content: "\2b";
+}
+.glyphicon-euro:before,
+.glyphicon-eur:before {
+ content: "\20ac";
+}
+.glyphicon-minus:before {
+ content: "\2212";
+}
+.glyphicon-cloud:before {
+ content: "\2601";
+}
+.glyphicon-envelope:before {
+ content: "\2709";
+}
+.glyphicon-pencil:before {
+ content: "\270f";
+}
+.glyphicon-glass:before {
+ content: "\e001";
+}
+.glyphicon-music:before {
+ content: "\e002";
+}
+.glyphicon-search:before {
+ content: "\e003";
+}
+.glyphicon-heart:before {
+ content: "\e005";
+}
+.glyphicon-star:before {
+ content: "\e006";
+}
+.glyphicon-star-empty:before {
+ content: "\e007";
+}
+.glyphicon-user:before {
+ content: "\e008";
+}
+.glyphicon-film:before {
+ content: "\e009";
+}
+.glyphicon-th-large:before {
+ content: "\e010";
+}
+.glyphicon-th:before {
+ content: "\e011";
+}
+.glyphicon-th-list:before {
+ content: "\e012";
+}
+.glyphicon-ok:before {
+ content: "\e013";
+}
+.glyphicon-remove:before {
+ content: "\e014";
+}
+.glyphicon-zoom-in:before {
+ content: "\e015";
+}
+.glyphicon-zoom-out:before {
+ content: "\e016";
+}
+.glyphicon-off:before {
+ content: "\e017";
+}
+.glyphicon-signal:before {
+ content: "\e018";
+}
+.glyphicon-cog:before {
+ content: "\e019";
+}
+.glyphicon-trash:before {
+ content: "\e020";
+}
+.glyphicon-home:before {
+ content: "\e021";
+}
+.glyphicon-file:before {
+ content: "\e022";
+}
+.glyphicon-time:before {
+ content: "\e023";
+}
+.glyphicon-road:before {
+ content: "\e024";
+}
+.glyphicon-download-alt:before {
+ content: "\e025";
+}
+.glyphicon-download:before {
+ content: "\e026";
+}
+.glyphicon-upload:before {
+ content: "\e027";
+}
+.glyphicon-inbox:before {
+ content: "\e028";
+}
+.glyphicon-play-circle:before {
+ content: "\e029";
+}
+.glyphicon-repeat:before {
+ content: "\e030";
+}
+.glyphicon-refresh:before {
+ content: "\e031";
+}
+.glyphicon-list-alt:before {
+ content: "\e032";
+}
+.glyphicon-lock:before {
+ content: "\e033";
+}
+.glyphicon-flag:before {
+ content: "\e034";
+}
+.glyphicon-headphones:before {
+ content: "\e035";
+}
+.glyphicon-volume-off:before {
+ content: "\e036";
+}
+.glyphicon-volume-down:before {
+ content: "\e037";
+}
+.glyphicon-volume-up:before {
+ content: "\e038";
+}
+.glyphicon-qrcode:before {
+ content: "\e039";
+}
+.glyphicon-barcode:before {
+ content: "\e040";
+}
+.glyphicon-tag:before {
+ content: "\e041";
+}
+.glyphicon-tags:before {
+ content: "\e042";
+}
+.glyphicon-book:before {
+ content: "\e043";
+}
+.glyphicon-bookmark:before {
+ content: "\e044";
+}
+.glyphicon-print:before {
+ content: "\e045";
+}
+.glyphicon-camera:before {
+ content: "\e046";
+}
+.glyphicon-font:before {
+ content: "\e047";
+}
+.glyphicon-bold:before {
+ content: "\e048";
+}
+.glyphicon-italic:before {
+ content: "\e049";
+}
+.glyphicon-text-height:before {
+ content: "\e050";
+}
+.glyphicon-text-width:before {
+ content: "\e051";
+}
+.glyphicon-align-left:before {
+ content: "\e052";
+}
+.glyphicon-align-center:before {
+ content: "\e053";
+}
+.glyphicon-align-right:before {
+ content: "\e054";
+}
+.glyphicon-align-justify:before {
+ content: "\e055";
+}
+.glyphicon-list:before {
+ content: "\e056";
+}
+.glyphicon-indent-left:before {
+ content: "\e057";
+}
+.glyphicon-indent-right:before {
+ content: "\e058";
+}
+.glyphicon-facetime-video:before {
+ content: "\e059";
+}
+.glyphicon-picture:before {
+ content: "\e060";
+}
+.glyphicon-map-marker:before {
+ content: "\e062";
+}
+.glyphicon-adjust:before {
+ content: "\e063";
+}
+.glyphicon-tint:before {
+ content: "\e064";
+}
+.glyphicon-edit:before {
+ content: "\e065";
+}
+.glyphicon-share:before {
+ content: "\e066";
+}
+.glyphicon-check:before {
+ content: "\e067";
+}
+.glyphicon-move:before {
+ content: "\e068";
+}
+.glyphicon-step-backward:before {
+ content: "\e069";
+}
+.glyphicon-fast-backward:before {
+ content: "\e070";
+}
+.glyphicon-backward:before {
+ content: "\e071";
+}
+.glyphicon-play:before {
+ content: "\e072";
+}
+.glyphicon-pause:before {
+ content: "\e073";
+}
+.glyphicon-stop:before {
+ content: "\e074";
+}
+.glyphicon-forward:before {
+ content: "\e075";
+}
+.glyphicon-fast-forward:before {
+ content: "\e076";
+}
+.glyphicon-step-forward:before {
+ content: "\e077";
+}
+.glyphicon-eject:before {
+ content: "\e078";
+}
+.glyphicon-chevron-left:before {
+ content: "\e079";
+}
+.glyphicon-chevron-right:before {
+ content: "\e080";
+}
+.glyphicon-plus-sign:before {
+ content: "\e081";
+}
+.glyphicon-minus-sign:before {
+ content: "\e082";
+}
+.glyphicon-remove-sign:before {
+ content: "\e083";
+}
+.glyphicon-ok-sign:before {
+ content: "\e084";
+}
+.glyphicon-question-sign:before {
+ content: "\e085";
+}
+.glyphicon-info-sign:before {
+ content: "\e086";
+}
+.glyphicon-screenshot:before {
+ content: "\e087";
+}
+.glyphicon-remove-circle:before {
+ content: "\e088";
+}
+.glyphicon-ok-circle:before {
+ content: "\e089";
+}
+.glyphicon-ban-circle:before {
+ content: "\e090";
+}
+.glyphicon-arrow-left:before {
+ content: "\e091";
+}
+.glyphicon-arrow-right:before {
+ content: "\e092";
+}
+.glyphicon-arrow-up:before {
+ content: "\e093";
+}
+.glyphicon-arrow-down:before {
+ content: "\e094";
+}
+.glyphicon-share-alt:before {
+ content: "\e095";
+}
+.glyphicon-resize-full:before {
+ content: "\e096";
+}
+.glyphicon-resize-small:before {
+ content: "\e097";
+}
+.glyphicon-exclamation-sign:before {
+ content: "\e101";
+}
+.glyphicon-gift:before {
+ content: "\e102";
+}
+.glyphicon-leaf:before {
+ content: "\e103";
+}
+.glyphicon-fire:before {
+ content: "\e104";
+}
+.glyphicon-eye-open:before {
+ content: "\e105";
+}
+.glyphicon-eye-close:before {
+ content: "\e106";
+}
+.glyphicon-warning-sign:before {
+ content: "\e107";
+}
+.glyphicon-plane:before {
+ content: "\e108";
+}
+.glyphicon-calendar:before {
+ content: "\e109";
+}
+.glyphicon-random:before {
+ content: "\e110";
+}
+.glyphicon-comment:before {
+ content: "\e111";
+}
+.glyphicon-magnet:before {
+ content: "\e112";
+}
+.glyphicon-chevron-up:before {
+ content: "\e113";
+}
+.glyphicon-chevron-down:before {
+ content: "\e114";
+}
+.glyphicon-retweet:before {
+ content: "\e115";
+}
+.glyphicon-shopping-cart:before {
+ content: "\e116";
+}
+.glyphicon-folder-close:before {
+ content: "\e117";
+}
+.glyphicon-folder-open:before {
+ content: "\e118";
+}
+.glyphicon-resize-vertical:before {
+ content: "\e119";
+}
+.glyphicon-resize-horizontal:before {
+ content: "\e120";
+}
+.glyphicon-hdd:before {
+ content: "\e121";
+}
+.glyphicon-bullhorn:before {
+ content: "\e122";
+}
+.glyphicon-bell:before {
+ content: "\e123";
+}
+.glyphicon-certificate:before {
+ content: "\e124";
+}
+.glyphicon-thumbs-up:before {
+ content: "\e125";
+}
+.glyphicon-thumbs-down:before {
+ content: "\e126";
+}
+.glyphicon-hand-right:before {
+ content: "\e127";
+}
+.glyphicon-hand-left:before {
+ content: "\e128";
+}
+.glyphicon-hand-up:before {
+ content: "\e129";
+}
+.glyphicon-hand-down:before {
+ content: "\e130";
+}
+.glyphicon-circle-arrow-right:before {
+ content: "\e131";
+}
+.glyphicon-circle-arrow-left:before {
+ content: "\e132";
+}
+.glyphicon-circle-arrow-up:before {
+ content: "\e133";
+}
+.glyphicon-circle-arrow-down:before {
+ content: "\e134";
+}
+.glyphicon-globe:before {
+ content: "\e135";
+}
+.glyphicon-wrench:before {
+ content: "\e136";
+}
+.glyphicon-tasks:before {
+ content: "\e137";
+}
+.glyphicon-filter:before {
+ content: "\e138";
+}
+.glyphicon-briefcase:before {
+ content: "\e139";
+}
+.glyphicon-fullscreen:before {
+ content: "\e140";
+}
+.glyphicon-dashboard:before {
+ content: "\e141";
+}
+.glyphicon-paperclip:before {
+ content: "\e142";
+}
+.glyphicon-heart-empty:before {
+ content: "\e143";
+}
+.glyphicon-link:before {
+ content: "\e144";
+}
+.glyphicon-phone:before {
+ content: "\e145";
+}
+.glyphicon-pushpin:before {
+ content: "\e146";
+}
+.glyphicon-usd:before {
+ content: "\e148";
+}
+.glyphicon-gbp:before {
+ content: "\e149";
+}
+.glyphicon-sort:before {
+ content: "\e150";
+}
+.glyphicon-sort-by-alphabet:before {
+ content: "\e151";
+}
+.glyphicon-sort-by-alphabet-alt:before {
+ content: "\e152";
+}
+.glyphicon-sort-by-order:before {
+ content: "\e153";
+}
+.glyphicon-sort-by-order-alt:before {
+ content: "\e154";
+}
+.glyphicon-sort-by-attributes:before {
+ content: "\e155";
+}
+.glyphicon-sort-by-attributes-alt:before {
+ content: "\e156";
+}
+.glyphicon-unchecked:before {
+ content: "\e157";
+}
+.glyphicon-expand:before {
+ content: "\e158";
+}
+.glyphicon-collapse-down:before {
+ content: "\e159";
+}
+.glyphicon-collapse-up:before {
+ content: "\e160";
+}
+.glyphicon-log-in:before {
+ content: "\e161";
+}
+.glyphicon-flash:before {
+ content: "\e162";
+}
+.glyphicon-log-out:before {
+ content: "\e163";
+}
+.glyphicon-new-window:before {
+ content: "\e164";
+}
+.glyphicon-record:before {
+ content: "\e165";
+}
+.glyphicon-save:before {
+ content: "\e166";
+}
+.glyphicon-open:before {
+ content: "\e167";
+}
+.glyphicon-saved:before {
+ content: "\e168";
+}
+.glyphicon-import:before {
+ content: "\e169";
+}
+.glyphicon-export:before {
+ content: "\e170";
+}
+.glyphicon-send:before {
+ content: "\e171";
+}
+.glyphicon-floppy-disk:before {
+ content: "\e172";
+}
+.glyphicon-floppy-saved:before {
+ content: "\e173";
+}
+.glyphicon-floppy-remove:before {
+ content: "\e174";
+}
+.glyphicon-floppy-save:before {
+ content: "\e175";
+}
+.glyphicon-floppy-open:before {
+ content: "\e176";
+}
+.glyphicon-credit-card:before {
+ content: "\e177";
+}
+.glyphicon-transfer:before {
+ content: "\e178";
+}
+.glyphicon-cutlery:before {
+ content: "\e179";
+}
+.glyphicon-header:before {
+ content: "\e180";
+}
+.glyphicon-compressed:before {
+ content: "\e181";
+}
+.glyphicon-earphone:before {
+ content: "\e182";
+}
+.glyphicon-phone-alt:before {
+ content: "\e183";
+}
+.glyphicon-tower:before {
+ content: "\e184";
+}
+.glyphicon-stats:before {
+ content: "\e185";
+}
+.glyphicon-sd-video:before {
+ content: "\e186";
+}
+.glyphicon-hd-video:before {
+ content: "\e187";
+}
+.glyphicon-subtitles:before {
+ content: "\e188";
+}
+.glyphicon-sound-stereo:before {
+ content: "\e189";
+}
+.glyphicon-sound-dolby:before {
+ content: "\e190";
+}
+.glyphicon-sound-5-1:before {
+ content: "\e191";
+}
+.glyphicon-sound-6-1:before {
+ content: "\e192";
+}
+.glyphicon-sound-7-1:before {
+ content: "\e193";
+}
+.glyphicon-copyright-mark:before {
+ content: "\e194";
+}
+.glyphicon-registration-mark:before {
+ content: "\e195";
+}
+.glyphicon-cloud-download:before {
+ content: "\e197";
+}
+.glyphicon-cloud-upload:before {
+ content: "\e198";
+}
+.glyphicon-tree-conifer:before {
+ content: "\e199";
+}
+.glyphicon-tree-deciduous:before {
+ content: "\e200";
+}
+.glyphicon-cd:before {
+ content: "\e201";
+}
+.glyphicon-save-file:before {
+ content: "\e202";
+}
+.glyphicon-open-file:before {
+ content: "\e203";
+}
+.glyphicon-level-up:before {
+ content: "\e204";
+}
+.glyphicon-copy:before {
+ content: "\e205";
+}
+.glyphicon-paste:before {
+ content: "\e206";
+}
+.glyphicon-alert:before {
+ content: "\e209";
+}
+.glyphicon-equalizer:before {
+ content: "\e210";
+}
+.glyphicon-king:before {
+ content: "\e211";
+}
+.glyphicon-queen:before {
+ content: "\e212";
+}
+.glyphicon-pawn:before {
+ content: "\e213";
+}
+.glyphicon-bishop:before {
+ content: "\e214";
+}
+.glyphicon-knight:before {
+ content: "\e215";
+}
+.glyphicon-baby-formula:before {
+ content: "\e216";
+}
+.glyphicon-tent:before {
+ content: "\26fa";
+}
+.glyphicon-blackboard:before {
+ content: "\e218";
+}
+.glyphicon-bed:before {
+ content: "\e219";
+}
+.glyphicon-apple:before {
+ content: "\f8ff";
+}
+.glyphicon-erase:before {
+ content: "\e221";
+}
+.glyphicon-hourglass:before {
+ content: "\231b";
+}
+.glyphicon-lamp:before {
+ content: "\e223";
+}
+.glyphicon-duplicate:before {
+ content: "\e224";
+}
+.glyphicon-piggy-bank:before {
+ content: "\e225";
+}
+.glyphicon-scissors:before {
+ content: "\e226";
+}
+.glyphicon-bitcoin:before {
+ content: "\e227";
+}
+.glyphicon-yen:before {
+ content: "\00a5";
+}
+.glyphicon-ruble:before {
+ content: "\20bd";
+}
+.glyphicon-scale:before {
+ content: "\e230";
+}
+.glyphicon-ice-lolly:before {
+ content: "\e231";
+}
+.glyphicon-ice-lolly-tasted:before {
+ content: "\e232";
+}
+.glyphicon-education:before {
+ content: "\e233";
+}
+.glyphicon-option-horizontal:before {
+ content: "\e234";
+}
+.glyphicon-option-vertical:before {
+ content: "\e235";
+}
+.glyphicon-menu-hamburger:before {
+ content: "\e236";
+}
+.glyphicon-modal-window:before {
+ content: "\e237";
+}
+.glyphicon-oil:before {
+ content: "\e238";
+}
+.glyphicon-grain:before {
+ content: "\e239";
+}
+.glyphicon-sunglasses:before {
+ content: "\e240";
+}
+.glyphicon-text-size:before {
+ content: "\e241";
+}
+.glyphicon-text-color:before {
+ content: "\e242";
+}
+.glyphicon-text-background:before {
+ content: "\e243";
+}
+.glyphicon-object-align-top:before {
+ content: "\e244";
+}
+.glyphicon-object-align-bottom:before {
+ content: "\e245";
+}
+.glyphicon-object-align-horizontal:before {
+ content: "\e246";
+}
+.glyphicon-object-align-left:before {
+ content: "\e247";
+}
+.glyphicon-object-align-vertical:before {
+ content: "\e248";
+}
+.glyphicon-object-align-right:before {
+ content: "\e249";
+}
+.glyphicon-triangle-right:before {
+ content: "\e250";
+}
+.glyphicon-triangle-left:before {
+ content: "\e251";
+}
+.glyphicon-triangle-bottom:before {
+ content: "\e252";
+}
+.glyphicon-triangle-top:before {
+ content: "\e253";
+}
+.glyphicon-console:before {
+ content: "\e254";
+}
+.glyphicon-superscript:before {
+ content: "\e255";
+}
+.glyphicon-subscript:before {
+ content: "\e256";
+}
+.glyphicon-menu-left:before {
+ content: "\e257";
+}
+.glyphicon-menu-right:before {
+ content: "\e258";
+}
+.glyphicon-menu-down:before {
+ content: "\e259";
+}
+.glyphicon-menu-up:before {
+ content: "\e260";
+}
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+*:before,
+*:after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+html {
+ font-size: 10px;
+
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #333;
+ background-color: #fff;
+}
+input,
+button,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+a {
+ color: #337ab7;
+ text-decoration: none;
+}
+a:hover,
+a:focus {
+ color: #23527c;
+ text-decoration: underline;
+}
+a:focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+figure {
+ margin: 0;
+}
+img {
+ vertical-align: middle;
+}
+.img-responsive,
+.thumbnail > img,
+.thumbnail a > img,
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+.img-rounded {
+ border-radius: 6px;
+}
+.img-thumbnail {
+ display: inline-block;
+ max-width: 100%;
+ height: auto;
+ padding: 4px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: all .2s ease-in-out;
+ -o-transition: all .2s ease-in-out;
+ transition: all .2s ease-in-out;
+}
+.img-circle {
+ border-radius: 50%;
+}
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #eee;
+}
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+ font-family: inherit;
+ font-weight: 500;
+ line-height: 1.1;
+ color: inherit;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+ font-weight: normal;
+ line-height: 1;
+ color: #777;
+}
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3 {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+h1 small,
+.h1 small,
+h2 small,
+.h2 small,
+h3 small,
+.h3 small,
+h1 .small,
+.h1 .small,
+h2 .small,
+.h2 .small,
+h3 .small,
+.h3 .small {
+ font-size: 65%;
+}
+h4,
+.h4,
+h5,
+.h5,
+h6,
+.h6 {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+h4 small,
+.h4 small,
+h5 small,
+.h5 small,
+h6 small,
+.h6 small,
+h4 .small,
+.h4 .small,
+h5 .small,
+.h5 .small,
+h6 .small,
+.h6 .small {
+ font-size: 75%;
+}
+h1,
+.h1 {
+ font-size: 36px;
+}
+h2,
+.h2 {
+ font-size: 30px;
+}
+h3,
+.h3 {
+ font-size: 24px;
+}
+h4,
+.h4 {
+ font-size: 18px;
+}
+h5,
+.h5 {
+ font-size: 14px;
+}
+h6,
+.h6 {
+ font-size: 12px;
+}
+p {
+ margin: 0 0 10px;
+}
+.lead {
+ margin-bottom: 20px;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 1.4;
+}
+@media (min-width: 768px) {
+ .lead {
+ font-size: 21px;
+ }
+}
+small,
+.small {
+ font-size: 85%;
+}
+mark,
+.mark {
+ padding: .2em;
+ background-color: #fcf8e3;
+}
+.text-left {
+ text-align: left;
+}
+.text-right {
+ text-align: right;
+}
+.text-center {
+ text-align: center;
+}
+.text-justify {
+ text-align: justify;
+}
+.text-nowrap {
+ white-space: nowrap;
+}
+.text-lowercase {
+ text-transform: lowercase;
+}
+.text-uppercase {
+ text-transform: uppercase;
+}
+.text-capitalize {
+ text-transform: capitalize;
+}
+.text-muted {
+ color: #777;
+}
+.text-primary {
+ color: #337ab7;
+}
+a.text-primary:hover {
+ color: #286090;
+}
+.text-success {
+ color: #3c763d;
+}
+a.text-success:hover {
+ color: #2b542c;
+}
+.text-info {
+ color: #31708f;
+}
+a.text-info:hover {
+ color: #245269;
+}
+.text-warning {
+ color: #8a6d3b;
+}
+a.text-warning:hover {
+ color: #66512c;
+}
+.text-danger {
+ color: #a94442;
+}
+a.text-danger:hover {
+ color: #843534;
+}
+.bg-primary {
+ color: #fff;
+ background-color: #337ab7;
+}
+a.bg-primary:hover {
+ background-color: #286090;
+}
+.bg-success {
+ background-color: #dff0d8;
+}
+a.bg-success:hover {
+ background-color: #c1e2b3;
+}
+.bg-info {
+ background-color: #d9edf7;
+}
+a.bg-info:hover {
+ background-color: #afd9ee;
+}
+.bg-warning {
+ background-color: #fcf8e3;
+}
+a.bg-warning:hover {
+ background-color: #f7ecb5;
+}
+.bg-danger {
+ background-color: #f2dede;
+}
+a.bg-danger:hover {
+ background-color: #e4b9b9;
+}
+.page-header {
+ padding-bottom: 9px;
+ margin: 40px 0 20px;
+ border-bottom: 1px solid #eee;
+}
+ul,
+ol {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+ margin-bottom: 0;
+}
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+}
+.list-inline {
+ padding-left: 0;
+ margin-left: -5px;
+ list-style: none;
+}
+.list-inline > li {
+ display: inline-block;
+ padding-right: 5px;
+ padding-left: 5px;
+}
+dl {
+ margin-top: 0;
+ margin-bottom: 20px;
+}
+dt,
+dd {
+ line-height: 1.42857143;
+}
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-left: 0;
+}
+@media (min-width: 768px) {
+ .dl-horizontal dt {
+ float: left;
+ width: 160px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .dl-horizontal dd {
+ margin-left: 180px;
+ }
+}
+abbr[title],
+abbr[data-original-title] {
+ cursor: help;
+ border-bottom: 1px dotted #777;
+}
+.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ font-size: 17.5px;
+ border-left: 5px solid #eee;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+ margin-bottom: 0;
+}
+blockquote footer,
+blockquote small,
+blockquote .small {
+ display: block;
+ font-size: 80%;
+ line-height: 1.42857143;
+ color: #777;
+}
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+ content: '\2014 \00A0';
+}
+.blockquote-reverse,
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ text-align: right;
+ border-right: 5px solid #eee;
+ border-left: 0;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+ content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+ content: '\00A0 \2014';
+}
+address {
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 1.42857143;
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+kbd {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #fff;
+ background-color: #333;
+ border-radius: 3px;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+}
+kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ font-weight: bold;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 1.42857143;
+ color: #333;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border-radius: 0;
+}
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+.container {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+@media (min-width: 768px) {
+ .container {
+ width: 750px;
+ }
+}
+@media (min-width: 992px) {
+ .container {
+ width: 970px;
+ }
+}
+@media (min-width: 1200px) {
+ .container {
+ width: 1170px;
+ }
+}
+.container-fluid {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+.row {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+ position: relative;
+ min-height: 1px;
+ padding-right: 15px;
+ padding-left: 15px;
+}
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+ float: left;
+}
+.col-xs-12 {
+ width: 100%;
+}
+.col-xs-11 {
+ width: 91.66666667%;
+}
+.col-xs-10 {
+ width: 83.33333333%;
+}
+.col-xs-9 {
+ width: 75%;
+}
+.col-xs-8 {
+ width: 66.66666667%;
+}
+.col-xs-7 {
+ width: 58.33333333%;
+}
+.col-xs-6 {
+ width: 50%;
+}
+.col-xs-5 {
+ width: 41.66666667%;
+}
+.col-xs-4 {
+ width: 33.33333333%;
+}
+.col-xs-3 {
+ width: 25%;
+}
+.col-xs-2 {
+ width: 16.66666667%;
+}
+.col-xs-1 {
+ width: 8.33333333%;
+}
+.col-xs-pull-12 {
+ right: 100%;
+}
+.col-xs-pull-11 {
+ right: 91.66666667%;
+}
+.col-xs-pull-10 {
+ right: 83.33333333%;
+}
+.col-xs-pull-9 {
+ right: 75%;
+}
+.col-xs-pull-8 {
+ right: 66.66666667%;
+}
+.col-xs-pull-7 {
+ right: 58.33333333%;
+}
+.col-xs-pull-6 {
+ right: 50%;
+}
+.col-xs-pull-5 {
+ right: 41.66666667%;
+}
+.col-xs-pull-4 {
+ right: 33.33333333%;
+}
+.col-xs-pull-3 {
+ right: 25%;
+}
+.col-xs-pull-2 {
+ right: 16.66666667%;
+}
+.col-xs-pull-1 {
+ right: 8.33333333%;
+}
+.col-xs-pull-0 {
+ right: auto;
+}
+.col-xs-push-12 {
+ left: 100%;
+}
+.col-xs-push-11 {
+ left: 91.66666667%;
+}
+.col-xs-push-10 {
+ left: 83.33333333%;
+}
+.col-xs-push-9 {
+ left: 75%;
+}
+.col-xs-push-8 {
+ left: 66.66666667%;
+}
+.col-xs-push-7 {
+ left: 58.33333333%;
+}
+.col-xs-push-6 {
+ left: 50%;
+}
+.col-xs-push-5 {
+ left: 41.66666667%;
+}
+.col-xs-push-4 {
+ left: 33.33333333%;
+}
+.col-xs-push-3 {
+ left: 25%;
+}
+.col-xs-push-2 {
+ left: 16.66666667%;
+}
+.col-xs-push-1 {
+ left: 8.33333333%;
+}
+.col-xs-push-0 {
+ left: auto;
+}
+.col-xs-offset-12 {
+ margin-left: 100%;
+}
+.col-xs-offset-11 {
+ margin-left: 91.66666667%;
+}
+.col-xs-offset-10 {
+ margin-left: 83.33333333%;
+}
+.col-xs-offset-9 {
+ margin-left: 75%;
+}
+.col-xs-offset-8 {
+ margin-left: 66.66666667%;
+}
+.col-xs-offset-7 {
+ margin-left: 58.33333333%;
+}
+.col-xs-offset-6 {
+ margin-left: 50%;
+}
+.col-xs-offset-5 {
+ margin-left: 41.66666667%;
+}
+.col-xs-offset-4 {
+ margin-left: 33.33333333%;
+}
+.col-xs-offset-3 {
+ margin-left: 25%;
+}
+.col-xs-offset-2 {
+ margin-left: 16.66666667%;
+}
+.col-xs-offset-1 {
+ margin-left: 8.33333333%;
+}
+.col-xs-offset-0 {
+ margin-left: 0;
+}
+@media (min-width: 768px) {
+ .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+ float: left;
+ }
+ .col-sm-12 {
+ width: 100%;
+ }
+ .col-sm-11 {
+ width: 91.66666667%;
+ }
+ .col-sm-10 {
+ width: 83.33333333%;
+ }
+ .col-sm-9 {
+ width: 75%;
+ }
+ .col-sm-8 {
+ width: 66.66666667%;
+ }
+ .col-sm-7 {
+ width: 58.33333333%;
+ }
+ .col-sm-6 {
+ width: 50%;
+ }
+ .col-sm-5 {
+ width: 41.66666667%;
+ }
+ .col-sm-4 {
+ width: 33.33333333%;
+ }
+ .col-sm-3 {
+ width: 25%;
+ }
+ .col-sm-2 {
+ width: 16.66666667%;
+ }
+ .col-sm-1 {
+ width: 8.33333333%;
+ }
+ .col-sm-pull-12 {
+ right: 100%;
+ }
+ .col-sm-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-sm-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-sm-pull-9 {
+ right: 75%;
+ }
+ .col-sm-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-sm-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-sm-pull-6 {
+ right: 50%;
+ }
+ .col-sm-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-sm-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-sm-pull-3 {
+ right: 25%;
+ }
+ .col-sm-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-sm-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-sm-pull-0 {
+ right: auto;
+ }
+ .col-sm-push-12 {
+ left: 100%;
+ }
+ .col-sm-push-11 {
+ left: 91.66666667%;
+ }
+ .col-sm-push-10 {
+ left: 83.33333333%;
+ }
+ .col-sm-push-9 {
+ left: 75%;
+ }
+ .col-sm-push-8 {
+ left: 66.66666667%;
+ }
+ .col-sm-push-7 {
+ left: 58.33333333%;
+ }
+ .col-sm-push-6 {
+ left: 50%;
+ }
+ .col-sm-push-5 {
+ left: 41.66666667%;
+ }
+ .col-sm-push-4 {
+ left: 33.33333333%;
+ }
+ .col-sm-push-3 {
+ left: 25%;
+ }
+ .col-sm-push-2 {
+ left: 16.66666667%;
+ }
+ .col-sm-push-1 {
+ left: 8.33333333%;
+ }
+ .col-sm-push-0 {
+ left: auto;
+ }
+ .col-sm-offset-12 {
+ margin-left: 100%;
+ }
+ .col-sm-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-sm-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-sm-offset-9 {
+ margin-left: 75%;
+ }
+ .col-sm-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-sm-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-sm-offset-6 {
+ margin-left: 50%;
+ }
+ .col-sm-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-sm-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-sm-offset-3 {
+ margin-left: 25%;
+ }
+ .col-sm-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-sm-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-sm-offset-0 {
+ margin-left: 0;
+ }
+}
+@media (min-width: 992px) {
+ .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+ float: left;
+ }
+ .col-md-12 {
+ width: 100%;
+ }
+ .col-md-11 {
+ width: 91.66666667%;
+ }
+ .col-md-10 {
+ width: 83.33333333%;
+ }
+ .col-md-9 {
+ width: 75%;
+ }
+ .col-md-8 {
+ width: 66.66666667%;
+ }
+ .col-md-7 {
+ width: 58.33333333%;
+ }
+ .col-md-6 {
+ width: 50%;
+ }
+ .col-md-5 {
+ width: 41.66666667%;
+ }
+ .col-md-4 {
+ width: 33.33333333%;
+ }
+ .col-md-3 {
+ width: 25%;
+ }
+ .col-md-2 {
+ width: 16.66666667%;
+ }
+ .col-md-1 {
+ width: 8.33333333%;
+ }
+ .col-md-pull-12 {
+ right: 100%;
+ }
+ .col-md-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-md-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-md-pull-9 {
+ right: 75%;
+ }
+ .col-md-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-md-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-md-pull-6 {
+ right: 50%;
+ }
+ .col-md-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-md-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-md-pull-3 {
+ right: 25%;
+ }
+ .col-md-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-md-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-md-pull-0 {
+ right: auto;
+ }
+ .col-md-push-12 {
+ left: 100%;
+ }
+ .col-md-push-11 {
+ left: 91.66666667%;
+ }
+ .col-md-push-10 {
+ left: 83.33333333%;
+ }
+ .col-md-push-9 {
+ left: 75%;
+ }
+ .col-md-push-8 {
+ left: 66.66666667%;
+ }
+ .col-md-push-7 {
+ left: 58.33333333%;
+ }
+ .col-md-push-6 {
+ left: 50%;
+ }
+ .col-md-push-5 {
+ left: 41.66666667%;
+ }
+ .col-md-push-4 {
+ left: 33.33333333%;
+ }
+ .col-md-push-3 {
+ left: 25%;
+ }
+ .col-md-push-2 {
+ left: 16.66666667%;
+ }
+ .col-md-push-1 {
+ left: 8.33333333%;
+ }
+ .col-md-push-0 {
+ left: auto;
+ }
+ .col-md-offset-12 {
+ margin-left: 100%;
+ }
+ .col-md-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-md-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-md-offset-9 {
+ margin-left: 75%;
+ }
+ .col-md-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-md-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-md-offset-6 {
+ margin-left: 50%;
+ }
+ .col-md-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-md-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-md-offset-3 {
+ margin-left: 25%;
+ }
+ .col-md-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-md-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-md-offset-0 {
+ margin-left: 0;
+ }
+}
+@media (min-width: 1200px) {
+ .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+ float: left;
+ }
+ .col-lg-12 {
+ width: 100%;
+ }
+ .col-lg-11 {
+ width: 91.66666667%;
+ }
+ .col-lg-10 {
+ width: 83.33333333%;
+ }
+ .col-lg-9 {
+ width: 75%;
+ }
+ .col-lg-8 {
+ width: 66.66666667%;
+ }
+ .col-lg-7 {
+ width: 58.33333333%;
+ }
+ .col-lg-6 {
+ width: 50%;
+ }
+ .col-lg-5 {
+ width: 41.66666667%;
+ }
+ .col-lg-4 {
+ width: 33.33333333%;
+ }
+ .col-lg-3 {
+ width: 25%;
+ }
+ .col-lg-2 {
+ width: 16.66666667%;
+ }
+ .col-lg-1 {
+ width: 8.33333333%;
+ }
+ .col-lg-pull-12 {
+ right: 100%;
+ }
+ .col-lg-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-lg-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-lg-pull-9 {
+ right: 75%;
+ }
+ .col-lg-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-lg-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-lg-pull-6 {
+ right: 50%;
+ }
+ .col-lg-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-lg-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-lg-pull-3 {
+ right: 25%;
+ }
+ .col-lg-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-lg-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-lg-pull-0 {
+ right: auto;
+ }
+ .col-lg-push-12 {
+ left: 100%;
+ }
+ .col-lg-push-11 {
+ left: 91.66666667%;
+ }
+ .col-lg-push-10 {
+ left: 83.33333333%;
+ }
+ .col-lg-push-9 {
+ left: 75%;
+ }
+ .col-lg-push-8 {
+ left: 66.66666667%;
+ }
+ .col-lg-push-7 {
+ left: 58.33333333%;
+ }
+ .col-lg-push-6 {
+ left: 50%;
+ }
+ .col-lg-push-5 {
+ left: 41.66666667%;
+ }
+ .col-lg-push-4 {
+ left: 33.33333333%;
+ }
+ .col-lg-push-3 {
+ left: 25%;
+ }
+ .col-lg-push-2 {
+ left: 16.66666667%;
+ }
+ .col-lg-push-1 {
+ left: 8.33333333%;
+ }
+ .col-lg-push-0 {
+ left: auto;
+ }
+ .col-lg-offset-12 {
+ margin-left: 100%;
+ }
+ .col-lg-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-lg-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-lg-offset-9 {
+ margin-left: 75%;
+ }
+ .col-lg-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-lg-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-lg-offset-6 {
+ margin-left: 50%;
+ }
+ .col-lg-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-lg-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-lg-offset-3 {
+ margin-left: 25%;
+ }
+ .col-lg-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-lg-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-lg-offset-0 {
+ margin-left: 0;
+ }
+}
+table {
+ background-color: transparent;
+}
+caption {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ color: #777;
+ text-align: left;
+}
+th {
+ text-align: left;
+}
+.table {
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 20px;
+}
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+ padding: 8px;
+ line-height: 1.42857143;
+ vertical-align: top;
+ border-top: 1px solid #ddd;
+}
+.table > thead > tr > th {
+ vertical-align: bottom;
+ border-bottom: 2px solid #ddd;
+}
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+ border-top: 0;
+}
+.table > tbody + tbody {
+ border-top: 2px solid #ddd;
+}
+.table .table {
+ background-color: #fff;
+}
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+ padding: 5px;
+}
+.table-bordered {
+ border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+ border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+ border-bottom-width: 2px;
+}
+.table-striped > tbody > tr:nth-of-type(odd) {
+ background-color: #f9f9f9;
+}
+.table-hover > tbody > tr:hover {
+ background-color: #f5f5f5;
+}
+table col[class*="col-"] {
+ position: static;
+ display: table-column;
+ float: none;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+ position: static;
+ display: table-cell;
+ float: none;
+}
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+ background-color: #f5f5f5;
+}
+.table-hover > tbody > tr > td.active:hover,
+.table-hover > tbody > tr > th.active:hover,
+.table-hover > tbody > tr.active:hover > td,
+.table-hover > tbody > tr:hover > .active,
+.table-hover > tbody > tr.active:hover > th {
+ background-color: #e8e8e8;
+}
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+ background-color: #dff0d8;
+}
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr:hover > .success,
+.table-hover > tbody > tr.success:hover > th {
+ background-color: #d0e9c6;
+}
+.table > thead > tr > td.info,
+.table > tbody > tr > td.info,
+.table > tfoot > tr > td.info,
+.table > thead > tr > th.info,
+.table > tbody > tr > th.info,
+.table > tfoot > tr > th.info,
+.table > thead > tr.info > td,
+.table > tbody > tr.info > td,
+.table > tfoot > tr.info > td,
+.table > thead > tr.info > th,
+.table > tbody > tr.info > th,
+.table > tfoot > tr.info > th {
+ background-color: #d9edf7;
+}
+.table-hover > tbody > tr > td.info:hover,
+.table-hover > tbody > tr > th.info:hover,
+.table-hover > tbody > tr.info:hover > td,
+.table-hover > tbody > tr:hover > .info,
+.table-hover > tbody > tr.info:hover > th {
+ background-color: #c4e3f3;
+}
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+ background-color: #fcf8e3;
+}
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr:hover > .warning,
+.table-hover > tbody > tr.warning:hover > th {
+ background-color: #faf2cc;
+}
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+ background-color: #f2dede;
+}
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr:hover > .danger,
+.table-hover > tbody > tr.danger:hover > th {
+ background-color: #ebcccc;
+}
+.table-responsive {
+ min-height: .01%;
+ overflow-x: auto;
+}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-y: hidden;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ border: 1px solid #ddd;
+ }
+ .table-responsive > .table {
+ margin-bottom: 0;
+ }
+ .table-responsive > .table > thead > tr > th,
+ .table-responsive > .table > tbody > tr > th,
+ .table-responsive > .table > tfoot > tr > th,
+ .table-responsive > .table > thead > tr > td,
+ .table-responsive > .table > tbody > tr > td,
+ .table-responsive > .table > tfoot > tr > td {
+ white-space: nowrap;
+ }
+ .table-responsive > .table-bordered {
+ border: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:first-child,
+ .table-responsive > .table-bordered > tbody > tr > th:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+ .table-responsive > .table-bordered > thead > tr > td:first-child,
+ .table-responsive > .table-bordered > tbody > tr > td:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:last-child,
+ .table-responsive > .table-bordered > tbody > tr > th:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+ .table-responsive > .table-bordered > thead > tr > td:last-child,
+ .table-responsive > .table-bordered > tbody > tr > td:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+ }
+ .table-responsive > .table-bordered > tbody > tr:last-child > th,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+ .table-responsive > .table-bordered > tbody > tr:last-child > td,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+ border-bottom: 0;
+ }
+}
+fieldset {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: inherit;
+ color: #333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+label {
+ display: inline-block;
+ max-width: 100%;
+ margin-bottom: 5px;
+ font-weight: bold;
+}
+input[type="search"] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ line-height: normal;
+}
+input[type="file"] {
+ display: block;
+}
+input[type="range"] {
+ display: block;
+ width: 100%;
+}
+select[multiple],
+select[size] {
+ height: auto;
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+output {
+ display: block;
+ padding-top: 7px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+}
+.form-control {
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
+ -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.form-control:focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+}
+.form-control::-moz-placeholder {
+ color: #999;
+ opacity: 1;
+}
+.form-control:-ms-input-placeholder {
+ color: #999;
+}
+.form-control::-webkit-input-placeholder {
+ color: #999;
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+ cursor: not-allowed;
+ background-color: #eee;
+ opacity: 1;
+}
+textarea.form-control {
+ height: auto;
+}
+input[type="search"] {
+ -webkit-appearance: none;
+}
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+ input[type="date"],
+ input[type="time"],
+ input[type="datetime-local"],
+ input[type="month"] {
+ line-height: 34px;
+ }
+ input[type="date"].input-sm,
+ input[type="time"].input-sm,
+ input[type="datetime-local"].input-sm,
+ input[type="month"].input-sm,
+ .input-group-sm input[type="date"],
+ .input-group-sm input[type="time"],
+ .input-group-sm input[type="datetime-local"],
+ .input-group-sm input[type="month"] {
+ line-height: 30px;
+ }
+ input[type="date"].input-lg,
+ input[type="time"].input-lg,
+ input[type="datetime-local"].input-lg,
+ input[type="month"].input-lg,
+ .input-group-lg input[type="date"],
+ .input-group-lg input[type="time"],
+ .input-group-lg input[type="datetime-local"],
+ .input-group-lg input[type="month"] {
+ line-height: 46px;
+ }
+}
+.form-group {
+ margin-bottom: 15px;
+}
+.radio,
+.checkbox {
+ position: relative;
+ display: block;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+.radio label,
+.checkbox label {
+ min-height: 20px;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: normal;
+ cursor: pointer;
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+ position: absolute;
+ margin-top: 4px \9;
+ margin-left: -20px;
+}
+.radio + .radio,
+.checkbox + .checkbox {
+ margin-top: -5px;
+}
+.radio-inline,
+.checkbox-inline {
+ display: inline-block;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: normal;
+ vertical-align: middle;
+ cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+ margin-top: 0;
+ margin-left: 10px;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"].disabled,
+input[type="checkbox"].disabled,
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"] {
+ cursor: not-allowed;
+}
+.radio-inline.disabled,
+.checkbox-inline.disabled,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox-inline {
+ cursor: not-allowed;
+}
+.radio.disabled label,
+.checkbox.disabled label,
+fieldset[disabled] .radio label,
+fieldset[disabled] .checkbox label {
+ cursor: not-allowed;
+}
+.form-control-static {
+ padding-top: 7px;
+ padding-bottom: 7px;
+ margin-bottom: 0;
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+ padding-right: 0;
+ padding-left: 0;
+}
+.input-sm {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.input-sm {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.input-sm,
+select[multiple].input-sm {
+ height: auto;
+}
+.form-group-sm .form-control {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.form-group-sm .form-control {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.form-group-sm .form-control,
+select[multiple].form-group-sm .form-control {
+ height: auto;
+}
+.form-group-sm .form-control-static {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+}
+.input-lg {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.input-lg {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.input-lg,
+select[multiple].input-lg {
+ height: auto;
+}
+.form-group-lg .form-control {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.form-group-lg .form-control {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.form-group-lg .form-control,
+select[multiple].form-group-lg .form-control {
+ height: auto;
+}
+.form-group-lg .form-control-static {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+}
+.has-feedback {
+ position: relative;
+}
+.has-feedback .form-control {
+ padding-right: 42.5px;
+}
+.form-control-feedback {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ pointer-events: none;
+}
+.input-lg + .form-control-feedback {
+ width: 46px;
+ height: 46px;
+ line-height: 46px;
+}
+.input-sm + .form-control-feedback {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+}
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline,
+.has-success.radio label,
+.has-success.checkbox label,
+.has-success.radio-inline label,
+.has-success.checkbox-inline label {
+ color: #3c763d;
+}
+.has-success .form-control {
+ border-color: #3c763d;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-success .form-control:focus {
+ border-color: #2b542c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+}
+.has-success .input-group-addon {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #3c763d;
+}
+.has-success .form-control-feedback {
+ color: #3c763d;
+}
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline,
+.has-warning.radio label,
+.has-warning.checkbox label,
+.has-warning.radio-inline label,
+.has-warning.checkbox-inline label {
+ color: #8a6d3b;
+}
+.has-warning .form-control {
+ border-color: #8a6d3b;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-warning .form-control:focus {
+ border-color: #66512c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+}
+.has-warning .input-group-addon {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #8a6d3b;
+}
+.has-warning .form-control-feedback {
+ color: #8a6d3b;
+}
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline,
+.has-error.radio label,
+.has-error.checkbox label,
+.has-error.radio-inline label,
+.has-error.checkbox-inline label {
+ color: #a94442;
+}
+.has-error .form-control {
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-error .form-control:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+}
+.has-error .input-group-addon {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #a94442;
+}
+.has-error .form-control-feedback {
+ color: #a94442;
+}
+.has-feedback label ~ .form-control-feedback {
+ top: 25px;
+}
+.has-feedback label.sr-only ~ .form-control-feedback {
+ top: 0;
+}
+.help-block {
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: #737373;
+}
+@media (min-width: 768px) {
+ .form-inline .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle;
+ }
+ .form-inline .form-control-static {
+ display: inline-block;
+ }
+ .form-inline .input-group {
+ display: inline-table;
+ vertical-align: middle;
+ }
+ .form-inline .input-group .input-group-addon,
+ .form-inline .input-group .input-group-btn,
+ .form-inline .input-group .form-control {
+ width: auto;
+ }
+ .form-inline .input-group > .form-control {
+ width: 100%;
+ }
+ .form-inline .control-label {
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .radio,
+ .form-inline .checkbox {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .radio label,
+ .form-inline .checkbox label {
+ padding-left: 0;
+ }
+ .form-inline .radio input[type="radio"],
+ .form-inline .checkbox input[type="checkbox"] {
+ position: relative;
+ margin-left: 0;
+ }
+ .form-inline .has-feedback .form-control-feedback {
+ top: 0;
+ }
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+ padding-top: 7px;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox {
+ min-height: 27px;
+}
+.form-horizontal .form-group {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+@media (min-width: 768px) {
+ .form-horizontal .control-label {
+ padding-top: 7px;
+ margin-bottom: 0;
+ text-align: right;
+ }
+}
+.form-horizontal .has-feedback .form-control-feedback {
+ right: 15px;
+}
+@media (min-width: 768px) {
+ .form-horizontal .form-group-lg .control-label {
+ padding-top: 14.333333px;
+ }
+}
+@media (min-width: 768px) {
+ .form-horizontal .form-group-sm .control-label {
+ padding-top: 6px;
+ }
+}
+.btn {
+ display: inline-block;
+ padding: 6px 12px;
+ margin-bottom: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.btn:focus,
+.btn:active:focus,
+.btn.active:focus,
+.btn.focus,
+.btn:active.focus,
+.btn.active.focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.btn:hover,
+.btn:focus,
+.btn.focus {
+ color: #333;
+ text-decoration: none;
+}
+.btn:active,
+.btn.active {
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+ pointer-events: none;
+ cursor: not-allowed;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ opacity: .65;
+}
+.btn-default {
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus,
+.btn-default.focus,
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #adadad;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+ background-image: none;
+}
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled.focus,
+.btn-default[disabled].focus,
+fieldset[disabled] .btn-default.focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+ background-color: #fff;
+ border-color: #ccc;
+}
+.btn-default .badge {
+ color: #fff;
+ background-color: #333;
+}
+.btn-primary {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+}
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary.focus,
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+ background-image: none;
+}
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled.focus,
+.btn-primary[disabled].focus,
+fieldset[disabled] .btn-primary.focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+ background-color: #337ab7;
+ border-color: #2e6da4;
+}
+.btn-primary .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.btn-success {
+ color: #fff;
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+.btn-success:hover,
+.btn-success:focus,
+.btn-success.focus,
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+ color: #fff;
+ background-color: #449d44;
+ border-color: #398439;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+ background-image: none;
+}
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled.focus,
+.btn-success[disabled].focus,
+fieldset[disabled] .btn-success.focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+.btn-success .badge {
+ color: #5cb85c;
+ background-color: #fff;
+}
+.btn-info {
+ color: #fff;
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+.btn-info:hover,
+.btn-info:focus,
+.btn-info.focus,
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+ color: #fff;
+ background-color: #31b0d5;
+ border-color: #269abc;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+ background-image: none;
+}
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled.focus,
+.btn-info[disabled].focus,
+fieldset[disabled] .btn-info.focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+.btn-info .badge {
+ color: #5bc0de;
+ background-color: #fff;
+}
+.btn-warning {
+ color: #fff;
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning.focus,
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+ color: #fff;
+ background-color: #ec971f;
+ border-color: #d58512;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+ background-image: none;
+}
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled.focus,
+.btn-warning[disabled].focus,
+fieldset[disabled] .btn-warning.focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+.btn-warning .badge {
+ color: #f0ad4e;
+ background-color: #fff;
+}
+.btn-danger {
+ color: #fff;
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger.focus,
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+ color: #fff;
+ background-color: #c9302c;
+ border-color: #ac2925;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+ background-image: none;
+}
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled.focus,
+.btn-danger[disabled].focus,
+fieldset[disabled] .btn-danger.focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+.btn-danger .badge {
+ color: #d9534f;
+ background-color: #fff;
+}
+.btn-link {
+ font-weight: normal;
+ color: #337ab7;
+ border-radius: 0;
+}
+.btn-link,
+.btn-link:active,
+.btn-link.active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+ background-color: transparent;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+ border-color: transparent;
+}
+.btn-link:hover,
+.btn-link:focus {
+ color: #23527c;
+ text-decoration: underline;
+ background-color: transparent;
+}
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+ color: #777;
+ text-decoration: none;
+}
+.btn-lg,
+.btn-group-lg > .btn {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+.btn-sm,
+.btn-group-sm > .btn {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+.btn-xs,
+.btn-group-xs > .btn {
+ padding: 1px 5px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+.btn-block {
+ display: block;
+ width: 100%;
+}
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity .15s linear;
+ -o-transition: opacity .15s linear;
+ transition: opacity .15s linear;
+}
+.fade.in {
+ opacity: 1;
+}
+.collapse {
+ display: none;
+ visibility: hidden;
+}
+.collapse.in {
+ display: block;
+ visibility: visible;
+}
+tr.collapse.in {
+ display: table-row;
+}
+tbody.collapse.in {
+ display: table-row-group;
+}
+.collapsing {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition-timing-function: ease;
+ -o-transition-timing-function: ease;
+ transition-timing-function: ease;
+ -webkit-transition-duration: .35s;
+ -o-transition-duration: .35s;
+ transition-duration: .35s;
+ -webkit-transition-property: height, visibility;
+ -o-transition-property: height, visibility;
+ transition-property: height, visibility;
+}
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: 4px solid;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+}
+.dropup,
+.dropdown {
+ position: relative;
+}
+.dropdown-toggle:focus {
+ outline: 0;
+}
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ font-size: 14px;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+.dropdown-menu .divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+.dropdown-menu > li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.42857143;
+ color: #333;
+ white-space: nowrap;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ color: #262626;
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ color: #fff;
+ text-decoration: none;
+ background-color: #337ab7;
+ outline: 0;
+}
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ color: #777;
+}
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+ background-image: none;
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.open > .dropdown-menu {
+ display: block;
+}
+.open > a {
+ outline: 0;
+}
+.dropdown-menu-right {
+ right: 0;
+ left: auto;
+}
+.dropdown-menu-left {
+ right: auto;
+ left: 0;
+}
+.dropdown-header {
+ display: block;
+ padding: 3px 20px;
+ font-size: 12px;
+ line-height: 1.42857143;
+ color: #777;
+ white-space: nowrap;
+}
+.dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 990;
+}
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ content: "";
+ border-top: 0;
+ border-bottom: 4px solid;
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 2px;
+}
+@media (min-width: 768px) {
+ .navbar-right .dropdown-menu {
+ right: 0;
+ left: auto;
+ }
+ .navbar-right .dropdown-menu-left {
+ right: auto;
+ left: 0;
+ }
+}
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+ position: relative;
+ float: left;
+}
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+ z-index: 2;
+}
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+ margin-left: -1px;
+}
+.btn-toolbar {
+ margin-left: -5px;
+}
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group {
+ float: left;
+}
+.btn-toolbar > .btn,
+.btn-toolbar > .btn-group,
+.btn-toolbar > .input-group {
+ margin-left: 5px;
+}
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+ border-radius: 0;
+}
+.btn-group > .btn:first-child {
+ margin-left: 0;
+}
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group > .btn-group {
+ float: left;
+}
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+.btn-group > .btn + .dropdown-toggle {
+ padding-right: 8px;
+ padding-left: 8px;
+}
+.btn-group > .btn-lg + .dropdown-toggle {
+ padding-right: 12px;
+ padding-left: 12px;
+}
+.btn-group.open .dropdown-toggle {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-group.open .dropdown-toggle.btn-link {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.btn .caret {
+ margin-left: 0;
+}
+.btn-lg .caret {
+ border-width: 5px 5px 0;
+ border-bottom-width: 0;
+}
+.dropup .btn-lg .caret {
+ border-width: 0 5px 5px;
+}
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group,
+.btn-group-vertical > .btn-group > .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ max-width: 100%;
+}
+.btn-group-vertical > .btn-group > .btn {
+ float: none;
+}
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+ margin-top: -1px;
+ margin-left: 0;
+}
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 4px;
+}
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.btn-group-justified {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: separate;
+}
+.btn-group-justified > .btn,
+.btn-group-justified > .btn-group {
+ display: table-cell;
+ float: none;
+ width: 1%;
+}
+.btn-group-justified > .btn-group .btn {
+ width: 100%;
+}
+.btn-group-justified > .btn-group .dropdown-menu {
+ left: auto;
+}
+[data-toggle="buttons"] > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn input[type="checkbox"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+}
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate;
+}
+.input-group[class*="col-"] {
+ float: none;
+ padding-right: 0;
+ padding-left: 0;
+}
+.input-group .form-control {
+ position: relative;
+ z-index: 2;
+ float: left;
+ width: 100%;
+ margin-bottom: 0;
+}
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn,
+select[multiple].input-group-lg > .form-control,
+select[multiple].input-group-lg > .input-group-addon,
+select[multiple].input-group-lg > .input-group-btn > .btn {
+ height: auto;
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn,
+select[multiple].input-group-sm > .form-control,
+select[multiple].input-group-sm > .input-group-addon,
+select[multiple].input-group-sm > .input-group-btn > .btn {
+ height: auto;
+}
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+ display: table-cell;
+}
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+.input-group-addon,
+.input-group-btn {
+ width: 1%;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+.input-group-addon {
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1;
+ color: #555;
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+.input-group-addon.input-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+ border-radius: 3px;
+}
+.input-group-addon.input-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ border-radius: 6px;
+}
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+ margin-top: 0;
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.input-group-addon:first-child {
+ border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child),
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.input-group-addon:last-child {
+ border-left: 0;
+}
+.input-group-btn {
+ position: relative;
+ font-size: 0;
+ white-space: nowrap;
+}
+.input-group-btn > .btn {
+ position: relative;
+}
+.input-group-btn > .btn + .btn {
+ margin-left: -1px;
+}
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:focus,
+.input-group-btn > .btn:active {
+ z-index: 2;
+}
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group {
+ margin-right: -1px;
+}
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group {
+ margin-left: -1px;
+}
+.nav {
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+.nav > li {
+ position: relative;
+ display: block;
+}
+.nav > li > a {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+ text-decoration: none;
+ background-color: #eee;
+}
+.nav > li.disabled > a {
+ color: #777;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+ color: #777;
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+ background-color: #eee;
+ border-color: #337ab7;
+}
+.nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+.nav > li > a > img {
+ max-width: none;
+}
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+ float: left;
+ margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+ border-color: #eee #eee #ddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+ color: #555;
+ cursor: default;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+.nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+ float: none;
+}
+.nav-tabs.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+}
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-tabs.nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs.nav-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+ border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs.nav-justified > .active > a,
+ .nav-tabs.nav-justified > .active > a:hover,
+ .nav-tabs.nav-justified > .active > a:focus {
+ border-bottom-color: #fff;
+ }
+}
+.nav-pills > li {
+ float: left;
+}
+.nav-pills > li > a {
+ border-radius: 4px;
+}
+.nav-pills > li + li {
+ margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+ color: #fff;
+ background-color: #337ab7;
+}
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li + li {
+ margin-top: 2px;
+ margin-left: 0;
+}
+.nav-justified {
+ width: 100%;
+}
+.nav-justified > li {
+ float: none;
+}
+.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+}
+.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs-justified {
+ border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+ border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs-justified > .active > a,
+ .nav-tabs-justified > .active > a:hover,
+ .nav-tabs-justified > .active > a:focus {
+ border-bottom-color: #fff;
+ }
+}
+.tab-content > .tab-pane {
+ display: none;
+ visibility: hidden;
+}
+.tab-content > .active {
+ display: block;
+ visibility: visible;
+}
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.navbar {
+ position: relative;
+ min-height: 50px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+}
+@media (min-width: 768px) {
+ .navbar {
+ border-radius: 4px;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-header {
+ float: left;
+ }
+}
+.navbar-collapse {
+ padding-right: 15px;
+ padding-left: 15px;
+ overflow-x: visible;
+ -webkit-overflow-scrolling: touch;
+ border-top: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+}
+.navbar-collapse.in {
+ overflow-y: auto;
+}
+@media (min-width: 768px) {
+ .navbar-collapse {
+ width: auto;
+ border-top: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+ .navbar-collapse.collapse {
+ display: block !important;
+ height: auto !important;
+ padding-bottom: 0;
+ overflow: visible !important;
+ visibility: visible !important;
+ }
+ .navbar-collapse.in {
+ overflow-y: visible;
+ }
+ .navbar-fixed-top .navbar-collapse,
+ .navbar-static-top .navbar-collapse,
+ .navbar-fixed-bottom .navbar-collapse {
+ padding-right: 0;
+ padding-left: 0;
+ }
+}
+.navbar-fixed-top .navbar-collapse,
+.navbar-fixed-bottom .navbar-collapse {
+ max-height: 340px;
+}
+@media (max-device-width: 480px) and (orientation: landscape) {
+ .navbar-fixed-top .navbar-collapse,
+ .navbar-fixed-bottom .navbar-collapse {
+ max-height: 200px;
+ }
+}
+.container > .navbar-header,
+.container-fluid > .navbar-header,
+.container > .navbar-collapse,
+.container-fluid > .navbar-collapse {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+@media (min-width: 768px) {
+ .container > .navbar-header,
+ .container-fluid > .navbar-header,
+ .container > .navbar-collapse,
+ .container-fluid > .navbar-collapse {
+ margin-right: 0;
+ margin-left: 0;
+ }
+}
+.navbar-static-top {
+ z-index: 1000;
+ border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+ .navbar-static-top {
+ border-radius: 0;
+ }
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+}
+@media (min-width: 768px) {
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ border-radius: 0;
+ }
+}
+.navbar-fixed-top {
+ top: 0;
+ border-width: 0 0 1px;
+}
+.navbar-fixed-bottom {
+ bottom: 0;
+ margin-bottom: 0;
+ border-width: 1px 0 0;
+}
+.navbar-brand {
+ float: left;
+ height: 50px;
+ padding: 15px 15px;
+ font-size: 18px;
+ line-height: 20px;
+}
+.navbar-brand:hover,
+.navbar-brand:focus {
+ text-decoration: none;
+}
+.navbar-brand > img {
+ display: block;
+}
+@media (min-width: 768px) {
+ .navbar > .container .navbar-brand,
+ .navbar > .container-fluid .navbar-brand {
+ margin-left: -15px;
+ }
+}
+.navbar-toggle {
+ position: relative;
+ float: right;
+ padding: 9px 10px;
+ margin-top: 8px;
+ margin-right: 15px;
+ margin-bottom: 8px;
+ background-color: transparent;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.navbar-toggle:focus {
+ outline: 0;
+}
+.navbar-toggle .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px;
+}
+.navbar-toggle .icon-bar + .icon-bar {
+ margin-top: 4px;
+}
+@media (min-width: 768px) {
+ .navbar-toggle {
+ display: none;
+ }
+}
+.navbar-nav {
+ margin: 7.5px -15px;
+}
+.navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 20px;
+}
+@media (max-width: 767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+ .navbar-nav .open .dropdown-menu > li > a,
+ .navbar-nav .open .dropdown-menu .dropdown-header {
+ padding: 5px 15px 5px 25px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a {
+ line-height: 20px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-nav .open .dropdown-menu > li > a:focus {
+ background-image: none;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-nav {
+ float: left;
+ margin: 0;
+ }
+ .navbar-nav > li {
+ float: left;
+ }
+ .navbar-nav > li > a {
+ padding-top: 15px;
+ padding-bottom: 15px;
+ }
+}
+.navbar-form {
+ padding: 10px 15px;
+ margin-top: 8px;
+ margin-right: -15px;
+ margin-bottom: 8px;
+ margin-left: -15px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+}
+@media (min-width: 768px) {
+ .navbar-form .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle;
+ }
+ .navbar-form .form-control-static {
+ display: inline-block;
+ }
+ .navbar-form .input-group {
+ display: inline-table;
+ vertical-align: middle;
+ }
+ .navbar-form .input-group .input-group-addon,
+ .navbar-form .input-group .input-group-btn,
+ .navbar-form .input-group .form-control {
+ width: auto;
+ }
+ .navbar-form .input-group > .form-control {
+ width: 100%;
+ }
+ .navbar-form .control-label {
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .radio,
+ .navbar-form .checkbox {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .radio label,
+ .navbar-form .checkbox label {
+ padding-left: 0;
+ }
+ .navbar-form .radio input[type="radio"],
+ .navbar-form .checkbox input[type="checkbox"] {
+ position: relative;
+ margin-left: 0;
+ }
+ .navbar-form .has-feedback .form-control-feedback {
+ top: 0;
+ }
+}
+@media (max-width: 767px) {
+ .navbar-form .form-group {
+ margin-bottom: 5px;
+ }
+ .navbar-form .form-group:last-child {
+ margin-bottom: 0;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-form {
+ width: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-right: 0;
+ margin-left: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+}
+.navbar-nav > li > .dropdown-menu {
+ margin-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+ margin-bottom: 0;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.navbar-btn {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+.navbar-btn.btn-sm {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+.navbar-btn.btn-xs {
+ margin-top: 14px;
+ margin-bottom: 14px;
+}
+.navbar-text {
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+@media (min-width: 768px) {
+ .navbar-text {
+ float: left;
+ margin-right: 15px;
+ margin-left: 15px;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-left {
+ float: left !important;
+ }
+ .navbar-right {
+ float: right !important;
+ margin-right: -15px;
+ }
+ .navbar-right ~ .navbar-right {
+ margin-right: 0;
+ }
+}
+.navbar-default {
+ background-color: #f8f8f8;
+ border-color: #e7e7e7;
+}
+.navbar-default .navbar-brand {
+ color: #777;
+}
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+ color: #5e5e5e;
+ background-color: transparent;
+}
+.navbar-default .navbar-text {
+ color: #777;
+}
+.navbar-default .navbar-nav > li > a {
+ color: #777;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+ color: #333;
+ background-color: transparent;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+ color: #ccc;
+ background-color: transparent;
+}
+.navbar-default .navbar-toggle {
+ border-color: #ddd;
+}
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+ background-color: #ddd;
+}
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #888;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+ border-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+}
+@media (max-width: 767px) {
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #777;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #333;
+ background-color: transparent;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #ccc;
+ background-color: transparent;
+ }
+}
+.navbar-default .navbar-link {
+ color: #777;
+}
+.navbar-default .navbar-link:hover {
+ color: #333;
+}
+.navbar-default .btn-link {
+ color: #777;
+}
+.navbar-default .btn-link:hover,
+.navbar-default .btn-link:focus {
+ color: #333;
+}
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:hover,
+.navbar-default .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-default .btn-link:focus {
+ color: #ccc;
+}
+.navbar-inverse {
+ background-color: #222;
+ border-color: #080808;
+}
+.navbar-inverse .navbar-brand {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+ color: #fff;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-text {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+ color: #fff;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+ color: #fff;
+ background-color: #080808;
+}
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+ color: #444;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-toggle {
+ border-color: #333;
+}
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+ background-color: #333;
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+ background-color: #fff;
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+ border-color: #101010;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+ color: #fff;
+ background-color: #080808;
+}
+@media (max-width: 767px) {
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+ border-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+ background-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+ color: #9d9d9d;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #fff;
+ background-color: transparent;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #fff;
+ background-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #444;
+ background-color: transparent;
+ }
+}
+.navbar-inverse .navbar-link {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-link:hover {
+ color: #fff;
+}
+.navbar-inverse .btn-link {
+ color: #9d9d9d;
+}
+.navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link:focus {
+ color: #fff;
+}
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-inverse .btn-link:focus {
+ color: #444;
+}
+.breadcrumb {
+ padding: 8px 15px;
+ margin-bottom: 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+.breadcrumb > li {
+ display: inline-block;
+}
+.breadcrumb > li + li:before {
+ padding: 0 5px;
+ color: #ccc;
+ content: "/\00a0";
+}
+.breadcrumb > .active {
+ color: #777;
+}
+.pagination {
+ display: inline-block;
+ padding-left: 0;
+ margin: 20px 0;
+ border-radius: 4px;
+}
+.pagination > li {
+ display: inline;
+}
+.pagination > li > a,
+.pagination > li > span {
+ position: relative;
+ float: left;
+ padding: 6px 12px;
+ margin-left: -1px;
+ line-height: 1.42857143;
+ color: #337ab7;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid #ddd;
+}
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+ margin-left: 0;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+ color: #23527c;
+ background-color: #eee;
+ border-color: #ddd;
+}
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+ z-index: 2;
+ color: #fff;
+ cursor: default;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff;
+ border-color: #ddd;
+}
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+ padding: 10px 16px;
+ font-size: 18px;
+}
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+}
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+ padding: 5px 10px;
+ font-size: 12px;
+}
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+.pager {
+ padding-left: 0;
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+}
+.pager li {
+ display: inline;
+}
+.pager li > a,
+.pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 15px;
+}
+.pager li > a:hover,
+.pager li > a:focus {
+ text-decoration: none;
+ background-color: #eee;
+}
+.pager .next > a,
+.pager .next > span {
+ float: right;
+}
+.pager .previous > a,
+.pager .previous > span {
+ float: left;
+}
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff;
+}
+.label {
+ display: inline;
+ padding: .2em .6em .3em;
+ font-size: 75%;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: .25em;
+}
+a.label:hover,
+a.label:focus {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer;
+}
+.label:empty {
+ display: none;
+}
+.btn .label {
+ position: relative;
+ top: -1px;
+}
+.label-default {
+ background-color: #777;
+}
+.label-default[href]:hover,
+.label-default[href]:focus {
+ background-color: #5e5e5e;
+}
+.label-primary {
+ background-color: #337ab7;
+}
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+ background-color: #286090;
+}
+.label-success {
+ background-color: #5cb85c;
+}
+.label-success[href]:hover,
+.label-success[href]:focus {
+ background-color: #449d44;
+}
+.label-info {
+ background-color: #5bc0de;
+}
+.label-info[href]:hover,
+.label-info[href]:focus {
+ background-color: #31b0d5;
+}
+.label-warning {
+ background-color: #f0ad4e;
+}
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+ background-color: #ec971f;
+}
+.label-danger {
+ background-color: #d9534f;
+}
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+ background-color: #c9302c;
+}
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #777;
+ border-radius: 10px;
+}
+.badge:empty {
+ display: none;
+}
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+.btn-xs .badge {
+ top: 0;
+ padding: 1px 5px;
+}
+a.badge:hover,
+a.badge:focus {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer;
+}
+.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.list-group-item > .badge {
+ float: right;
+}
+.list-group-item > .badge + .badge {
+ margin-right: 5px;
+}
+.nav-pills > li > a > .badge {
+ margin-left: 3px;
+}
+.jumbotron {
+ padding: 30px 15px;
+ margin-bottom: 30px;
+ color: inherit;
+ background-color: #eee;
+}
+.jumbotron h1,
+.jumbotron .h1 {
+ color: inherit;
+}
+.jumbotron p {
+ margin-bottom: 15px;
+ font-size: 21px;
+ font-weight: 200;
+}
+.jumbotron > hr {
+ border-top-color: #d5d5d5;
+}
+.container .jumbotron,
+.container-fluid .jumbotron {
+ border-radius: 6px;
+}
+.jumbotron .container {
+ max-width: 100%;
+}
+@media screen and (min-width: 768px) {
+ .jumbotron {
+ padding: 48px 0;
+ }
+ .container .jumbotron,
+ .container-fluid .jumbotron {
+ padding-right: 60px;
+ padding-left: 60px;
+ }
+ .jumbotron h1,
+ .jumbotron .h1 {
+ font-size: 63px;
+ }
+}
+.thumbnail {
+ display: block;
+ padding: 4px;
+ margin-bottom: 20px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: border .2s ease-in-out;
+ -o-transition: border .2s ease-in-out;
+ transition: border .2s ease-in-out;
+}
+.thumbnail > img,
+.thumbnail a > img {
+ margin-right: auto;
+ margin-left: auto;
+}
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+ border-color: #337ab7;
+}
+.thumbnail .caption {
+ padding: 9px;
+ color: #333;
+}
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.alert h4 {
+ margin-top: 0;
+ color: inherit;
+}
+.alert .alert-link {
+ font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+ margin-bottom: 0;
+}
+.alert > p + p {
+ margin-top: 5px;
+}
+.alert-dismissable,
+.alert-dismissible {
+ padding-right: 35px;
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit;
+}
+.alert-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+.alert-success hr {
+ border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+ color: #2b542c;
+}
+.alert-info {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+.alert-info hr {
+ border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+ color: #245269;
+}
+.alert-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
+.alert-warning hr {
+ border-top-color: #f7e1b5;
+}
+.alert-warning .alert-link {
+ color: #66512c;
+}
+.alert-danger {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+.alert-danger hr {
+ border-top-color: #e4b9c0;
+}
+.alert-danger .alert-link {
+ color: #843534;
+}
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+}
+.progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #337ab7;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ -webkit-transition: width .6s ease;
+ -o-transition: width .6s ease;
+ transition: width .6s ease;
+}
+.progress-striped .progress-bar,
+.progress-bar-striped {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+.progress.active .progress-bar,
+.progress-bar.active {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-bar-success {
+ background-color: #5cb85c;
+}
+.progress-striped .progress-bar-success {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-info {
+ background-color: #5bc0de;
+}
+.progress-striped .progress-bar-info {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-warning {
+ background-color: #f0ad4e;
+}
+.progress-striped .progress-bar-warning {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-danger {
+ background-color: #d9534f;
+}
+.progress-striped .progress-bar-danger {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.media {
+ margin-top: 15px;
+}
+.media:first-child {
+ margin-top: 0;
+}
+.media,
+.media-body {
+ overflow: hidden;
+ zoom: 1;
+}
+.media-body {
+ width: 10000px;
+}
+.media-object {
+ display: block;
+}
+.media-right,
+.media > .pull-right {
+ padding-left: 10px;
+}
+.media-left,
+.media > .pull-left {
+ padding-right: 10px;
+}
+.media-left,
+.media-right,
+.media-body {
+ display: table-cell;
+ vertical-align: top;
+}
+.media-middle {
+ vertical-align: middle;
+}
+.media-bottom {
+ vertical-align: bottom;
+}
+.media-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.media-list {
+ padding-left: 0;
+ list-style: none;
+}
+.list-group {
+ padding-left: 0;
+ margin-bottom: 20px;
+}
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+}
+.list-group-item:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+.list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+a.list-group-item {
+ color: #555;
+}
+a.list-group-item .list-group-item-heading {
+ color: #333;
+}
+a.list-group-item:hover,
+a.list-group-item:focus {
+ color: #555;
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.list-group-item.disabled,
+.list-group-item.disabled:hover,
+.list-group-item.disabled:focus {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #eee;
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading {
+ color: inherit;
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text {
+ color: #777;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ z-index: 2;
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active .list-group-item-heading > small,
+.list-group-item.active:hover .list-group-item-heading > small,
+.list-group-item.active:focus .list-group-item-heading > small,
+.list-group-item.active .list-group-item-heading > .small,
+.list-group-item.active:hover .list-group-item-heading > .small,
+.list-group-item.active:focus .list-group-item-heading > .small {
+ color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+ color: #c7ddef;
+}
+.list-group-item-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+}
+a.list-group-item-success {
+ color: #3c763d;
+}
+a.list-group-item-success .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-success:hover,
+a.list-group-item-success:focus {
+ color: #3c763d;
+ background-color: #d0e9c6;
+}
+a.list-group-item-success.active,
+a.list-group-item-success.active:hover,
+a.list-group-item-success.active:focus {
+ color: #fff;
+ background-color: #3c763d;
+ border-color: #3c763d;
+}
+.list-group-item-info {
+ color: #31708f;
+ background-color: #d9edf7;
+}
+a.list-group-item-info {
+ color: #31708f;
+}
+a.list-group-item-info .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-info:hover,
+a.list-group-item-info:focus {
+ color: #31708f;
+ background-color: #c4e3f3;
+}
+a.list-group-item-info.active,
+a.list-group-item-info.active:hover,
+a.list-group-item-info.active:focus {
+ color: #fff;
+ background-color: #31708f;
+ border-color: #31708f;
+}
+.list-group-item-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+}
+a.list-group-item-warning {
+ color: #8a6d3b;
+}
+a.list-group-item-warning .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-warning:hover,
+a.list-group-item-warning:focus {
+ color: #8a6d3b;
+ background-color: #faf2cc;
+}
+a.list-group-item-warning.active,
+a.list-group-item-warning.active:hover,
+a.list-group-item-warning.active:focus {
+ color: #fff;
+ background-color: #8a6d3b;
+ border-color: #8a6d3b;
+}
+.list-group-item-danger {
+ color: #a94442;
+ background-color: #f2dede;
+}
+a.list-group-item-danger {
+ color: #a94442;
+}
+a.list-group-item-danger .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-danger:hover,
+a.list-group-item-danger:focus {
+ color: #a94442;
+ background-color: #ebcccc;
+}
+a.list-group-item-danger.active,
+a.list-group-item-danger.active:hover,
+a.list-group-item-danger.active:focus {
+ color: #fff;
+ background-color: #a94442;
+ border-color: #a94442;
+}
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3;
+}
+.panel {
+ margin-bottom: 20px;
+ background-color: #fff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+}
+.panel-body {
+ padding: 15px;
+}
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel-heading > .dropdown .dropdown-toggle {
+ color: inherit;
+}
+.panel-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 16px;
+ color: inherit;
+}
+.panel-title > a,
+.panel-title > small,
+.panel-title > .small,
+.panel-title > small > a,
+.panel-title > .small > a {
+ color: inherit;
+}
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
+ margin-bottom: 0;
+}
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
+ border-width: 1px 0;
+ border-radius: 0;
+}
+.panel > .list-group:first-child .list-group-item:first-child,
+.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+ border-top: 0;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .list-group:last-child .list-group-item:last-child,
+.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+ border-bottom: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+ border-top-width: 0;
+}
+.list-group + .panel-footer {
+ border-top-width: 0;
+}
+.panel > .table,
+.panel > .table-responsive > .table,
+.panel > .panel-collapse > .table {
+ margin-bottom: 0;
+}
+.panel > .table caption,
+.panel > .table-responsive > .table caption,
+.panel > .panel-collapse > .table caption {
+ padding-right: 15px;
+ padding-left: 15px;
+}
+.panel > .table:first-child,
+.panel > .table-responsive:first-child > .table:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+ border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+ border-top-right-radius: 3px;
+}
+.panel > .table:last-child,
+.panel > .table-responsive:last-child > .table:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+ border-bottom-right-radius: 3px;
+}
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive,
+.panel > .table + .panel-body,
+.panel > .table-responsive + .panel-body {
+ border-top: 1px solid #ddd;
+}
+.panel > .table > tbody:first-child > tr:first-child th,
+.panel > .table > tbody:first-child > tr:first-child td {
+ border-top: 0;
+}
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+ border: 0;
+}
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+}
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+}
+.panel > .table-bordered > thead > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
+.panel > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-bordered > thead > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
+.panel > .table-bordered > tbody > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
+ border-bottom: 0;
+}
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+ border-bottom: 0;
+}
+.panel > .table-responsive {
+ margin-bottom: 0;
+ border: 0;
+}
+.panel-group {
+ margin-bottom: 20px;
+}
+.panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 4px;
+}
+.panel-group .panel + .panel {
+ margin-top: 5px;
+}
+.panel-group .panel-heading {
+ border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse > .panel-body,
+.panel-group .panel-heading + .panel-collapse > .list-group {
+ border-top: 1px solid #ddd;
+}
+.panel-group .panel-footer {
+ border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+ border-bottom: 1px solid #ddd;
+}
+.panel-default {
+ border-color: #ddd;
+}
+.panel-default > .panel-heading {
+ color: #333;
+ background-color: #f5f5f5;
+ border-color: #ddd;
+}
+.panel-default > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ddd;
+}
+.panel-default > .panel-heading .badge {
+ color: #f5f5f5;
+ background-color: #333;
+}
+.panel-default > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ddd;
+}
+.panel-primary {
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #337ab7;
+}
+.panel-primary > .panel-heading .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.panel-primary > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #337ab7;
+}
+.panel-success {
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #d6e9c6;
+}
+.panel-success > .panel-heading .badge {
+ color: #dff0d8;
+ background-color: #3c763d;
+}
+.panel-success > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #d6e9c6;
+}
+.panel-info {
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #bce8f1;
+}
+.panel-info > .panel-heading .badge {
+ color: #d9edf7;
+ background-color: #31708f;
+}
+.panel-info > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #bce8f1;
+}
+.panel-warning {
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #faebcc;
+}
+.panel-warning > .panel-heading .badge {
+ color: #fcf8e3;
+ background-color: #8a6d3b;
+}
+.panel-warning > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #faebcc;
+}
+.panel-danger {
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ebccd1;
+}
+.panel-danger > .panel-heading .badge {
+ color: #f2dede;
+ background-color: #a94442;
+}
+.panel-danger > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ebccd1;
+}
+.embed-responsive {
+ position: relative;
+ display: block;
+ height: 0;
+ padding: 0;
+ overflow: hidden;
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+}
+.embed-responsive.embed-responsive-16by9 {
+ padding-bottom: 56.25%;
+}
+.embed-responsive.embed-responsive-4by3 {
+ padding-bottom: 75%;
+}
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+}
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, .15);
+}
+.well-lg {
+ padding: 24px;
+ border-radius: 6px;
+}
+.well-sm {
+ padding: 9px;
+ border-radius: 3px;
+}
+.close {
+ float: right;
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 1;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
+ filter: alpha(opacity=20);
+ opacity: .2;
+}
+.close:hover,
+.close:focus {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+button.close {
+ -webkit-appearance: none;
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+}
+.modal-open {
+ overflow: hidden;
+}
+.modal {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ display: none;
+ overflow: hidden;
+ -webkit-overflow-scrolling: touch;
+ outline: 0;
+}
+.modal.fade .modal-dialog {
+ -webkit-transition: -webkit-transform .3s ease-out;
+ -o-transition: -o-transform .3s ease-out;
+ transition: transform .3s ease-out;
+ -webkit-transform: translate(0, -25%);
+ -ms-transform: translate(0, -25%);
+ -o-transform: translate(0, -25%);
+ transform: translate(0, -25%);
+}
+.modal.in .modal-dialog {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ transform: translate(0, 0);
+}
+.modal-open .modal {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.modal-dialog {
+ position: relative;
+ width: auto;
+ margin: 10px;
+}
+.modal-content {
+ position: relative;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ outline: 0;
+ -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+ box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+}
+.modal-backdrop {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ background-color: #000;
+}
+.modal-backdrop.fade {
+ filter: alpha(opacity=0);
+ opacity: 0;
+}
+.modal-backdrop.in {
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+.modal-header {
+ min-height: 16.42857143px;
+ padding: 15px;
+ border-bottom: 1px solid #e5e5e5;
+}
+.modal-header .close {
+ margin-top: -2px;
+}
+.modal-title {
+ margin: 0;
+ line-height: 1.42857143;
+}
+.modal-body {
+ position: relative;
+ padding: 15px;
+}
+.modal-footer {
+ padding: 15px;
+ text-align: right;
+ border-top: 1px solid #e5e5e5;
+}
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+ margin-left: 0;
+}
+.modal-scrollbar-measure {
+ position: absolute;
+ top: -9999px;
+ width: 50px;
+ height: 50px;
+ overflow: scroll;
+}
+@media (min-width: 768px) {
+ .modal-dialog {
+ width: 600px;
+ margin: 30px auto;
+ }
+ .modal-content {
+ -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+ }
+ .modal-sm {
+ width: 300px;
+ }
+}
+@media (min-width: 992px) {
+ .modal-lg {
+ width: 900px;
+ }
+}
+.tooltip {
+ position: absolute;
+ z-index: 1070;
+ display: block;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 12px;
+ font-weight: normal;
+ line-height: 1.4;
+ visibility: visible;
+ filter: alpha(opacity=0);
+ opacity: 0;
+}
+.tooltip.in {
+ filter: alpha(opacity=90);
+ opacity: .9;
+}
+.tooltip.top {
+ padding: 5px 0;
+ margin-top: -3px;
+}
+.tooltip.right {
+ padding: 0 5px;
+ margin-left: 3px;
+}
+.tooltip.bottom {
+ padding: 5px 0;
+ margin-top: 3px;
+}
+.tooltip.left {
+ padding: 0 5px;
+ margin-left: -3px;
+}
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #fff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000;
+ border-radius: 4px;
+}
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.top-left .tooltip-arrow {
+ right: 5px;
+ bottom: 0;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.top-right .tooltip-arrow {
+ bottom: 0;
+ left: 5px;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-width: 5px 5px 5px 0;
+ border-right-color: #000;
+}
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-width: 5px 0 5px 5px;
+ border-left-color: #000;
+}
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.tooltip.bottom-left .tooltip-arrow {
+ top: 0;
+ right: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.tooltip.bottom-right .tooltip-arrow {
+ top: 0;
+ left: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1060;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-align: left;
+ white-space: normal;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+}
+.popover.top {
+ margin-top: -10px;
+}
+.popover.right {
+ margin-left: 10px;
+}
+.popover.bottom {
+ margin-top: 10px;
+}
+.popover.left {
+ margin-left: -10px;
+}
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0;
+}
+.popover-content {
+ padding: 9px 14px;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover > .arrow {
+ border-width: 11px;
+}
+.popover > .arrow:after {
+ content: "";
+ border-width: 10px;
+}
+.popover.top > .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: #999;
+ border-top-color: rgba(0, 0, 0, .25);
+ border-bottom-width: 0;
+}
+.popover.top > .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-color: #fff;
+ border-bottom-width: 0;
+}
+.popover.right > .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-right-color: #999;
+ border-right-color: rgba(0, 0, 0, .25);
+ border-left-width: 0;
+}
+.popover.right > .arrow:after {
+ bottom: -10px;
+ left: 1px;
+ content: " ";
+ border-right-color: #fff;
+ border-left-width: 0;
+}
+.popover.bottom > .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: #999;
+ border-bottom-color: rgba(0, 0, 0, .25);
+}
+.popover.bottom > .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-width: 0;
+ border-bottom-color: #fff;
+}
+.popover.left > .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-right-width: 0;
+ border-left-color: #999;
+ border-left-color: rgba(0, 0, 0, .25);
+}
+.popover.left > .arrow:after {
+ right: 1px;
+ bottom: -10px;
+ content: " ";
+ border-right-width: 0;
+ border-left-color: #fff;
+}
+.carousel {
+ position: relative;
+}
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+.carousel-inner > .item {
+ position: relative;
+ display: none;
+ -webkit-transition: .6s ease-in-out left;
+ -o-transition: .6s ease-in-out left;
+ transition: .6s ease-in-out left;
+}
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ line-height: 1;
+}
+@media all and (transform-3d), (-webkit-transform-3d) {
+ .carousel-inner > .item {
+ -webkit-transition: -webkit-transform .6s ease-in-out;
+ -o-transition: -o-transform .6s ease-in-out;
+ transition: transform .6s ease-in-out;
+
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ perspective: 1000;
+ }
+ .carousel-inner > .item.next,
+ .carousel-inner > .item.active.right {
+ left: 0;
+ -webkit-transform: translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0);
+ }
+ .carousel-inner > .item.prev,
+ .carousel-inner > .item.active.left {
+ left: 0;
+ -webkit-transform: translate3d(-100%, 0, 0);
+ transform: translate3d(-100%, 0, 0);
+ }
+ .carousel-inner > .item.next.left,
+ .carousel-inner > .item.prev.right,
+ .carousel-inner > .item.active {
+ left: 0;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ display: block;
+}
+.carousel-inner > .active {
+ left: 0;
+}
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+.carousel-inner > .next {
+ left: 100%;
+}
+.carousel-inner > .prev {
+ left: -100%;
+}
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+ left: 0;
+}
+.carousel-inner > .active.left {
+ left: -100%;
+}
+.carousel-inner > .active.right {
+ left: 100%;
+}
+.carousel-control {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 15%;
+ font-size: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+.carousel-control.left {
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+ background-repeat: repeat-x;
+}
+.carousel-control.right {
+ right: 0;
+ left: auto;
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+ background-repeat: repeat-x;
+}
+.carousel-control:hover,
+.carousel-control:focus {
+ color: #fff;
+ text-decoration: none;
+ filter: alpha(opacity=90);
+ outline: 0;
+ opacity: .9;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+ position: absolute;
+ top: 50%;
+ z-index: 5;
+ display: inline-block;
+}
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+ left: 50%;
+ margin-left: -10px;
+}
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+ right: 50%;
+ margin-right: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+ width: 20px;
+ height: 20px;
+ margin-top: -10px;
+ font-family: serif;
+ line-height: 1;
+}
+.carousel-control .icon-prev:before {
+ content: '\2039';
+}
+.carousel-control .icon-next:before {
+ content: '\203a';
+}
+.carousel-indicators {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ z-index: 15;
+ width: 60%;
+ padding-left: 0;
+ margin-left: -30%;
+ text-align: center;
+ list-style: none;
+}
+.carousel-indicators li {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin: 1px;
+ text-indent: -999px;
+ cursor: pointer;
+ background-color: #000 \9;
+ background-color: rgba(0, 0, 0, 0);
+ border: 1px solid #fff;
+ border-radius: 10px;
+}
+.carousel-indicators .active {
+ width: 12px;
+ height: 12px;
+ margin: 0;
+ background-color: #fff;
+}
+.carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 20px;
+ left: 15%;
+ z-index: 10;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+}
+.carousel-caption .btn {
+ text-shadow: none;
+}
+@media screen and (min-width: 768px) {
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-prev,
+ .carousel-control .icon-next {
+ width: 30px;
+ height: 30px;
+ margin-top: -15px;
+ font-size: 30px;
+ }
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .icon-prev {
+ margin-left: -15px;
+ }
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-next {
+ margin-right: -15px;
+ }
+ .carousel-caption {
+ right: 20%;
+ left: 20%;
+ padding-bottom: 30px;
+ }
+ .carousel-indicators {
+ bottom: 20px;
+ }
+}
+.clearfix:before,
+.clearfix:after,
+.dl-horizontal dd:before,
+.dl-horizontal dd:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after,
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after,
+.btn-toolbar:before,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after,
+.nav:before,
+.nav:after,
+.navbar:before,
+.navbar:after,
+.navbar-header:before,
+.navbar-header:after,
+.navbar-collapse:before,
+.navbar-collapse:after,
+.pager:before,
+.pager:after,
+.panel-body:before,
+.panel-body:after,
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ content: " ";
+}
+.clearfix:after,
+.dl-horizontal dd:after,
+.container:after,
+.container-fluid:after,
+.row:after,
+.form-horizontal .form-group:after,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:after,
+.nav:after,
+.navbar:after,
+.navbar-header:after,
+.navbar-collapse:after,
+.pager:after,
+.panel-body:after,
+.modal-footer:after {
+ clear: both;
+}
+.center-block {
+ display: block;
+ margin-right: auto;
+ margin-left: auto;
+}
+.pull-right {
+ float: right !important;
+}
+.pull-left {
+ float: left !important;
+}
+.hide {
+ display: none !important;
+}
+.show {
+ display: block !important;
+}
+.invisible {
+ visibility: hidden;
+}
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+.hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+.affix {
+ position: fixed;
+}
+@-ms-viewport {
+ width: device-width;
+}
+.visible-xs,
+.visible-sm,
+.visible-md,
+.visible-lg {
+ display: none !important;
+}
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block {
+ display: none !important;
+}
+@media (max-width: 767px) {
+ .visible-xs {
+ display: block !important;
+ }
+ table.visible-xs {
+ display: table;
+ }
+ tr.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-xs,
+ td.visible-xs {
+ display: table-cell !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-block {
+ display: block !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-inline {
+ display: inline !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm {
+ display: block !important;
+ }
+ table.visible-sm {
+ display: table;
+ }
+ tr.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-sm,
+ td.visible-sm {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-block {
+ display: block !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md {
+ display: block !important;
+ }
+ table.visible-md {
+ display: table;
+ }
+ tr.visible-md {
+ display: table-row !important;
+ }
+ th.visible-md,
+ td.visible-md {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-block {
+ display: block !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg {
+ display: block !important;
+ }
+ table.visible-lg {
+ display: table;
+ }
+ tr.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-lg,
+ td.visible-lg {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-block {
+ display: block !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (max-width: 767px) {
+ .hidden-xs {
+ display: none !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-sm {
+ display: none !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-md {
+ display: none !important;
+ }
+}
+@media (min-width: 1200px) {
+ .hidden-lg {
+ display: none !important;
+ }
+}
+.visible-print {
+ display: none !important;
+}
+@media print {
+ .visible-print {
+ display: block !important;
+ }
+ table.visible-print {
+ display: table;
+ }
+ tr.visible-print {
+ display: table-row !important;
+ }
+ th.visible-print,
+ td.visible-print {
+ display: table-cell !important;
+ }
+}
+.visible-print-block {
+ display: none !important;
+}
+@media print {
+ .visible-print-block {
+ display: block !important;
+ }
+}
+.visible-print-inline {
+ display: none !important;
+}
+@media print {
+ .visible-print-inline {
+ display: inline !important;
+ }
+}
+.visible-print-inline-block {
+ display: none !important;
+}
+@media print {
+ .visible-print-inline-block {
+ display: inline-block !important;
+ }
+}
+@media print {
+ .hidden-print {
+ display: none !important;
+ }
+}
+/*# sourceMappingURL=bootstrap.css.map */
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map
new file mode 100644
index 000000000..ff579ff56
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA,6DAA4D;ACQ5D;EACE,yBAAA;EACA,4BAAA;EACA,gCAAA;EDND;ACaD;EACE,WAAA;EDXD;ACwBD;;;;;;;;;;;;;EAaE,gBAAA;EDtBD;AC8BD;;;;EAIE,uBAAA;EACA,0BAAA;ED5BD;ACoCD;EACE,eAAA;EACA,WAAA;EDlCD;AC0CD;;EAEE,eAAA;EDxCD;ACkDD;EACE,+BAAA;EDhDD;ACuDD;;EAEE,YAAA;EDrDD;AC+DD;EACE,2BAAA;ED7DD;ACoED;;EAEE,mBAAA;EDlED;ACyED;EACE,oBAAA;EDvED;AC+ED;EACE,gBAAA;EACA,kBAAA;ED7ED;ACoFD;EACE,kBAAA;EACA,aAAA;EDlFD;ACyFD;EACE,gBAAA;EDvFD;AC8FD;;EAEE,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,0BAAA;ED5FD;AC+FD;EACE,aAAA;ED7FD;ACgGD;EACE,iBAAA;ED9FD;ACwGD;EACE,WAAA;EDtGD;AC6GD;EACE,kBAAA;ED3GD;ACqHD;EACE,kBAAA;EDnHD;AC0HD;EACE,8BAAA;EACA,iCAAA;UAAA,yBAAA;EACA,WAAA;EDxHD;AC+HD;EACE,gBAAA;ED7HD;ACoID;;;;EAIE,mCAAA;EACA,gBAAA;EDlID;ACoJD;;;;;EAKE,gBAAA;EACA,eAAA;EACA,WAAA;EDlJD;ACyJD;EACE,mBAAA;EDvJD;ACiKD;;EAEE,sBAAA;ED/JD;AC0KD;;;;EAIE,4BAAA;EACA,iBAAA;EDxKD;AC+KD;;EAEE,iBAAA;ED7KD;ACoLD;;EAEE,WAAA;EACA,YAAA;EDlLD;AC0LD;EACE,qBAAA;EDxLD;ACmMD;;EAEE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,YAAA;EDjMD;AC0MD;;EAEE,cAAA;EDxMD;ACiND;EACE,+BAAA;EACA,8BAAA;EACA,iCAAA;EACA,yBAAA;ED/MD;ACwND;;EAEE,0BAAA;EDtND;AC6ND;EACE,2BAAA;EACA,eAAA;EACA,gCAAA;ED3ND;ACmOD;EACE,WAAA;EACA,YAAA;EDjOD;ACwOD;EACE,gBAAA;EDtOD;AC8OD;EACE,mBAAA;ED5OD;ACsPD;EACE,2BAAA;EACA,mBAAA;EDpPD;ACuPD;;EAEE,YAAA;EDrPD;AACD,sFAAqF;AE1ErF;EAnGI;;;IAGI,oCAAA;IACA,wBAAA;IACA,qCAAA;YAAA,6BAAA;IACA,8BAAA;IFgLL;EE7KC;;IAEI,4BAAA;IF+KL;EE5KC;IACI,8BAAA;IF8KL;EE3KC;IACI,+BAAA;IF6KL;EExKC;;IAEI,aAAA;IF0KL;EEvKC;;IAEI,wBAAA;IACA,0BAAA;IFyKL;EEtKC;IACI,6BAAA;IFwKL;EErKC;;IAEI,0BAAA;IFuKL;EEpKC;IACI,4BAAA;IFsKL;EEnKC;;;IAGI,YAAA;IACA,WAAA;IFqKL;EElKC;;IAEI,yBAAA;IFoKL;EE7JC;IACI,6BAAA;IF+JL;EE3JC;IACI,eAAA;IF6JL;EE3JC;;IAGQ,mCAAA;IF4JT;EEzJC;IACI,wBAAA;IF2JL;EExJC;IACI,sCAAA;IF0JL;EE3JC;;IAKQ,mCAAA;IF0JT;EEvJC;;IAGQ,mCAAA;IFwJT;EACF;AGpPD;EACE,qCAAA;EACA,uDAAA;EACA,iYAAA;EHsPD;AG9OD;EACE,oBAAA;EACA,UAAA;EACA,uBAAA;EACA,qCAAA;EACA,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,qCAAA;EACA,oCAAA;EHgPD;AG5OmC;EAAW,gBAAA;EH+O9C;AG9OmC;EAAW,gBAAA;EHiP9C;AG/OmC;;EAAW,kBAAA;EHmP9C;AGlPmC;EAAW,kBAAA;EHqP9C;AGpPmC;EAAW,kBAAA;EHuP9C;AGtPmC;EAAW,kBAAA;EHyP9C;AGxPmC;EAAW,kBAAA;EH2P9C;AG1PmC;EAAW,kBAAA;EH6P9C;AG5PmC;EAAW,kBAAA;EH+P9C;AG9PmC;EAAW,kBAAA;EHiQ9C;AGhQmC;EAAW,kBAAA;EHmQ9C;AGlQmC;EAAW,kBAAA;EHqQ9C;AGpQmC;EAAW,kBAAA;EHuQ9C;AGtQmC;EAAW,kBAAA;EHyQ9C;AGxQmC;EAAW,kBAAA;EH2Q9C;AG1QmC;EAAW,kBAAA;EH6Q9C;AG5QmC;EAAW,kBAAA;EH+Q9C;AG9QmC;EAAW,kBAAA;EHiR9C;AGhRmC;EAAW,kBAAA;EHmR9C;AGlRmC;EAAW,kBAAA;EHqR9C;AGpRmC;EAAW,kBAAA;EHuR9C;AGtRmC;EAAW,kBAAA;EHyR9C;AGxRmC;EAAW,kBAAA;EH2R9C;AG1RmC;EAAW,kBAAA;EH6R9C;AG5RmC;EAAW,kBAAA;EH+R9C;AG9RmC;EAAW,kBAAA;EHiS9C;AGhSmC;EAAW,kBAAA;EHmS9C;AGlSmC;EAAW,kBAAA;EHqS9C;AGpSmC;EAAW,kBAAA;EHuS9C;AGtSmC;EAAW,kBAAA;EHyS9C;AGxSmC;EAAW,kBAAA;EH2S9C;AG1SmC;EAAW,kBAAA;EH6S9C;AG5SmC;EAAW,kBAAA;EH+S9C;AG9SmC;EAAW,kBAAA;EHiT9C;AGhTmC;EAAW,kBAAA;EHmT9C;AGlTmC;EAAW,kBAAA;EHqT9C;AGpTmC;EAAW,kBAAA;EHuT9C;AGtTmC;EAAW,kBAAA;EHyT9C;AGxTmC;EAAW,kBAAA;EH2T9C;AG1TmC;EAAW,kBAAA;EH6T9C;AG5TmC;EAAW,kBAAA;EH+T9C;AG9TmC;EAAW,kBAAA;EHiU9C;AGhUmC;EAAW,kBAAA;EHmU9C;AGlUmC;EAAW,kBAAA;EHqU9C;AGpUmC;EAAW,kBAAA;EHuU9C;AGtUmC;EAAW,kBAAA;EHyU9C;AGxUmC;EAAW,kBAAA;EH2U9C;AG1UmC;EAAW,kBAAA;EH6U9C;AG5UmC;EAAW,kBAAA;EH+U9C;AG9UmC;EAAW,kBAAA;EHiV9C;AGhVmC;EAAW,kBAAA;EHmV9C;AGlVmC;EAAW,kBAAA;EHqV9C;AGpVmC;EAAW,kBAAA;EHuV9C;AGtVmC;EAAW,kBAAA;EHyV9C;AGxVmC;EAAW,kBAAA;EH2V9C;AG1VmC;EAAW,kBAAA;EH6V9C;AG5VmC;EAAW,kBAAA;EH+V9C;AG9VmC;EAAW,kBAAA;EHiW9C;AGhWmC;EAAW,kBAAA;EHmW9C;AGlWmC;EAAW,kBAAA;EHqW9C;AGpWmC;EAAW,kBAAA;EHuW9C;AGtWmC;EAAW,kBAAA;EHyW9C;AGxWmC;EAAW,kBAAA;EH2W9C;AG1WmC;EAAW,kBAAA;EH6W9C;AG5WmC;EAAW,kBAAA;EH+W9C;AG9WmC;EAAW,kBAAA;EHiX9C;AGhXmC;EAAW,kBAAA;EHmX9C;AGlXmC;EAAW,kBAAA;EHqX9C;AGpXmC;EAAW,kBAAA;EHuX9C;AGtXmC;EAAW,kBAAA;EHyX9C;AGxXmC;EAAW,kBAAA;EH2X9C;AG1XmC;EAAW,kBAAA;EH6X9C;AG5XmC;EAAW,kBAAA;EH+X9C;AG9XmC;EAAW,kBAAA;EHiY9C;AGhYmC;EAAW,kBAAA;EHmY9C;AGlYmC;EAAW,kBAAA;EHqY9C;AGpYmC;EAAW,kBAAA;EHuY9C;AGtYmC;EAAW,kBAAA;EHyY9C;AGxYmC;EAAW,kBAAA;EH2Y9C;AG1YmC;EAAW,kBAAA;EH6Y9C;AG5YmC;EAAW,kBAAA;EH+Y9C;AG9YmC;EAAW,kBAAA;EHiZ9C;AGhZmC;EAAW,kBAAA;EHmZ9C;AGlZmC;EAAW,kBAAA;EHqZ9C;AGpZmC;EAAW,kBAAA;EHuZ9C;AGtZmC;EAAW,kBAAA;EHyZ9C;AGxZmC;EAAW,kBAAA;EH2Z9C;AG1ZmC;EAAW,kBAAA;EH6Z9C;AG5ZmC;EAAW,kBAAA;EH+Z9C;AG9ZmC;EAAW,kBAAA;EHia9C;AGhamC;EAAW,kBAAA;EHma9C;AGlamC;EAAW,kBAAA;EHqa9C;AGpamC;EAAW,kBAAA;EHua9C;AGtamC;EAAW,kBAAA;EHya9C;AGxamC;EAAW,kBAAA;EH2a9C;AG1amC;EAAW,kBAAA;EH6a9C;AG5amC;EAAW,kBAAA;EH+a9C;AG9amC;EAAW,kBAAA;EHib9C;AGhbmC;EAAW,kBAAA;EHmb9C;AGlbmC;EAAW,kBAAA;EHqb9C;AGpbmC;EAAW,kBAAA;EHub9C;AGtbmC;EAAW,kBAAA;EHyb9C;AGxbmC;EAAW,kBAAA;EH2b9C;AG1bmC;EAAW,kBAAA;EH6b9C;AG5bmC;EAAW,kBAAA;EH+b9C;AG9bmC;EAAW,kBAAA;EHic9C;AGhcmC;EAAW,kBAAA;EHmc9C;AGlcmC;EAAW,kBAAA;EHqc9C;AGpcmC;EAAW,kBAAA;EHuc9C;AGtcmC;EAAW,kBAAA;EHyc9C;AGxcmC;EAAW,kBAAA;EH2c9C;AG1cmC;EAAW,kBAAA;EH6c9C;AG5cmC;EAAW,kBAAA;EH+c9C;AG9cmC;EAAW,kBAAA;EHid9C;AGhdmC;EAAW,kBAAA;EHmd9C;AGldmC;EAAW,kBAAA;EHqd9C;AGpdmC;EAAW,kBAAA;EHud9C;AGtdmC;EAAW,kBAAA;EHyd9C;AGxdmC;EAAW,kBAAA;EH2d9C;AG1dmC;EAAW,kBAAA;EH6d9C;AG5dmC;EAAW,kBAAA;EH+d9C;AG9dmC;EAAW,kBAAA;EHie9C;AGhemC;EAAW,kBAAA;EHme9C;AGlemC;EAAW,kBAAA;EHqe9C;AGpemC;EAAW,kBAAA;EHue9C;AGtemC;EAAW,kBAAA;EHye9C;AGxemC;EAAW,kBAAA;EH2e9C;AG1emC;EAAW,kBAAA;EH6e9C;AG5emC;EAAW,kBAAA;EH+e9C;AG9emC;EAAW,kBAAA;EHif9C;AGhfmC;EAAW,kBAAA;EHmf9C;AGlfmC;EAAW,kBAAA;EHqf9C;AGpfmC;EAAW,kBAAA;EHuf9C;AGtfmC;EAAW,kBAAA;EHyf9C;AGxfmC;EAAW,kBAAA;EH2f9C;AG1fmC;EAAW,kBAAA;EH6f9C;AG5fmC;EAAW,kBAAA;EH+f9C;AG9fmC;EAAW,kBAAA;EHigB9C;AGhgBmC;EAAW,kBAAA;EHmgB9C;AGlgBmC;EAAW,kBAAA;EHqgB9C;AGpgBmC;EAAW,kBAAA;EHugB9C;AGtgBmC;EAAW,kBAAA;EHygB9C;AGxgBmC;EAAW,kBAAA;EH2gB9C;AG1gBmC;EAAW,kBAAA;EH6gB9C;AG5gBmC;EAAW,kBAAA;EH+gB9C;AG9gBmC;EAAW,kBAAA;EHihB9C;AGhhBmC;EAAW,kBAAA;EHmhB9C;AGlhBmC;EAAW,kBAAA;EHqhB9C;AGphBmC;EAAW,kBAAA;EHuhB9C;AGthBmC;EAAW,kBAAA;EHyhB9C;AGxhBmC;EAAW,kBAAA;EH2hB9C;AG1hBmC;EAAW,kBAAA;EH6hB9C;AG5hBmC;EAAW,kBAAA;EH+hB9C;AG9hBmC;EAAW,kBAAA;EHiiB9C;AGhiBmC;EAAW,kBAAA;EHmiB9C;AGliBmC;EAAW,kBAAA;EHqiB9C;AGpiBmC;EAAW,kBAAA;EHuiB9C;AGtiBmC;EAAW,kBAAA;EHyiB9C;AGxiBmC;EAAW,kBAAA;EH2iB9C;AG1iBmC;EAAW,kBAAA;EH6iB9C;AG5iBmC;EAAW,kBAAA;EH+iB9C;AG9iBmC;EAAW,kBAAA;EHijB9C;AGhjBmC;EAAW,kBAAA;EHmjB9C;AGljBmC;EAAW,kBAAA;EHqjB9C;AGpjBmC;EAAW,kBAAA;EHujB9C;AGtjBmC;EAAW,kBAAA;EHyjB9C;AGxjBmC;EAAW,kBAAA;EH2jB9C;AG1jBmC;EAAW,kBAAA;EH6jB9C;AG5jBmC;EAAW,kBAAA;EH+jB9C;AG9jBmC;EAAW,kBAAA;EHikB9C;AGhkBmC;EAAW,kBAAA;EHmkB9C;AGlkBmC;EAAW,kBAAA;EHqkB9C;AGpkBmC;EAAW,kBAAA;EHukB9C;AGtkBmC;EAAW,kBAAA;EHykB9C;AGxkBmC;EAAW,kBAAA;EH2kB9C;AG1kBmC;EAAW,kBAAA;EH6kB9C;AG5kBmC;EAAW,kBAAA;EH+kB9C;AG9kBmC;EAAW,kBAAA;EHilB9C;AGhlBmC;EAAW,kBAAA;EHmlB9C;AGllBmC;EAAW,kBAAA;EHqlB9C;AGplBmC;EAAW,kBAAA;EHulB9C;AGtlBmC;EAAW,kBAAA;EHylB9C;AGxlBmC;EAAW,kBAAA;EH2lB9C;AG1lBmC;EAAW,kBAAA;EH6lB9C;AG5lBmC;EAAW,kBAAA;EH+lB9C;AG9lBmC;EAAW,kBAAA;EHimB9C;AGhmBmC;EAAW,kBAAA;EHmmB9C;AGlmBmC;EAAW,kBAAA;EHqmB9C;AGpmBmC;EAAW,kBAAA;EHumB9C;AGtmBmC;EAAW,kBAAA;EHymB9C;AGxmBmC;EAAW,kBAAA;EH2mB9C;AG1mBmC;EAAW,kBAAA;EH6mB9C;AG5mBmC;EAAW,kBAAA;EH+mB9C;AG9mBmC;EAAW,kBAAA;EHinB9C;AGhnBmC;EAAW,kBAAA;EHmnB9C;AGlnBmC;EAAW,kBAAA;EHqnB9C;AGpnBmC;EAAW,kBAAA;EHunB9C;AGtnBmC;EAAW,kBAAA;EHynB9C;AGxnBmC;EAAW,kBAAA;EH2nB9C;AG1nBmC;EAAW,kBAAA;EH6nB9C;AG5nBmC;EAAW,kBAAA;EH+nB9C;AG9nBmC;EAAW,kBAAA;EHioB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGxoBmC;EAAW,kBAAA;EH2oB9C;AG1oBmC;EAAW,kBAAA;EH6oB9C;AG5oBmC;EAAW,kBAAA;EH+oB9C;AG9oBmC;EAAW,kBAAA;EHipB9C;AGhpBmC;EAAW,kBAAA;EHmpB9C;AGlpBmC;EAAW,kBAAA;EHqpB9C;AGppBmC;EAAW,kBAAA;EHupB9C;AGtpBmC;EAAW,kBAAA;EHypB9C;AGxpBmC;EAAW,kBAAA;EH2pB9C;AG1pBmC;EAAW,kBAAA;EH6pB9C;AG5pBmC;EAAW,kBAAA;EH+pB9C;AG9pBmC;EAAW,kBAAA;EHiqB9C;AGhqBmC;EAAW,kBAAA;EHmqB9C;AGlqBmC;EAAW,kBAAA;EHqqB9C;AGpqBmC;EAAW,kBAAA;EHuqB9C;AGtqBmC;EAAW,kBAAA;EHyqB9C;AGxqBmC;EAAW,kBAAA;EH2qB9C;AG1qBmC;EAAW,kBAAA;EH6qB9C;AG5qBmC;EAAW,kBAAA;EH+qB9C;AG9qBmC;EAAW,kBAAA;EHirB9C;AGhrBmC;EAAW,kBAAA;EHmrB9C;AGlrBmC;EAAW,kBAAA;EHqrB9C;AGprBmC;EAAW,kBAAA;EHurB9C;AGtrBmC;EAAW,kBAAA;EHyrB9C;AGxrBmC;EAAW,kBAAA;EH2rB9C;AG1rBmC;EAAW,kBAAA;EH6rB9C;AG5rBmC;EAAW,kBAAA;EH+rB9C;AG9rBmC;EAAW,kBAAA;EHisB9C;AGhsBmC;EAAW,kBAAA;EHmsB9C;AGlsBmC;EAAW,kBAAA;EHqsB9C;AGpsBmC;EAAW,kBAAA;EHusB9C;AGtsBmC;EAAW,kBAAA;EHysB9C;AGxsBmC;EAAW,kBAAA;EH2sB9C;AG1sBmC;EAAW,kBAAA;EH6sB9C;AG5sBmC;EAAW,kBAAA;EH+sB9C;AG9sBmC;EAAW,kBAAA;EHitB9C;AGhtBmC;EAAW,kBAAA;EHmtB9C;AGltBmC;EAAW,kBAAA;EHqtB9C;AGptBmC;EAAW,kBAAA;EHutB9C;AGttBmC;EAAW,kBAAA;EHytB9C;AGxtBmC;EAAW,kBAAA;EH2tB9C;AG1tBmC;EAAW,kBAAA;EH6tB9C;AG5tBmC;EAAW,kBAAA;EH+tB9C;AG9tBmC;EAAW,kBAAA;EHiuB9C;AGhuBmC;EAAW,kBAAA;EHmuB9C;AGluBmC;EAAW,kBAAA;EHquB9C;AGpuBmC;EAAW,kBAAA;EHuuB9C;AGtuBmC;EAAW,kBAAA;EHyuB9C;AI3gCD;ECgEE,gCAAA;EACG,6BAAA;EACK,wBAAA;EL88BT;AI7gCD;;EC6DE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELo9BT;AI3gCD;EACE,iBAAA;EACA,+CAAA;EJ6gCD;AI1gCD;EACE,6DAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EJ4gCD;AIxgCD;;;;EAIE,sBAAA;EACA,oBAAA;EACA,sBAAA;EJ0gCD;AIpgCD;EACE,gBAAA;EACA,uBAAA;EJsgCD;AIpgCC;;EAEE,gBAAA;EACA,4BAAA;EJsgCH;AIngCC;EErDA,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN0jCD;AI7/BD;EACE,WAAA;EJ+/BD;AIz/BD;EACE,wBAAA;EJ2/BD;AIv/BD;;;;;EGvEE,gBAAA;EACA,iBAAA;EACA,cAAA;EPqkCD;AI3/BD;EACE,oBAAA;EJ6/BD;AIv/BD;EACE,cAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EC6FA,0CAAA;EACK,qCAAA;EACG,kCAAA;EEvLR,uBAAA;EACA,iBAAA;EACA,cAAA;EPqlCD;AIv/BD;EACE,oBAAA;EJy/BD;AIn/BD;EACE,kBAAA;EACA,qBAAA;EACA,WAAA;EACA,+BAAA;EJq/BD;AI7+BD;EACE,oBAAA;EACA,YAAA;EACA,aAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,WAAA;EJ++BD;AIv+BC;;EAEE,kBAAA;EACA,aAAA;EACA,cAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;EJy+BH;AQpnCD;;;;;;;;;;;;EAEE,sBAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;ERgoCD;AQroCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,qBAAA;EACA,gBAAA;EACA,gBAAA;ERspCH;AQlpCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERupCD;AQ3pCD;;;;;;;;;;;;EAQI,gBAAA;ERiqCH;AQ9pCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERmqCD;AQvqCD;;;;;;;;;;;;EAQI,gBAAA;ER6qCH;AQzqCD;;EAAU,iBAAA;ER6qCT;AQ5qCD;;EAAU,iBAAA;ERgrCT;AQ/qCD;;EAAU,iBAAA;ERmrCT;AQlrCD;;EAAU,iBAAA;ERsrCT;AQrrCD;;EAAU,iBAAA;ERyrCT;AQxrCD;;EAAU,iBAAA;ER4rCT;AQtrCD;EACE,kBAAA;ERwrCD;AQrrCD;EACE,qBAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;ERurCD;AQlrCD;EAAA;IAFI,iBAAA;IRwrCD;EACF;AQhrCD;;EAEE,gBAAA;ERkrCD;AQ/qCD;;EAEE,2BAAA;EACA,eAAA;ERirCD;AQ7qCD;EAAuB,kBAAA;ERgrCtB;AQ/qCD;EAAuB,mBAAA;ERkrCtB;AQjrCD;EAAuB,oBAAA;ERorCtB;AQnrCD;EAAuB,qBAAA;ERsrCtB;AQrrCD;EAAuB,qBAAA;ERwrCtB;AQrrCD;EAAuB,2BAAA;ERwrCtB;AQvrCD;EAAuB,2BAAA;ER0rCtB;AQzrCD;EAAuB,4BAAA;ER4rCtB;AQzrCD;EACE,gBAAA;ER2rCD;AQzrCD;ECrGE,gBAAA;ETiyCD;AShyCC;EACE,gBAAA;ETkyCH;AQ5rCD;ECxGE,gBAAA;ETuyCD;AStyCC;EACE,gBAAA;ETwyCH;AQ/rCD;EC3GE,gBAAA;ET6yCD;AS5yCC;EACE,gBAAA;ET8yCH;AQlsCD;EC9GE,gBAAA;ETmzCD;ASlzCC;EACE,gBAAA;ETozCH;AQrsCD;ECjHE,gBAAA;ETyzCD;ASxzCC;EACE,gBAAA;ET0zCH;AQpsCD;EAGE,aAAA;EE3HA,2BAAA;EVg0CD;AU/zCC;EACE,2BAAA;EVi0CH;AQrsCD;EE9HE,2BAAA;EVs0CD;AUr0CC;EACE,2BAAA;EVu0CH;AQxsCD;EEjIE,2BAAA;EV40CD;AU30CC;EACE,2BAAA;EV60CH;AQ3sCD;EEpIE,2BAAA;EVk1CD;AUj1CC;EACE,2BAAA;EVm1CH;AQ9sCD;EEvIE,2BAAA;EVw1CD;AUv1CC;EACE,2BAAA;EVy1CH;AQ5sCD;EACE,qBAAA;EACA,qBAAA;EACA,kCAAA;ER8sCD;AQtsCD;;EAEE,eAAA;EACA,qBAAA;ERwsCD;AQ3sCD;;;;EAMI,kBAAA;ER2sCH;AQpsCD;EACE,iBAAA;EACA,kBAAA;ERssCD;AQlsCD;EALE,iBAAA;EACA,kBAAA;EAMA,mBAAA;ERqsCD;AQvsCD;EAKI,uBAAA;EACA,mBAAA;EACA,oBAAA;ERqsCH;AQhsCD;EACE,eAAA;EACA,qBAAA;ERksCD;AQhsCD;;EAEE,yBAAA;ERksCD;AQhsCD;EACE,mBAAA;ERksCD;AQhsCD;EACE,gBAAA;ERksCD;AQzqCD;EAAA;IAVM,aAAA;IACA,cAAA;IACA,aAAA;IACA,mBAAA;IGtNJ,kBAAA;IACA,yBAAA;IACA,qBAAA;IX84CC;EQnrCH;IAHM,oBAAA;IRyrCH;EACF;AQhrCD;;EAGE,cAAA;EACA,mCAAA;ERirCD;AQ/qCD;EACE,gBAAA;EACA,2BAAA;ERirCD;AQ7qCD;EACE,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,gCAAA;ER+qCD;AQ1qCG;;;EACE,kBAAA;ER8qCL;AQxrCD;;;EAmBI,gBAAA;EACA,gBAAA;EACA,yBAAA;EACA,gBAAA;ER0qCH;AQxqCG;;;EACE,wBAAA;ER4qCL;AQpqCD;;EAEE,qBAAA;EACA,iBAAA;EACA,iCAAA;EACA,gBAAA;EACA,mBAAA;ERsqCD;AQhqCG;;;;;;EAAW,aAAA;ERwqCd;AQvqCG;;;;;;EACE,wBAAA;ER8qCL;AQxqCD;EACE,qBAAA;EACA,oBAAA;EACA,yBAAA;ER0qCD;AYh9CD;;;;EAIE,gEAAA;EZk9CD;AY98CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EZg9CD;AY58CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EACA,wDAAA;UAAA,gDAAA;EZ88CD;AYp9CD;EASI,YAAA;EACA,iBAAA;EACA,mBAAA;EACA,0BAAA;UAAA,kBAAA;EZ88CH;AYz8CD;EACE,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,uBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EZ28CD;AYt9CD;EAeI,YAAA;EACA,oBAAA;EACA,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,kBAAA;EZ08CH;AYr8CD;EACE,mBAAA;EACA,oBAAA;EZu8CD;AajgDD;ECHE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;EdugDD;AajgDC;EAAA;IAFE,cAAA;IbugDD;EACF;AangDC;EAAA;IAFE,cAAA;IbygDD;EACF;AargDD;EAAA;IAFI,eAAA;Ib2gDD;EACF;AalgDD;ECvBE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;Ed4hDD;Aa//CD;ECvBE,oBAAA;EACA,qBAAA;EdyhDD;AezhDG;EACE,oBAAA;EAEA,iBAAA;EAEA,oBAAA;EACA,qBAAA;EfyhDL;AezgDG;EACE,aAAA;Ef2gDL;AepgDC;EACE,aAAA;EfsgDH;AevgDC;EACE,qBAAA;EfygDH;Ae1gDC;EACE,qBAAA;Ef4gDH;Ae7gDC;EACE,YAAA;Ef+gDH;AehhDC;EACE,qBAAA;EfkhDH;AenhDC;EACE,qBAAA;EfqhDH;AethDC;EACE,YAAA;EfwhDH;AezhDC;EACE,qBAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,YAAA;EfiiDH;AeliDC;EACE,qBAAA;EfoiDH;AeriDC;EACE,oBAAA;EfuiDH;AezhDC;EACE,aAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,qBAAA;EfiiDH;AeliDC;EACE,YAAA;EfoiDH;AeriDC;EACE,qBAAA;EfuiDH;AexiDC;EACE,qBAAA;Ef0iDH;Ae3iDC;EACE,YAAA;Ef6iDH;Ae9iDC;EACE,qBAAA;EfgjDH;AejjDC;EACE,qBAAA;EfmjDH;AepjDC;EACE,YAAA;EfsjDH;AevjDC;EACE,qBAAA;EfyjDH;Ae1jDC;EACE,oBAAA;Ef4jDH;AexjDC;EACE,aAAA;Ef0jDH;Ae1kDC;EACE,YAAA;Ef4kDH;Ae7kDC;EACE,oBAAA;Ef+kDH;AehlDC;EACE,oBAAA;EfklDH;AenlDC;EACE,WAAA;EfqlDH;AetlDC;EACE,oBAAA;EfwlDH;AezlDC;EACE,oBAAA;Ef2lDH;Ae5lDC;EACE,WAAA;Ef8lDH;Ae/lDC;EACE,oBAAA;EfimDH;AelmDC;EACE,oBAAA;EfomDH;AermDC;EACE,WAAA;EfumDH;AexmDC;EACE,oBAAA;Ef0mDH;Ae3mDC;EACE,mBAAA;Ef6mDH;AezmDC;EACE,YAAA;Ef2mDH;Ae7lDC;EACE,mBAAA;Ef+lDH;AehmDC;EACE,2BAAA;EfkmDH;AenmDC;EACE,2BAAA;EfqmDH;AetmDC;EACE,kBAAA;EfwmDH;AezmDC;EACE,2BAAA;Ef2mDH;Ae5mDC;EACE,2BAAA;Ef8mDH;Ae/mDC;EACE,kBAAA;EfinDH;AelnDC;EACE,2BAAA;EfonDH;AernDC;EACE,2BAAA;EfunDH;AexnDC;EACE,kBAAA;Ef0nDH;Ae3nDC;EACE,2BAAA;Ef6nDH;Ae9nDC;EACE,0BAAA;EfgoDH;AejoDC;EACE,iBAAA;EfmoDH;AanoDD;EElCI;IACE,aAAA;IfwqDH;EejqDD;IACE,aAAA;IfmqDD;EepqDD;IACE,qBAAA;IfsqDD;EevqDD;IACE,qBAAA;IfyqDD;Ee1qDD;IACE,YAAA;If4qDD;Ee7qDD;IACE,qBAAA;If+qDD;EehrDD;IACE,qBAAA;IfkrDD;EenrDD;IACE,YAAA;IfqrDD;EetrDD;IACE,qBAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,YAAA;If8rDD;Ee/rDD;IACE,qBAAA;IfisDD;EelsDD;IACE,oBAAA;IfosDD;EetrDD;IACE,aAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,qBAAA;If8rDD;Ee/rDD;IACE,YAAA;IfisDD;EelsDD;IACE,qBAAA;IfosDD;EersDD;IACE,qBAAA;IfusDD;EexsDD;IACE,YAAA;If0sDD;Ee3sDD;IACE,qBAAA;If6sDD;Ee9sDD;IACE,qBAAA;IfgtDD;EejtDD;IACE,YAAA;IfmtDD;EeptDD;IACE,qBAAA;IfstDD;EevtDD;IACE,oBAAA;IfytDD;EertDD;IACE,aAAA;IfutDD;EevuDD;IACE,YAAA;IfyuDD;Ee1uDD;IACE,oBAAA;If4uDD;Ee7uDD;IACE,oBAAA;If+uDD;EehvDD;IACE,WAAA;IfkvDD;EenvDD;IACE,oBAAA;IfqvDD;EetvDD;IACE,oBAAA;IfwvDD;EezvDD;IACE,WAAA;If2vDD;Ee5vDD;IACE,oBAAA;If8vDD;Ee/vDD;IACE,oBAAA;IfiwDD;EelwDD;IACE,WAAA;IfowDD;EerwDD;IACE,oBAAA;IfuwDD;EexwDD;IACE,mBAAA;If0wDD;EetwDD;IACE,YAAA;IfwwDD;Ee1vDD;IACE,mBAAA;If4vDD;Ee7vDD;IACE,2BAAA;If+vDD;EehwDD;IACE,2BAAA;IfkwDD;EenwDD;IACE,kBAAA;IfqwDD;EetwDD;IACE,2BAAA;IfwwDD;EezwDD;IACE,2BAAA;If2wDD;Ee5wDD;IACE,kBAAA;If8wDD;Ee/wDD;IACE,2BAAA;IfixDD;EelxDD;IACE,2BAAA;IfoxDD;EerxDD;IACE,kBAAA;IfuxDD;EexxDD;IACE,2BAAA;If0xDD;Ee3xDD;IACE,0BAAA;If6xDD;Ee9xDD;IACE,iBAAA;IfgyDD;EACF;AaxxDD;EE3CI;IACE,aAAA;Ifs0DH;Ee/zDD;IACE,aAAA;Ifi0DD;Eel0DD;IACE,qBAAA;Ifo0DD;Eer0DD;IACE,qBAAA;Ifu0DD;Eex0DD;IACE,YAAA;If00DD;Ee30DD;IACE,qBAAA;If60DD;Ee90DD;IACE,qBAAA;Ifg1DD;Eej1DD;IACE,YAAA;Ifm1DD;Eep1DD;IACE,qBAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,YAAA;If41DD;Ee71DD;IACE,qBAAA;If+1DD;Eeh2DD;IACE,oBAAA;Ifk2DD;Eep1DD;IACE,aAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,qBAAA;If41DD;Ee71DD;IACE,YAAA;If+1DD;Eeh2DD;IACE,qBAAA;Ifk2DD;Een2DD;IACE,qBAAA;Ifq2DD;Eet2DD;IACE,YAAA;Ifw2DD;Eez2DD;IACE,qBAAA;If22DD;Ee52DD;IACE,qBAAA;If82DD;Ee/2DD;IACE,YAAA;Ifi3DD;Eel3DD;IACE,qBAAA;Ifo3DD;Eer3DD;IACE,oBAAA;Ifu3DD;Een3DD;IACE,aAAA;Ifq3DD;Eer4DD;IACE,YAAA;Ifu4DD;Eex4DD;IACE,oBAAA;If04DD;Ee34DD;IACE,oBAAA;If64DD;Ee94DD;IACE,WAAA;Ifg5DD;Eej5DD;IACE,oBAAA;Ifm5DD;Eep5DD;IACE,oBAAA;Ifs5DD;Eev5DD;IACE,WAAA;Ify5DD;Ee15DD;IACE,oBAAA;If45DD;Ee75DD;IACE,oBAAA;If+5DD;Eeh6DD;IACE,WAAA;Ifk6DD;Een6DD;IACE,oBAAA;Ifq6DD;Eet6DD;IACE,mBAAA;Ifw6DD;Eep6DD;IACE,YAAA;Ifs6DD;Eex5DD;IACE,mBAAA;If05DD;Ee35DD;IACE,2BAAA;If65DD;Ee95DD;IACE,2BAAA;Ifg6DD;Eej6DD;IACE,kBAAA;Ifm6DD;Eep6DD;IACE,2BAAA;Ifs6DD;Eev6DD;IACE,2BAAA;Ify6DD;Ee16DD;IACE,kBAAA;If46DD;Ee76DD;IACE,2BAAA;If+6DD;Eeh7DD;IACE,2BAAA;Ifk7DD;Een7DD;IACE,kBAAA;Ifq7DD;Eet7DD;IACE,2BAAA;Ifw7DD;Eez7DD;IACE,0BAAA;If27DD;Ee57DD;IACE,iBAAA;If87DD;EACF;Aan7DD;EE9CI;IACE,aAAA;Ifo+DH;Ee79DD;IACE,aAAA;If+9DD;Eeh+DD;IACE,qBAAA;Ifk+DD;Een+DD;IACE,qBAAA;Ifq+DD;Eet+DD;IACE,YAAA;Ifw+DD;Eez+DD;IACE,qBAAA;If2+DD;Ee5+DD;IACE,qBAAA;If8+DD;Ee/+DD;IACE,YAAA;Ifi/DD;Eel/DD;IACE,qBAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,YAAA;If0/DD;Ee3/DD;IACE,qBAAA;If6/DD;Ee9/DD;IACE,oBAAA;IfggED;Eel/DD;IACE,aAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,qBAAA;If0/DD;Ee3/DD;IACE,YAAA;If6/DD;Ee9/DD;IACE,qBAAA;IfggED;EejgED;IACE,qBAAA;IfmgED;EepgED;IACE,YAAA;IfsgED;EevgED;IACE,qBAAA;IfygED;Ee1gED;IACE,qBAAA;If4gED;Ee7gED;IACE,YAAA;If+gED;EehhED;IACE,qBAAA;IfkhED;EenhED;IACE,oBAAA;IfqhED;EejhED;IACE,aAAA;IfmhED;EeniED;IACE,YAAA;IfqiED;EetiED;IACE,oBAAA;IfwiED;EeziED;IACE,oBAAA;If2iED;Ee5iED;IACE,WAAA;If8iED;Ee/iED;IACE,oBAAA;IfijED;EeljED;IACE,oBAAA;IfojED;EerjED;IACE,WAAA;IfujED;EexjED;IACE,oBAAA;If0jED;Ee3jED;IACE,oBAAA;If6jED;Ee9jED;IACE,WAAA;IfgkED;EejkED;IACE,oBAAA;IfmkED;EepkED;IACE,mBAAA;IfskED;EelkED;IACE,YAAA;IfokED;EetjED;IACE,mBAAA;IfwjED;EezjED;IACE,2BAAA;If2jED;Ee5jED;IACE,2BAAA;If8jED;Ee/jED;IACE,kBAAA;IfikED;EelkED;IACE,2BAAA;IfokED;EerkED;IACE,2BAAA;IfukED;EexkED;IACE,kBAAA;If0kED;Ee3kED;IACE,2BAAA;If6kED;Ee9kED;IACE,2BAAA;IfglED;EejlED;IACE,kBAAA;IfmlED;EeplED;IACE,2BAAA;IfslED;EevlED;IACE,0BAAA;IfylED;Ee1lED;IACE,iBAAA;If4lED;EACF;AgBhqED;EACE,+BAAA;EhBkqED;AgBhqED;EACE,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,kBAAA;EhBkqED;AgBhqED;EACE,kBAAA;EhBkqED;AgB5pED;EACE,aAAA;EACA,iBAAA;EACA,qBAAA;EhB8pED;AgBjqED;;;;;;EAWQ,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,+BAAA;EhB8pEP;AgB5qED;EAoBI,wBAAA;EACA,kCAAA;EhB2pEH;AgBhrED;;;;;;EA8BQ,eAAA;EhB0pEP;AgBxrED;EAoCI,+BAAA;EhBupEH;AgB3rED;EAyCI,2BAAA;EhBqpEH;AgB9oED;;;;;;EAOQ,cAAA;EhB+oEP;AgBpoED;EACE,2BAAA;EhBsoED;AgBvoED;;;;;;EAQQ,2BAAA;EhBuoEP;AgB/oED;;EAeM,0BAAA;EhBooEL;AgB1nED;EAEI,2BAAA;EhB2nEH;AgBlnED;EAEI,2BAAA;EhBmnEH;AgB1mED;EACE,kBAAA;EACA,aAAA;EACA,uBAAA;EhB4mED;AgBvmEG;;EACE,kBAAA;EACA,aAAA;EACA,qBAAA;EhB0mEL;AiBtvEC;;;;;;;;;;;;EAOI,2BAAA;EjB6vEL;AiBvvEC;;;;;EAMI,2BAAA;EjBwvEL;AiB3wEC;;;;;;;;;;;;EAOI,2BAAA;EjBkxEL;AiB5wEC;;;;;EAMI,2BAAA;EjB6wEL;AiBhyEC;;;;;;;;;;;;EAOI,2BAAA;EjBuyEL;AiBjyEC;;;;;EAMI,2BAAA;EjBkyEL;AiBrzEC;;;;;;;;;;;;EAOI,2BAAA;EjB4zEL;AiBtzEC;;;;;EAMI,2BAAA;EjBuzEL;AiB10EC;;;;;;;;;;;;EAOI,2BAAA;EjBi1EL;AiB30EC;;;;;EAMI,2BAAA;EjB40EL;AgB1rED;EACE,kBAAA;EACA,mBAAA;EhB4rED;AgB/nED;EAAA;IA1DI,aAAA;IACA,qBAAA;IACA,oBAAA;IACA,8CAAA;IACA,2BAAA;IhB6rED;EgBvoEH;IAlDM,kBAAA;IhB4rEH;EgB1oEH;;;;;;IAzCY,qBAAA;IhB2rET;EgBlpEH;IAjCM,WAAA;IhBsrEH;EgBrpEH;;;;;;IAxBY,gBAAA;IhBqrET;EgB7pEH;;;;;;IApBY,iBAAA;IhByrET;EgBrqEH;;;;IAPY,kBAAA;IhBkrET;EACF;AkB54ED;EACE,YAAA;EACA,WAAA;EACA,WAAA;EAIA,cAAA;ElB24ED;AkBx4ED;EACE,gBAAA;EACA,aAAA;EACA,YAAA;EACA,qBAAA;EACA,iBAAA;EACA,sBAAA;EACA,gBAAA;EACA,WAAA;EACA,kCAAA;ElB04ED;AkBv4ED;EACE,uBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;ElBy4ED;AkB93ED;Eb4BE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELq2ET;AkB93ED;;EAEE,iBAAA;EACA,oBAAA;EACA,qBAAA;ElBg4ED;AkB53ED;EACE,gBAAA;ElB83ED;AkB13ED;EACE,gBAAA;EACA,aAAA;ElB43ED;AkBx3ED;;EAEE,cAAA;ElB03ED;AkBt3ED;;;EZxEE,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENk8ED;AkBt3ED;EACE,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;ElBw3ED;AkB91ED;EACE,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EACA,wBAAA;EACA,2BAAA;EACA,oBAAA;EbzDA,0DAAA;EACQ,kDAAA;EAyHR,wFAAA;EACK,2EAAA;EACG,wEAAA;ELkyET;AmB16EC;EACE,uBAAA;EACA,YAAA;EdUF,wFAAA;EACQ,gFAAA;ELm6ET;AKl4EC;EACE,gBAAA;EACA,YAAA;ELo4EH;AKl4EC;EAA0B,gBAAA;ELq4E3B;AKp4EC;EAAgC,gBAAA;ELu4EjC;AkBt2EC;;;EAGE,qBAAA;EACA,2BAAA;EACA,YAAA;ElBw2EH;AkBp2EC;EACE,cAAA;ElBs2EH;AkB11ED;EACE,0BAAA;ElB41ED;AkBxzED;EAxBE;;;;IAIE,mBAAA;IlBm1ED;EkBj1EC;;;;;;;;IAEE,mBAAA;IlBy1EH;EkBt1EC;;;;;;;;IAEE,mBAAA;IlB81EH;EACF;AkBp1ED;EACE,qBAAA;ElBs1ED;AkB90ED;;EAEE,oBAAA;EACA,gBAAA;EACA,kBAAA;EACA,qBAAA;ElBg1ED;AkBr1ED;;EAQI,kBAAA;EACA,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,iBAAA;ElBi1EH;AkB90ED;;;;EAIE,oBAAA;EACA,oBAAA;EACA,oBAAA;ElBg1ED;AkB70ED;;EAEE,kBAAA;ElB+0ED;AkB30ED;;EAEE,uBAAA;EACA,oBAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,iBAAA;ElB60ED;AkB30ED;;EAEE,eAAA;EACA,mBAAA;ElB60ED;AkBp0EC;;;;;;EAGE,qBAAA;ElBy0EH;AkBn0EC;;;;EAEE,qBAAA;ElBu0EH;AkBj0EC;;;;EAGI,qBAAA;ElBo0EL;AkBzzED;EAEE,kBAAA;EACA,qBAAA;EAEA,kBAAA;ElByzED;AkBvzEC;;EAEE,iBAAA;EACA,kBAAA;ElByzEH;AkB5yED;ECpPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBmiFD;AmBjiFC;EACE,cAAA;EACA,mBAAA;EnBmiFH;AmBhiFC;;EAEE,cAAA;EnBkiFH;AkBxzED;ECvPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBkjFD;AmBhjFC;EACE,cAAA;EACA,mBAAA;EnBkjFH;AmB/iFC;;EAEE,cAAA;EnBijFH;AkBv0ED;EAKI,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;ElBq0EH;AkBj0ED;ECnQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBukFD;AmBrkFC;EACE,cAAA;EACA,mBAAA;EnBukFH;AmBpkFC;;EAEE,cAAA;EnBskFH;AkB70ED;ECtQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBslFD;AmBplFC;EACE,cAAA;EACA,mBAAA;EnBslFH;AmBnlFC;;EAEE,cAAA;EnBqlFH;AkB51ED;EAKI,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;ElB01EH;AkBj1ED;EAEE,oBAAA;ElBk1ED;AkBp1ED;EAMI,uBAAA;ElBi1EH;AkB70ED;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,oBAAA;EACA,sBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB30ED;;;;;;;;;;EC7WI,gBAAA;EnBosFH;AkBv1ED;ECzWI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELqpFT;AmBnsFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;EL0pFT;AkBj2ED;EC/VI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBmsFH;AkBt2ED;ECzVI,gBAAA;EnBksFH;AkBt2ED;;;;;;;;;;EChXI,gBAAA;EnBkuFH;AkBl3ED;EC5WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELmrFT;AmBjuFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELwrFT;AkB53ED;EClWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBiuFH;AkBj4ED;EC5VI,gBAAA;EnBguFH;AkBj4ED;;;;;;;;;;ECnXI,gBAAA;EnBgwFH;AkB74ED;EC/WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELitFT;AmB/vFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELstFT;AkBv5ED;ECrWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnB+vFH;AkB55ED;EC/VI,gBAAA;EnB8vFH;AkBx5EC;EACG,WAAA;ElB05EJ;AkBx5EC;EACG,QAAA;ElB05EJ;AkBh5ED;EACE,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;ElBk5ED;AkB/zED;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBi4EH;EkBr0EH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB+3EH;EkB10EH;IAhDM,uBAAA;IlB63EH;EkB70EH;IA5CM,uBAAA;IACA,wBAAA;IlB43EH;EkBj1EH;;;IAtCQ,aAAA;IlB43EL;EkBt1EH;IAhCM,aAAA;IlBy3EH;EkBz1EH;IA5BM,kBAAA;IACA,wBAAA;IlBw3EH;EkB71EH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBq3EH;EkBp2EH;;IAdQ,iBAAA;IlBs3EL;EkBx2EH;;IATM,oBAAA;IACA,gBAAA;IlBq3EH;EkB72EH;IAHM,QAAA;IlBm3EH;EACF;AkBz2ED;;;;EASI,eAAA;EACA,kBAAA;EACA,kBAAA;ElBs2EH;AkBj3ED;;EAiBI,kBAAA;ElBo2EH;AkBr3ED;EJzeE,oBAAA;EACA,qBAAA;Edi2FD;AkBl1EC;EAAA;IAVI,mBAAA;IACA,kBAAA;IACA,kBAAA;IlBg2EH;EACF;AkBh4ED;EAwCI,aAAA;ElB21EH;AkB90EC;EAAA;IAHM,0BAAA;IlBq1EL;EACF;AkB50EC;EAAA;IAHM,kBAAA;IlBm1EL;EACF;AoB73FD;EACE,uBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,wBAAA;EACA,gCAAA;MAAA,4BAAA;EACA,iBAAA;EACA,wBAAA;EACA,+BAAA;EACA,qBAAA;EC6BA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,oBAAA;EhB4KA,2BAAA;EACG,wBAAA;EACC,uBAAA;EACI,mBAAA;ELwrFT;AoBh4FG;;;;;;EdrBF,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN45FD;AoBp4FC;;;EAGE,gBAAA;EACA,uBAAA;EpBs4FH;AoBn4FC;;EAEE,YAAA;EACA,wBAAA;Ef2BF,0DAAA;EACQ,kDAAA;EL22FT;AoBn4FC;;;EAGE,qBAAA;EACA,sBAAA;EE9CF,eAAA;EAGA,2BAAA;EjB8DA,0BAAA;EACQ,kBAAA;ELq3FT;AoB/3FD;ECrDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBu7FD;AqBr7FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBu7FP;AqBr7FC;;;EAGE,wBAAA;ErBu7FH;AqBl7FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBg8FT;AoBx6FD;ECnBI,gBAAA;EACA,2BAAA;ErB87FH;AoBz6FD;ECxDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBo+FD;AqBl+FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBo+FP;AqBl+FC;;;EAGE,wBAAA;ErBo+FH;AqB/9FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB6+FT;AoBl9FD;ECtBI,gBAAA;EACA,2BAAA;ErB2+FH;AoBl9FD;EC5DE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBihGD;AqB/gGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBihGP;AqB/gGC;;;EAGE,wBAAA;ErBihGH;AqB5gGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB0hGT;AoB3/FD;EC1BI,gBAAA;EACA,2BAAA;ErBwhGH;AoB3/FD;EChEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB8jGD;AqB5jGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB8jGP;AqB5jGC;;;EAGE,wBAAA;ErB8jGH;AqBzjGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBukGT;AoBpiGD;EC9BI,gBAAA;EACA,2BAAA;ErBqkGH;AoBpiGD;ECpEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB2mGD;AqBzmGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB2mGP;AqBzmGC;;;EAGE,wBAAA;ErB2mGH;AqBtmGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBonGT;AoB7kGD;EClCI,gBAAA;EACA,2BAAA;ErBknGH;AoB7kGD;ECxEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBwpGD;AqBtpGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBwpGP;AqBtpGC;;;EAGE,wBAAA;ErBwpGH;AqBnpGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBiqGT;AoBtnGD;ECtCI,gBAAA;EACA,2BAAA;ErB+pGH;AoBjnGD;EACE,gBAAA;EACA,qBAAA;EACA,kBAAA;EpBmnGD;AoBjnGC;;;;;EAKE,+BAAA;Ef7BF,0BAAA;EACQ,kBAAA;ELipGT;AoBlnGC;;;;EAIE,2BAAA;EpBonGH;AoBlnGC;;EAEE,gBAAA;EACA,4BAAA;EACA,+BAAA;EpBonGH;AoBhnGG;;;;EAEE,gBAAA;EACA,uBAAA;EpBonGL;AoB3mGD;;EC/EE,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;ErB8rGD;AoB9mGD;;ECnFE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErBqsGD;AoBjnGD;;ECvFE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErB4sGD;AoBhnGD;EACE,gBAAA;EACA,aAAA;EpBknGD;AoB9mGD;EACE,iBAAA;EpBgnGD;AoBzmGC;;;EACE,aAAA;EpB6mGH;AuBjwGD;EACE,YAAA;ElBoLA,0CAAA;EACK,qCAAA;EACG,kCAAA;ELglGT;AuBpwGC;EACE,YAAA;EvBswGH;AuBlwGD;EACE,eAAA;EACA,oBAAA;EvBowGD;AuBlwGC;EAAY,gBAAA;EAAgB,qBAAA;EvBswG7B;AuBrwGC;EAAY,oBAAA;EvBwwGb;AuBvwGC;EAAY,0BAAA;EvB0wGb;AuBvwGD;EACE,oBAAA;EACA,WAAA;EACA,kBAAA;ElBsKA,iDAAA;EACQ,4CAAA;KAAA,yCAAA;EAOR,oCAAA;EACQ,+BAAA;KAAA,4BAAA;EAGR,0CAAA;EACQ,qCAAA;KAAA,kCAAA;EL4lGT;AwBtyGD;EACE,uBAAA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;EACA,qCAAA;EACA,oCAAA;ExBwyGD;AwBpyGD;;EAEE,oBAAA;ExBsyGD;AwBlyGD;EACE,YAAA;ExBoyGD;AwBhyGD;EACE,oBAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,2BAAA;EACA,2BAAA;EACA,uCAAA;EACA,oBAAA;EnBuBA,qDAAA;EACQ,6CAAA;EmBtBR,sCAAA;UAAA,8BAAA;ExBmyGD;AwB9xGC;EACE,UAAA;EACA,YAAA;ExBgyGH;AwBzzGD;ECxBE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzBo1GD;AwB/zGD;EAmCI,gBAAA;EACA,mBAAA;EACA,aAAA;EACA,qBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExB+xGH;AwBzxGC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;ExB2xGH;AwBrxGC;;;EAGE,gBAAA;EACA,uBAAA;EACA,YAAA;EACA,2BAAA;ExBuxGH;AwB9wGC;;;EAGE,gBAAA;ExBgxGH;AwB5wGC;;EAEE,uBAAA;EACA,+BAAA;EACA,wBAAA;EE1GF,qEAAA;EF4GE,qBAAA;ExB8wGH;AwBzwGD;EAGI,gBAAA;ExBywGH;AwB5wGD;EAQI,YAAA;ExBuwGH;AwB/vGD;EACE,YAAA;EACA,UAAA;ExBiwGD;AwBzvGD;EACE,SAAA;EACA,aAAA;ExB2vGD;AwBvvGD;EACE,gBAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExByvGD;AwBrvGD;EACE,iBAAA;EACA,SAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA;EACA,cAAA;ExBuvGD;AwBnvGD;EACE,UAAA;EACA,YAAA;ExBqvGD;AwB7uGD;;EAII,eAAA;EACA,0BAAA;EACA,aAAA;ExB6uGH;AwBnvGD;;EAUI,WAAA;EACA,cAAA;EACA,oBAAA;ExB6uGH;AwBxtGD;EAXE;IAnEA,YAAA;IACA,UAAA;IxB0yGC;EwBxuGD;IAzDA,SAAA;IACA,aAAA;IxBoyGC;EACF;A2Bn7GD;;EAEE,oBAAA;EACA,uBAAA;EACA,wBAAA;E3Bq7GD;A2Bz7GD;;EAMI,oBAAA;EACA,aAAA;E3Bu7GH;A2Br7GG;;;;;;;;EAIE,YAAA;E3B27GL;A2Br7GD;;;;EAKI,mBAAA;E3Bs7GH;A2Bj7GD;EACE,mBAAA;E3Bm7GD;A2Bp7GD;;EAMI,aAAA;E3Bk7GH;A2Bx7GD;;;EAWI,kBAAA;E3Bk7GH;A2B96GD;EACE,kBAAA;E3Bg7GD;A2B56GD;EACE,gBAAA;E3B86GD;A2B76GC;ECjDA,+BAAA;EACG,4BAAA;E5Bi+GJ;A2B56GD;;EC9CE,8BAAA;EACG,2BAAA;E5B89GJ;A2B36GD;EACE,aAAA;E3B66GD;A2B36GD;EACE,kBAAA;E3B66GD;A2B36GD;;EClEE,+BAAA;EACG,4BAAA;E5Bi/GJ;A2B16GD;EChEE,8BAAA;EACG,2BAAA;E5B6+GJ;A2Bz6GD;;EAEE,YAAA;E3B26GD;A2B15GD;EACE,mBAAA;EACA,oBAAA;E3B45GD;A2B15GD;EACE,oBAAA;EACA,qBAAA;E3B45GD;A2Bv5GD;EtB9CE,0DAAA;EACQ,kDAAA;ELw8GT;A2Bv5GC;EtBlDA,0BAAA;EACQ,kBAAA;EL48GT;A2Bp5GD;EACE,gBAAA;E3Bs5GD;A2Bn5GD;EACE,yBAAA;EACA,wBAAA;E3Bq5GD;A2Bl5GD;EACE,yBAAA;E3Bo5GD;A2B74GD;;;EAII,gBAAA;EACA,aAAA;EACA,aAAA;EACA,iBAAA;E3B84GH;A2Br5GD;EAcM,aAAA;E3B04GL;A2Bx5GD;;;;EAsBI,kBAAA;EACA,gBAAA;E3Bw4GH;A2Bn4GC;EACE,kBAAA;E3Bq4GH;A2Bn4GC;EACE,8BAAA;ECnKF,+BAAA;EACC,8BAAA;E5ByiHF;A2Bp4GC;EACE,gCAAA;EC/KF,4BAAA;EACC,2BAAA;E5BsjHF;A2Bp4GD;EACE,kBAAA;E3Bs4GD;A2Bp4GD;;EC9KE,+BAAA;EACC,8BAAA;E5BsjHF;A2Bn4GD;EC5LE,4BAAA;EACC,2BAAA;E5BkkHF;A2B/3GD;EACE,gBAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;E3Bi4GD;A2Br4GD;;EAOI,aAAA;EACA,qBAAA;EACA,WAAA;E3Bk4GH;A2B34GD;EAYI,aAAA;E3Bk4GH;A2B94GD;EAgBI,YAAA;E3Bi4GH;A2Bh3GD;;;;EAKM,oBAAA;EACA,wBAAA;EACA,sBAAA;E3Bi3GL;A6B1lHD;EACE,oBAAA;EACA,gBAAA;EACA,2BAAA;E7B4lHD;A6BzlHC;EACE,aAAA;EACA,iBAAA;EACA,kBAAA;E7B2lHH;A6BpmHD;EAeI,oBAAA;EACA,YAAA;EAKA,aAAA;EAEA,aAAA;EACA,kBAAA;E7BmlHH;A6B1kHD;;;EV8BE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBijHD;AmB/iHC;;;EACE,cAAA;EACA,mBAAA;EnBmjHH;AmBhjHC;;;;;;EAEE,cAAA;EnBsjHH;A6B5lHD;;;EVyBE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBwkHD;AmBtkHC;;;EACE,cAAA;EACA,mBAAA;EnB0kHH;AmBvkHC;;;;;;EAEE,cAAA;EnB6kHH;A6B1mHD;;;EAGE,qBAAA;E7B4mHD;A6B1mHC;;;EACE,kBAAA;E7B8mHH;A6B1mHD;;EAEE,WAAA;EACA,qBAAA;EACA,wBAAA;E7B4mHD;A6BvmHD;EACE,mBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;E7BymHD;A6BtmHC;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6BtmHC;EACE,oBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6B5nHD;;EA0BI,eAAA;E7BsmHH;A6BjmHD;;;;;;;EDhGE,+BAAA;EACG,4BAAA;E5B0sHJ;A6BlmHD;EACE,iBAAA;E7BomHD;A6BlmHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;E5B+sHJ;A6BnmHD;EACE,gBAAA;E7BqmHD;A6BhmHD;EACE,oBAAA;EAGA,cAAA;EACA,qBAAA;E7BgmHD;A6BrmHD;EAUI,oBAAA;E7B8lHH;A6BxmHD;EAYM,mBAAA;E7B+lHL;A6B5lHG;;;EAGE,YAAA;E7B8lHL;A6BzlHC;;EAGI,oBAAA;E7B0lHL;A6BvlHC;;EAGI,mBAAA;E7BwlHL;A8BlvHD;EACE,kBAAA;EACA,iBAAA;EACA,kBAAA;E9BovHD;A8BvvHD;EAOI,oBAAA;EACA,gBAAA;E9BmvHH;A8B3vHD;EAWM,oBAAA;EACA,gBAAA;EACA,oBAAA;E9BmvHL;A8BlvHK;;EAEE,uBAAA;EACA,2BAAA;E9BovHP;A8B/uHG;EACE,gBAAA;E9BivHL;A8B/uHK;;EAEE,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,qBAAA;E9BivHP;A8B1uHG;;;EAGE,2BAAA;EACA,uBAAA;E9B4uHL;A8BrxHD;ELHE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzB2xHD;A8B3xHD;EA0DI,iBAAA;E9BouHH;A8B3tHD;EACE,kCAAA;E9B6tHD;A8B9tHD;EAGI,aAAA;EAEA,qBAAA;E9B6tHH;A8BluHD;EASM,mBAAA;EACA,yBAAA;EACA,+BAAA;EACA,4BAAA;E9B4tHL;A8B3tHK;EACE,uCAAA;E9B6tHP;A8BvtHK;;;EAGE,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,kCAAA;EACA,iBAAA;E9BytHP;A8BptHC;EAqDA,aAAA;EA8BA,kBAAA;E9BqoHD;A8BxtHC;EAwDE,aAAA;E9BmqHH;A8B3tHC;EA0DI,oBAAA;EACA,oBAAA;E9BoqHL;A8B/tHC;EAgEE,WAAA;EACA,YAAA;E9BkqHH;A8BtpHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BiqHH;E8B3pHH;IAJQ,kBAAA;I9BkqHL;EACF;A8B5uHC;EAuFE,iBAAA;EACA,oBAAA;E9BwpHH;A8BhvHC;;;EA8FE,2BAAA;E9BupHH;A8BzoHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9BspHH;E8B9oHH;;;IAHM,8BAAA;I9BspHH;EACF;A8BvvHD;EAEI,aAAA;E9BwvHH;A8B1vHD;EAMM,oBAAA;E9BuvHL;A8B7vHD;EASM,kBAAA;E9BuvHL;A8BlvHK;;;EAGE,gBAAA;EACA,2BAAA;E9BovHP;A8B5uHD;EAEI,aAAA;E9B6uHH;A8B/uHD;EAIM,iBAAA;EACA,gBAAA;E9B8uHL;A8BluHD;EACE,aAAA;E9BouHD;A8BruHD;EAII,aAAA;E9BouHH;A8BxuHD;EAMM,oBAAA;EACA,oBAAA;E9BquHL;A8B5uHD;EAYI,WAAA;EACA,YAAA;E9BmuHH;A8BvtHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BkuHH;E8B5tHH;IAJQ,kBAAA;I9BmuHL;EACF;A8B3tHD;EACE,kBAAA;E9B6tHD;A8B9tHD;EAKI,iBAAA;EACA,oBAAA;E9B4tHH;A8BluHD;;;EAYI,2BAAA;E9B2tHH;A8B7sHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9B0tHH;E8BltHH;;;IAHM,8BAAA;I9B0tHH;EACF;A8BjtHD;EAEI,eAAA;EACA,oBAAA;E9BktHH;A8BrtHD;EAMI,gBAAA;EACA,qBAAA;E9BktHH;A8BzsHD;EAEE,kBAAA;EF7OA,4BAAA;EACC,2BAAA;E5Bw7HF;A+Bl7HD;EACE,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,+BAAA;E/Bo7HD;A+B56HD;EAAA;IAFI,oBAAA;I/Bk7HD;EACF;A+Bn6HD;EAAA;IAFI,aAAA;I/By6HD;EACF;A+B35HD;EACE,qBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,4DAAA;UAAA,oDAAA;EAEA,mCAAA;E/B45HD;A+B15HC;EACE,kBAAA;E/B45HH;A+B/3HD;EAAA;IAzBI,aAAA;IACA,eAAA;IACA,0BAAA;YAAA,kBAAA;I/B45HD;E+B15HC;IACE,2BAAA;IACA,gCAAA;IACA,yBAAA;IACA,mBAAA;IACA,8BAAA;I/B45HH;E+Bz5HC;IACE,qBAAA;I/B25HH;E+Bt5HC;;;IAGE,iBAAA;IACA,kBAAA;I/Bw5HH;EACF;A+Bp5HD;;EAGI,mBAAA;E/Bq5HH;A+Bh5HC;EAAA;;IAFI,mBAAA;I/Bu5HH;EACF;A+B94HD;;;;EAII,qBAAA;EACA,oBAAA;E/Bg5HH;A+B14HC;EAAA;;;;IAHI,iBAAA;IACA,gBAAA;I/Bo5HH;EACF;A+Bx4HD;EACE,eAAA;EACA,uBAAA;E/B04HD;A+Br4HD;EAAA;IAFI,kBAAA;I/B24HD;EACF;A+Bv4HD;;EAEE,iBAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;E/By4HD;A+Bn4HD;EAAA;;IAFI,kBAAA;I/B04HD;EACF;A+Bx4HD;EACE,QAAA;EACA,uBAAA;E/B04HD;A+Bx4HD;EACE,WAAA;EACA,kBAAA;EACA,uBAAA;E/B04HD;A+Bp4HD;EACE,aAAA;EACA,oBAAA;EACA,iBAAA;EACA,mBAAA;EACA,cAAA;E/Bs4HD;A+Bp4HC;;EAEE,uBAAA;E/Bs4HH;A+B/4HD;EAaI,gBAAA;E/Bq4HH;A+B53HD;EALI;;IAEE,oBAAA;I/Bo4HH;EACF;A+B13HD;EACE,oBAAA;EACA,cAAA;EACA,oBAAA;EACA,mBAAA;EC/LA,iBAAA;EACA,oBAAA;EDgMA,+BAAA;EACA,wBAAA;EACA,+BAAA;EACA,oBAAA;E/B63HD;A+Bz3HC;EACE,YAAA;E/B23HH;A+Bz4HD;EAmBI,gBAAA;EACA,aAAA;EACA,aAAA;EACA,oBAAA;E/By3HH;A+B/4HD;EAyBI,iBAAA;E/By3HH;A+Bn3HD;EAAA;IAFI,eAAA;I/By3HD;EACF;A+Bh3HD;EACE,qBAAA;E/Bk3HD;A+Bn3HD;EAII,mBAAA;EACA,sBAAA;EACA,mBAAA;E/Bk3HH;A+Bt1HC;EAAA;IAtBI,kBAAA;IACA,aAAA;IACA,aAAA;IACA,eAAA;IACA,+BAAA;IACA,WAAA;IACA,0BAAA;YAAA,kBAAA;I/Bg3HH;E+Bh2HD;;IAbM,4BAAA;I/Bi3HL;E+Bp2HD;IAVM,mBAAA;I/Bi3HL;E+Bh3HK;;IAEE,wBAAA;I/Bk3HP;EACF;A+Bh2HD;EAAA;IAXI,aAAA;IACA,WAAA;I/B+2HD;E+Br2HH;IAPM,aAAA;I/B+2HH;E+Bx2HH;IALQ,mBAAA;IACA,sBAAA;I/Bg3HL;EACF;A+Br2HD;EACE,oBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,sCAAA;E1B/NA,8FAAA;EACQ,sFAAA;E2B/DR,iBAAA;EACA,oBAAA;EhCuoID;AkB9pHD;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBguHH;EkBpqHH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB8tHH;EkBzqHH;IAhDM,uBAAA;IlB4tHH;EkB5qHH;IA5CM,uBAAA;IACA,wBAAA;IlB2tHH;EkBhrHH;;;IAtCQ,aAAA;IlB2tHL;EkBrrHH;IAhCM,aAAA;IlBwtHH;EkBxrHH;IA5BM,kBAAA;IACA,wBAAA;IlButHH;EkB5rHH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBotHH;EkBnsHH;;IAdQ,iBAAA;IlBqtHL;EkBvsHH;;IATM,oBAAA;IACA,gBAAA;IlBotHH;EkB5sHH;IAHM,QAAA;IlBktHH;EACF;A+B94HC;EAAA;IANI,oBAAA;I/Bw5HH;E+Bt5HG;IACE,kBAAA;I/Bw5HL;EACF;A+Bv4HD;EAAA;IARI,aAAA;IACA,WAAA;IACA,gBAAA;IACA,iBAAA;IACA,gBAAA;IACA,mBAAA;I1B1PF,0BAAA;IACQ,kBAAA;IL8oIP;EACF;A+B74HD;EACE,eAAA;EHrUA,4BAAA;EACC,2BAAA;E5BqtIF;A+B74HD;EACE,kBAAA;EH1UA,8BAAA;EACC,6BAAA;EAOD,+BAAA;EACC,8BAAA;E5BotIF;A+Bz4HD;ECjVE,iBAAA;EACA,oBAAA;EhC6tID;A+B14HC;ECpVA,kBAAA;EACA,qBAAA;EhCiuID;A+B34HC;ECvVA,kBAAA;EACA,qBAAA;EhCquID;A+Br4HD;ECjWE,kBAAA;EACA,qBAAA;EhCyuID;A+Bj4HD;EAAA;IAJI,aAAA;IACA,mBAAA;IACA,oBAAA;I/By4HD;EACF;A+B52HD;EAhBE;IEzWA,wBAAA;IjCyuIC;E+B/3HD;IE7WA,yBAAA;IF+WE,qBAAA;I/Bi4HD;E+Bn4HD;IAKI,iBAAA;I/Bi4HH;EACF;A+Bx3HD;EACE,2BAAA;EACA,uBAAA;E/B03HD;A+B53HD;EAKI,gBAAA;E/B03HH;A+Bz3HG;;EAEE,gBAAA;EACA,+BAAA;E/B23HL;A+Bp4HD;EAcI,gBAAA;E/By3HH;A+Bv4HD;EAmBM,gBAAA;E/Bu3HL;A+Br3HK;;EAEE,gBAAA;EACA,+BAAA;E/Bu3HP;A+Bn3HK;;;EAGE,gBAAA;EACA,2BAAA;E/Bq3HP;A+Bj3HK;;;EAGE,gBAAA;EACA,+BAAA;E/Bm3HP;A+B35HD;EA8CI,uBAAA;E/Bg3HH;A+B/2HG;;EAEE,2BAAA;E/Bi3HL;A+Bl6HD;EAoDM,2BAAA;E/Bi3HL;A+Br6HD;;EA0DI,uBAAA;E/B+2HH;A+Bx2HK;;;EAGE,2BAAA;EACA,gBAAA;E/B02HP;A+Bz0HC;EAAA;IAzBQ,gBAAA;I/Bs2HP;E+Br2HO;;IAEE,gBAAA;IACA,+BAAA;I/Bu2HT;E+Bn2HO;;;IAGE,gBAAA;IACA,2BAAA;I/Bq2HT;E+Bj2HO;;;IAGE,gBAAA;IACA,+BAAA;I/Bm2HT;EACF;A+Br8HD;EA8GI,gBAAA;E/B01HH;A+Bz1HG;EACE,gBAAA;E/B21HL;A+B38HD;EAqHI,gBAAA;E/By1HH;A+Bx1HG;;EAEE,gBAAA;E/B01HL;A+Bt1HK;;;;EAEE,gBAAA;E/B01HP;A+Bl1HD;EACE,2BAAA;EACA,uBAAA;E/Bo1HD;A+Bt1HD;EAKI,gBAAA;E/Bo1HH;A+Bn1HG;;EAEE,gBAAA;EACA,+BAAA;E/Bq1HL;A+B91HD;EAcI,gBAAA;E/Bm1HH;A+Bj2HD;EAmBM,gBAAA;E/Bi1HL;A+B/0HK;;EAEE,gBAAA;EACA,+BAAA;E/Bi1HP;A+B70HK;;;EAGE,gBAAA;EACA,2BAAA;E/B+0HP;A+B30HK;;;EAGE,gBAAA;EACA,+BAAA;E/B60HP;A+Br3HD;EA+CI,uBAAA;E/By0HH;A+Bx0HG;;EAEE,2BAAA;E/B00HL;A+B53HD;EAqDM,2BAAA;E/B00HL;A+B/3HD;;EA2DI,uBAAA;E/Bw0HH;A+Bl0HK;;;EAGE,2BAAA;EACA,gBAAA;E/Bo0HP;A+B7xHC;EAAA;IA/BQ,uBAAA;I/Bg0HP;E+BjyHD;IA5BQ,2BAAA;I/Bg0HP;E+BpyHD;IAzBQ,gBAAA;I/Bg0HP;E+B/zHO;;IAEE,gBAAA;IACA,+BAAA;I/Bi0HT;E+B7zHO;;;IAGE,gBAAA;IACA,2BAAA;I/B+zHT;E+B3zHO;;;IAGE,gBAAA;IACA,+BAAA;I/B6zHT;EACF;A+Br6HD;EA+GI,gBAAA;E/ByzHH;A+BxzHG;EACE,gBAAA;E/B0zHL;A+B36HD;EAsHI,gBAAA;E/BwzHH;A+BvzHG;;EAEE,gBAAA;E/ByzHL;A+BrzHK;;;;EAEE,gBAAA;E/ByzHP;AkCp8ID;EACE,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,2BAAA;EACA,oBAAA;ElCs8ID;AkC38ID;EAQI,uBAAA;ElCs8IH;AkC98ID;EAWM,mBAAA;EACA,gBAAA;EACA,gBAAA;ElCs8IL;AkCn9ID;EAkBI,gBAAA;ElCo8IH;AmCx9ID;EACE,uBAAA;EACA,iBAAA;EACA,gBAAA;EACA,oBAAA;EnC09ID;AmC99ID;EAOI,iBAAA;EnC09IH;AmCj+ID;;EAUM,oBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,mBAAA;EnC29IL;AmCz9IG;;EAGI,gBAAA;EPXN,gCAAA;EACG,6BAAA;E5Bs+IJ;AmCx9IG;;EPvBF,iCAAA;EACG,8BAAA;E5Bm/IJ;AmCn9IG;;;;EAEE,gBAAA;EACA,2BAAA;EACA,uBAAA;EnCu9IL;AmCj9IG;;;;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,iBAAA;EnCs9IL;AmC5gJD;;;;;;EAiEM,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,qBAAA;EnCm9IL;AmC18ID;;EC1EM,oBAAA;EACA,iBAAA;EpCwhJL;AoCthJG;;ERMF,gCAAA;EACG,6BAAA;E5BohJJ;AoCrhJG;;ERRF,iCAAA;EACG,8BAAA;E5BiiJJ;AmCp9ID;;EC/EM,mBAAA;EACA,iBAAA;EpCuiJL;AoCriJG;;ERMF,gCAAA;EACG,6BAAA;E5BmiJJ;AoCpiJG;;ERRF,iCAAA;EACG,8BAAA;E5BgjJJ;AqCnjJD;EACE,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oBAAA;ErCqjJD;AqCzjJD;EAOI,iBAAA;ErCqjJH;AqC5jJD;;EAUM,uBAAA;EACA,mBAAA;EACA,2BAAA;EACA,2BAAA;EACA,qBAAA;ErCsjJL;AqCpkJD;;EAmBM,uBAAA;EACA,2BAAA;ErCqjJL;AqCzkJD;;EA2BM,cAAA;ErCkjJL;AqC7kJD;;EAkCM,aAAA;ErC+iJL;AqCjlJD;;;;EA2CM,gBAAA;EACA,2BAAA;EACA,qBAAA;ErC4iJL;AsC1lJD;EACE,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,qBAAA;EACA,0BAAA;EACA,sBAAA;EtC4lJD;AsCxlJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EtC0lJL;AsCrlJC;EACE,eAAA;EtCulJH;AsCnlJC;EACE,oBAAA;EACA,WAAA;EtCqlJH;AsC9kJD;ECtCE,2BAAA;EvCunJD;AuCpnJG;;EAEE,2BAAA;EvCsnJL;AsCjlJD;EC1CE,2BAAA;EvC8nJD;AuC3nJG;;EAEE,2BAAA;EvC6nJL;AsCplJD;EC9CE,2BAAA;EvCqoJD;AuCloJG;;EAEE,2BAAA;EvCooJL;AsCvlJD;EClDE,2BAAA;EvC4oJD;AuCzoJG;;EAEE,2BAAA;EvC2oJL;AsC1lJD;ECtDE,2BAAA;EvCmpJD;AuChpJG;;EAEE,2BAAA;EvCkpJL;AsC7lJD;EC1DE,2BAAA;EvC0pJD;AuCvpJG;;EAEE,2BAAA;EvCypJL;AwC3pJD;EACE,uBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EACA,qBAAA;EACA,oBAAA;EACA,2BAAA;EACA,qBAAA;ExC6pJD;AwC1pJC;EACE,eAAA;ExC4pJH;AwCxpJC;EACE,oBAAA;EACA,WAAA;ExC0pJH;AwCvpJC;EACE,QAAA;EACA,kBAAA;ExCypJH;AwCppJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;ExCspJL;AwCjpJC;;EAEE,gBAAA;EACA,2BAAA;ExCmpJH;AwChpJC;EACE,cAAA;ExCkpJH;AwC/oJC;EACE,mBAAA;ExCipJH;AwC9oJC;EACE,kBAAA;ExCgpJH;AyCzsJD;EACE,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2BAAA;EzC2sJD;AyC/sJD;;EAQI,gBAAA;EzC2sJH;AyCntJD;EAYI,qBAAA;EACA,iBAAA;EACA,kBAAA;EzC0sJH;AyCxtJD;EAkBI,2BAAA;EzCysJH;AyCtsJC;;EAEE,oBAAA;EzCwsJH;AyC/tJD;EA2BI,iBAAA;EzCusJH;AyCtrJD;EAAA;IAbI,iBAAA;IzCusJD;EyCrsJC;;IAEE,oBAAA;IACA,qBAAA;IzCusJH;EyC/rJH;;IAHM,iBAAA;IzCssJH;EACF;A0C/uJD;EACE,gBAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;ErCiLA,6CAAA;EACK,wCAAA;EACG,qCAAA;ELikJT;A0C3vJD;;EAaI,mBAAA;EACA,oBAAA;E1CkvJH;A0C9uJC;;;EAGE,uBAAA;E1CgvJH;A0CrwJD;EA0BI,cAAA;EACA,gBAAA;E1C8uJH;A2CvwJD;EACE,eAAA;EACA,qBAAA;EACA,+BAAA;EACA,oBAAA;E3CywJD;A2C7wJD;EAQI,eAAA;EAEA,gBAAA;E3CuwJH;A2CjxJD;EAeI,mBAAA;E3CqwJH;A2CpxJD;;EAqBI,kBAAA;E3CmwJH;A2CxxJD;EAyBI,iBAAA;E3CkwJH;A2C1vJD;;EAEE,qBAAA;E3C4vJD;A2C9vJD;;EAMI,oBAAA;EACA,WAAA;EACA,cAAA;EACA,gBAAA;E3C4vJH;A2CpvJD;ECvDE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C8yJD;A2CzvJD;EClDI,2BAAA;E5C8yJH;A2C5vJD;EC/CI,gBAAA;E5C8yJH;A2C3vJD;EC3DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5CyzJD;A2ChwJD;ECtDI,2BAAA;E5CyzJH;A2CnwJD;ECnDI,gBAAA;E5CyzJH;A2ClwJD;EC/DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Co0JD;A2CvwJD;EC1DI,2BAAA;E5Co0JH;A2C1wJD;ECvDI,gBAAA;E5Co0JH;A2CzwJD;ECnEE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C+0JD;A2C9wJD;EC9DI,2BAAA;E5C+0JH;A2CjxJD;EC3DI,gBAAA;E5C+0JH;A6Cj1JD;EACE;IAAQ,6BAAA;I7Co1JP;E6Cn1JD;IAAQ,0BAAA;I7Cs1JP;EACF;A6Cn1JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6C31JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6Cj1JD;EACE,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,2BAAA;EACA,oBAAA;ExCsCA,wDAAA;EACQ,gDAAA;EL8yJT;A6Ch1JD;EACE,aAAA;EACA,WAAA;EACA,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;ExCyBA,wDAAA;EACQ,gDAAA;EAyHR,qCAAA;EACK,gCAAA;EACG,6BAAA;ELksJT;A6C70JD;;ECCI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDAF,oCAAA;UAAA,4BAAA;E7Ci1JD;A6C10JD;;ExC5CE,4DAAA;EACK,uDAAA;EACG,oDAAA;EL03JT;A6Cv0JD;EErEE,2BAAA;E/C+4JD;A+C54JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+1JH;A6C30JD;EEzEE,2BAAA;E/Cu5JD;A+Cp5JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu2JH;A6C/0JD;EE7EE,2BAAA;E/C+5JD;A+C55JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+2JH;A6Cn1JD;EEjFE,2BAAA;E/Cu6JD;A+Cp6JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu3JH;AgD/6JD;EAEE,kBAAA;EhDg7JD;AgD96JC;EACE,eAAA;EhDg7JH;AgD56JD;;EAEE,SAAA;EACA,kBAAA;EhD86JD;AgD36JD;EACE,gBAAA;EhD66JD;AgD16JD;EACE,gBAAA;EhD46JD;AgDz6JD;;EAEE,oBAAA;EhD26JD;AgDx6JD;;EAEE,qBAAA;EhD06JD;AgDv6JD;;;EAGE,qBAAA;EACA,qBAAA;EhDy6JD;AgDt6JD;EACE,wBAAA;EhDw6JD;AgDr6JD;EACE,wBAAA;EhDu6JD;AgDn6JD;EACE,eAAA;EACA,oBAAA;EhDq6JD;AgD/5JD;EACE,iBAAA;EACA,kBAAA;EhDi6JD;AiDn9JD;EAEE,qBAAA;EACA,iBAAA;EjDo9JD;AiD58JD;EACE,oBAAA;EACA,gBAAA;EACA,oBAAA;EAEA,qBAAA;EACA,2BAAA;EACA,2BAAA;EjD68JD;AiD18JC;ErB3BA,8BAAA;EACC,6BAAA;E5Bw+JF;AiD38JC;EACE,kBAAA;ErBvBF,iCAAA;EACC,gCAAA;E5Bq+JF;AiDp8JD;EACE,gBAAA;EjDs8JD;AiDv8JD;EAII,gBAAA;EjDs8JH;AiDl8JC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;EjDo8JH;AiD97JC;;;EAGE,2BAAA;EACA,gBAAA;EACA,qBAAA;EjDg8JH;AiDr8JC;;;EASI,gBAAA;EjDi8JL;AiD18JC;;;EAYI,gBAAA;EjDm8JL;AiD97JC;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EjDg8JH;AiDt8JC;;;;;;;;;EAYI,gBAAA;EjDq8JL;AiDj9JC;;;EAeI,gBAAA;EjDu8JL;AkDniKC;EACE,gBAAA;EACA,2BAAA;ElDqiKH;AkDniKG;EACE,gBAAA;ElDqiKL;AkDtiKG;EAII,gBAAA;ElDqiKP;AkDliKK;;EAEE,gBAAA;EACA,2BAAA;ElDoiKP;AkDliKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDoiKP;AkDzjKC;EACE,gBAAA;EACA,2BAAA;ElD2jKH;AkDzjKG;EACE,gBAAA;ElD2jKL;AkD5jKG;EAII,gBAAA;ElD2jKP;AkDxjKK;;EAEE,gBAAA;EACA,2BAAA;ElD0jKP;AkDxjKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElD0jKP;AkD/kKC;EACE,gBAAA;EACA,2BAAA;ElDilKH;AkD/kKG;EACE,gBAAA;ElDilKL;AkDllKG;EAII,gBAAA;ElDilKP;AkD9kKK;;EAEE,gBAAA;EACA,2BAAA;ElDglKP;AkD9kKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDglKP;AkDrmKC;EACE,gBAAA;EACA,2BAAA;ElDumKH;AkDrmKG;EACE,gBAAA;ElDumKL;AkDxmKG;EAII,gBAAA;ElDumKP;AkDpmKK;;EAEE,gBAAA;EACA,2BAAA;ElDsmKP;AkDpmKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDsmKP;AiD1gKD;EACE,eAAA;EACA,oBAAA;EjD4gKD;AiD1gKD;EACE,kBAAA;EACA,kBAAA;EjD4gKD;AmDhoKD;EACE,qBAAA;EACA,2BAAA;EACA,+BAAA;EACA,oBAAA;E9C0DA,mDAAA;EACQ,2CAAA;ELykKT;AmD/nKD;EACE,eAAA;EnDioKD;AmD5nKD;EACE,oBAAA;EACA,sCAAA;EvBpBA,8BAAA;EACC,6BAAA;E5BmpKF;AmDloKD;EAMI,gBAAA;EnD+nKH;AmD1nKD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,gBAAA;EnD4nKD;AmDhoKD;;;;;EAWI,gBAAA;EnD4nKH;AmDvnKD;EACE,oBAAA;EACA,2BAAA;EACA,+BAAA;EvBxCA,iCAAA;EACC,gCAAA;E5BkqKF;AmDjnKD;;EAGI,kBAAA;EnDknKH;AmDrnKD;;EAMM,qBAAA;EACA,kBAAA;EnDmnKL;AmD/mKG;;EAEI,eAAA;EvBvEN,8BAAA;EACC,6BAAA;E5ByrKF;AmD9mKG;;EAEI,kBAAA;EvBtEN,iCAAA;EACC,gCAAA;E5BurKF;AmD3mKD;EAEI,qBAAA;EnD4mKH;AmDzmKD;EACE,qBAAA;EnD2mKD;AmDnmKD;;;EAII,kBAAA;EnDomKH;AmDxmKD;;;EAOM,oBAAA;EACA,qBAAA;EnDsmKL;AmD9mKD;;EvBnGE,8BAAA;EACC,6BAAA;E5BqtKF;AmDnnKD;;;;EAmBQ,6BAAA;EACA,8BAAA;EnDsmKP;AmD1nKD;;;;;;;;EAwBU,6BAAA;EnD4mKT;AmDpoKD;;;;;;;;EA4BU,8BAAA;EnDknKT;AmD9oKD;;EvB3FE,iCAAA;EACC,gCAAA;E5B6uKF;AmDnpKD;;;;EAyCQ,gCAAA;EACA,iCAAA;EnDgnKP;AmD1pKD;;;;;;;;EA8CU,gCAAA;EnDsnKT;AmDpqKD;;;;;;;;EAkDU,iCAAA;EnD4nKT;AmD9qKD;;;;EA2DI,+BAAA;EnDynKH;AmDprKD;;EA+DI,eAAA;EnDynKH;AmDxrKD;;EAmEI,WAAA;EnDynKH;AmD5rKD;;;;;;;;;;;;EA0EU,gBAAA;EnDgoKT;AmD1sKD;;;;;;;;;;;;EA8EU,iBAAA;EnD0oKT;AmDxtKD;;;;;;;;EAuFU,kBAAA;EnD2oKT;AmDluKD;;;;;;;;EAgGU,kBAAA;EnD4oKT;AmD5uKD;EAsGI,WAAA;EACA,kBAAA;EnDyoKH;AmD/nKD;EACE,qBAAA;EnDioKD;AmDloKD;EAKI,kBAAA;EACA,oBAAA;EnDgoKH;AmDtoKD;EASM,iBAAA;EnDgoKL;AmDzoKD;EAcI,kBAAA;EnD8nKH;AmD5oKD;;EAkBM,+BAAA;EnD8nKL;AmDhpKD;EAuBI,eAAA;EnD4nKH;AmDnpKD;EAyBM,kCAAA;EnD6nKL;AmDtnKD;ECpPE,uBAAA;EpD62KD;AoD32KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD62KH;AoDh3KC;EAMI,2BAAA;EpD62KL;AoDn3KC;EASI,gBAAA;EACA,2BAAA;EpD62KL;AoD12KC;EAEI,8BAAA;EpD22KL;AmDroKD;ECvPE,uBAAA;EpD+3KD;AoD73KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD+3KH;AoDl4KC;EAMI,2BAAA;EpD+3KL;AoDr4KC;EASI,gBAAA;EACA,2BAAA;EpD+3KL;AoD53KC;EAEI,8BAAA;EpD63KL;AmDppKD;EC1PE,uBAAA;EpDi5KD;AoD/4KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDi5KH;AoDp5KC;EAMI,2BAAA;EpDi5KL;AoDv5KC;EASI,gBAAA;EACA,2BAAA;EpDi5KL;AoD94KC;EAEI,8BAAA;EpD+4KL;AmDnqKD;EC7PE,uBAAA;EpDm6KD;AoDj6KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDm6KH;AoDt6KC;EAMI,2BAAA;EpDm6KL;AoDz6KC;EASI,gBAAA;EACA,2BAAA;EpDm6KL;AoDh6KC;EAEI,8BAAA;EpDi6KL;AmDlrKD;EChQE,uBAAA;EpDq7KD;AoDn7KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDq7KH;AoDx7KC;EAMI,2BAAA;EpDq7KL;AoD37KC;EASI,gBAAA;EACA,2BAAA;EpDq7KL;AoDl7KC;EAEI,8BAAA;EpDm7KL;AmDjsKD;ECnQE,uBAAA;EpDu8KD;AoDr8KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDu8KH;AoD18KC;EAMI,2BAAA;EpDu8KL;AoD78KC;EASI,gBAAA;EACA,2BAAA;EpDu8KL;AoDp8KC;EAEI,8BAAA;EpDq8KL;AqDr9KD;EACE,oBAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;ErDu9KD;AqD59KD;;;;;EAYI,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,cAAA;EACA,aAAA;EACA,WAAA;ErDu9KH;AqDn9KC;EACE,wBAAA;ErDq9KH;AqDj9KC;EACE,qBAAA;ErDm9KH;AsD7+KD;EACE,kBAAA;EACA,eAAA;EACA,qBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EjDwDA,yDAAA;EACQ,iDAAA;ELw7KT;AsDv/KD;EASI,oBAAA;EACA,mCAAA;EtDi/KH;AsD5+KD;EACE,eAAA;EACA,oBAAA;EtD8+KD;AsD5+KD;EACE,cAAA;EACA,oBAAA;EtD8+KD;AuDpgLD;EACE,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,8BAAA;EjCRA,cAAA;EAGA,2BAAA;EtB6gLD;AuDrgLC;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EjCfF,cAAA;EAGA,2BAAA;EtBqhLD;AuDjgLC;EACE,YAAA;EACA,iBAAA;EACA,yBAAA;EACA,WAAA;EACA,0BAAA;EvDmgLH;AwDxhLD;EACE,kBAAA;ExD0hLD;AwDthLD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,mCAAA;EAIA,YAAA;ExDqhLD;AwDlhLC;EnD+GA,uCAAA;EACI,mCAAA;EACC,kCAAA;EACG,+BAAA;EAkER,qDAAA;EAEK,2CAAA;EACG,qCAAA;ELq2KT;AwDxhLC;EnD2GA,oCAAA;EACI,gCAAA;EACC,+BAAA;EACG,4BAAA;ELg7KT;AwD5hLD;EACE,oBAAA;EACA,kBAAA;ExD8hLD;AwD1hLD;EACE,oBAAA;EACA,aAAA;EACA,cAAA;ExD4hLD;AwDxhLD;EACE,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;EnDaA,kDAAA;EACQ,0CAAA;EmDZR,sCAAA;UAAA,8BAAA;EAEA,YAAA;ExD0hLD;AwDthLD;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,2BAAA;ExDwhLD;AwDthLC;ElCnEA,YAAA;EAGA,0BAAA;EtB0lLD;AwDzhLC;ElCpEA,cAAA;EAGA,2BAAA;EtB8lLD;AwDxhLD;EACE,eAAA;EACA,kCAAA;EACA,2BAAA;ExD0hLD;AwDvhLD;EACE,kBAAA;ExDyhLD;AwDrhLD;EACE,WAAA;EACA,yBAAA;ExDuhLD;AwDlhLD;EACE,oBAAA;EACA,eAAA;ExDohLD;AwDhhLD;EACE,eAAA;EACA,mBAAA;EACA,+BAAA;ExDkhLD;AwDrhLD;EAQI,kBAAA;EACA,kBAAA;ExDghLH;AwDzhLD;EAaI,mBAAA;ExD+gLH;AwD5hLD;EAiBI,gBAAA;ExD8gLH;AwDzgLD;EACE,oBAAA;EACA,cAAA;EACA,aAAA;EACA,cAAA;EACA,kBAAA;ExD2gLD;AwDz/KD;EAZE;IACE,cAAA;IACA,mBAAA;IxDwgLD;EwDtgLD;InDrEA,mDAAA;IACQ,2CAAA;IL8kLP;EwDrgLD;IAAY,cAAA;IxDwgLX;EACF;AwDngLD;EAFE;IAAY,cAAA;IxDygLX;EACF;AyDtpLD;EACE,oBAAA;EACA,eAAA;EACA,gBAAA;EACA,qBAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,kBAAA;EnCZA,YAAA;EAGA,0BAAA;EtBkqLD;AyDtpLC;EnCfA,cAAA;EAGA,2BAAA;EtBsqLD;AyDzpLC;EAAW,kBAAA;EAAmB,gBAAA;EzD6pL/B;AyD5pLC;EAAW,kBAAA;EAAmB,gBAAA;EzDgqL/B;AyD/pLC;EAAW,iBAAA;EAAmB,gBAAA;EzDmqL/B;AyDlqLC;EAAW,mBAAA;EAAmB,gBAAA;EzDsqL/B;AyDlqLD;EACE,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,uBAAA;EACA,2BAAA;EACA,oBAAA;EzDoqLD;AyDhqLD;EACE,oBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;EzDkqLD;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,YAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,SAAA;EACA,kBAAA;EACA,6BAAA;EACA,6BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,6BAAA;EACA,4BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,YAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;A0D/vLD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,kBAAA;EACA,cAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;EACA,2BAAA;EACA,sCAAA;UAAA,8BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;ErD6CA,mDAAA;EACQ,2CAAA;EqD1CR,qBAAA;E1D+vLD;A0D5vLC;EAAY,mBAAA;E1D+vLb;A0D9vLC;EAAY,mBAAA;E1DiwLb;A0DhwLC;EAAY,kBAAA;E1DmwLb;A0DlwLC;EAAY,oBAAA;E1DqwLb;A0DlwLD;EACE,WAAA;EACA,mBAAA;EACA,iBAAA;EACA,2BAAA;EACA,kCAAA;EACA,4BAAA;E1DowLD;A0DjwLD;EACE,mBAAA;E1DmwLD;A0D3vLC;;EAEE,oBAAA;EACA,gBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;E1D6vLH;A0D1vLD;EACE,oBAAA;E1D4vLD;A0D1vLD;EACE,oBAAA;EACA,aAAA;E1D4vLD;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;EACA,uCAAA;EACA,eAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;E1D2vLL;A0DxvLC;EACE,UAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,6BAAA;EACA,yCAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,WAAA;EACA,eAAA;EACA,sBAAA;EACA,6BAAA;E1D2vLL;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;EACA,0CAAA;EACA,YAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,UAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;E1D2vLL;A0DvvLC;EACE,UAAA;EACA,cAAA;EACA,mBAAA;EACA,uBAAA;EACA,4BAAA;EACA,wCAAA;E1DyvLH;A0DxvLG;EACE,cAAA;EACA,YAAA;EACA,uBAAA;EACA,4BAAA;EACA,eAAA;E1D0vLL;A2Dv3LD;EACE,oBAAA;E3Dy3LD;A2Dt3LD;EACE,oBAAA;EACA,kBAAA;EACA,aAAA;E3Dw3LD;A2D33LD;EAMI,eAAA;EACA,oBAAA;EtD6KF,2CAAA;EACK,sCAAA;EACG,mCAAA;EL4sLT;A2Dl4LD;;EAcM,gBAAA;E3Dw3LL;A2D91LC;EAAA;ItDiKA,wDAAA;IAEK,8CAAA;IACG,wCAAA;IA7JR,qCAAA;IAEQ,6BAAA;IA+GR,2BAAA;IAEQ,mBAAA;ILivLP;E2D53LG;;ItDmHJ,4CAAA;IACQ,oCAAA;IsDjHF,SAAA;I3D+3LL;E2D73LG;;ItD8GJ,6CAAA;IACQ,qCAAA;IsD5GF,SAAA;I3Dg4LL;E2D93LG;;;ItDyGJ,yCAAA;IACQ,iCAAA;IsDtGF,SAAA;I3Di4LL;EACF;A2Dv6LD;;;EA6CI,gBAAA;E3D+3LH;A2D56LD;EAiDI,SAAA;E3D83LH;A2D/6LD;;EAsDI,oBAAA;EACA,QAAA;EACA,aAAA;E3D63LH;A2Dr7LD;EA4DI,YAAA;E3D43LH;A2Dx7LD;EA+DI,aAAA;E3D43LH;A2D37LD;;EAmEI,SAAA;E3D43LH;A2D/7LD;EAuEI,aAAA;E3D23LH;A2Dl8LD;EA0EI,YAAA;E3D23LH;A2Dn3LD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA;ErC9FA,cAAA;EAGA,2BAAA;EqC6FA,iBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3Ds3LD;A2Dj3LC;EblGE,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9Cs9LH;A2Dr3LC;EACE,YAAA;EACA,UAAA;EbvGA,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9C+9LH;A2Dv3LC;;EAEE,YAAA;EACA,gBAAA;EACA,uBAAA;ErCtHF,cAAA;EAGA,2BAAA;EtB8+LD;A2Dx5LD;;;;EAsCI,oBAAA;EACA,UAAA;EACA,YAAA;EACA,uBAAA;E3Dw3LH;A2Dj6LD;;EA6CI,WAAA;EACA,oBAAA;E3Dw3LH;A2Dt6LD;;EAkDI,YAAA;EACA,qBAAA;E3Dw3LH;A2D36LD;;EAuDI,aAAA;EACA,cAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;E3Dw3LH;A2Dn3LG;EACE,kBAAA;E3Dq3LL;A2Dj3LG;EACE,kBAAA;E3Dm3LL;A2Dz2LD;EACE,oBAAA;EACA,cAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;E3D22LD;A2Dp3LD;EAYI,uBAAA;EACA,aAAA;EACA,cAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;EACA,qBAAA;EACA,iBAAA;EAWA,2BAAA;EACA,oCAAA;E3Di2LH;A2Dh4LD;EAkCI,WAAA;EACA,aAAA;EACA,cAAA;EACA,2BAAA;E3Di2LH;A2D11LD;EACE,oBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3D41LD;A2D31LC;EACE,mBAAA;E3D61LH;A2DpzLD;EAhCE;;;;IAKI,aAAA;IACA,cAAA;IACA,mBAAA;IACA,iBAAA;I3Ds1LH;E2D91LD;;IAYI,oBAAA;I3Ds1LH;E2Dl2LD;;IAgBI,qBAAA;I3Ds1LH;E2Dj1LD;IACE,WAAA;IACA,YAAA;IACA,sBAAA;I3Dm1LD;E2D/0LD;IACE,cAAA;I3Di1LD;EACF;A4D/kMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,cAAA;EACA,gBAAA;E5D6mMH;A4D3mMC;;;;;;;;;;;;;;;EACE,aAAA;E5D2nMH;AiCnoMD;E4BRE,gBAAA;EACA,mBAAA;EACA,oBAAA;E7D8oMD;AiCroMD;EACE,yBAAA;EjCuoMD;AiCroMD;EACE,wBAAA;EjCuoMD;AiC/nMD;EACE,0BAAA;EjCioMD;AiC/nMD;EACE,2BAAA;EjCioMD;AiC/nMD;EACE,oBAAA;EjCioMD;AiC/nMD;E6BzBE,aAAA;EACA,oBAAA;EACA,mBAAA;EACA,+BAAA;EACA,WAAA;E9D2pMD;AiC7nMD;EACE,0BAAA;EACA,+BAAA;EjC+nMD;AiCxnMD;EACE,iBAAA;EjC0nMD;A+D5pMD;EACE,qBAAA;E/D8pMD;A+DxpMD;;;;ECdE,0BAAA;EhE4qMD;A+DvpMD;;;;;;;;;;;;EAYE,0BAAA;E/DypMD;A+DlpMD;EAAA;IChDE,2BAAA;IhEssMC;EgErsMD;IAAU,gBAAA;IhEwsMT;EgEvsMD;IAAU,+BAAA;IhE0sMT;EgEzsMD;;IACU,gCAAA;IhE4sMT;EACF;A+D5pMD;EAAA;IAFI,2BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,4BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,kCAAA;I/DkqMD;EACF;A+D3pMD;EAAA;ICrEE,2BAAA;IhEouMC;EgEnuMD;IAAU,gBAAA;IhEsuMT;EgEruMD;IAAU,+BAAA;IhEwuMT;EgEvuMD;;IACU,gCAAA;IhE0uMT;EACF;A+DrqMD;EAAA;IAFI,2BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,4BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,kCAAA;I/D2qMD;EACF;A+DpqMD;EAAA;IC1FE,2BAAA;IhEkwMC;EgEjwMD;IAAU,gBAAA;IhEowMT;EgEnwMD;IAAU,+BAAA;IhEswMT;EgErwMD;;IACU,gCAAA;IhEwwMT;EACF;A+D9qMD;EAAA;IAFI,2BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,4BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,kCAAA;I/DorMD;EACF;A+D7qMD;EAAA;IC/GE,2BAAA;IhEgyMC;EgE/xMD;IAAU,gBAAA;IhEkyMT;EgEjyMD;IAAU,+BAAA;IhEoyMT;EgEnyMD;;IACU,gCAAA;IhEsyMT;EACF;A+DvrMD;EAAA;IAFI,2BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,4BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,kCAAA;I/D6rMD;EACF;A+DtrMD;EAAA;IC5HE,0BAAA;IhEszMC;EACF;A+DtrMD;EAAA;ICjIE,0BAAA;IhE2zMC;EACF;A+DtrMD;EAAA;ICtIE,0BAAA;IhEg0MC;EACF;A+DtrMD;EAAA;IC3IE,0BAAA;IhEq0MC;EACF;A+DnrMD;ECnJE,0BAAA;EhEy0MD;A+DhrMD;EAAA;ICjKE,2BAAA;IhEq1MC;EgEp1MD;IAAU,gBAAA;IhEu1MT;EgEt1MD;IAAU,+BAAA;IhEy1MT;EgEx1MD;;IACU,gCAAA;IhE21MT;EACF;A+D9rMD;EACE,0BAAA;E/DgsMD;A+D3rMD;EAAA;IAFI,2BAAA;I/DisMD;EACF;A+D/rMD;EACE,0BAAA;E/DisMD;A+D5rMD;EAAA;IAFI,4BAAA;I/DksMD;EACF;A+DhsMD;EACE,0BAAA;E/DksMD;A+D7rMD;EAAA;IAFI,kCAAA;I/DmsMD;EACF;A+D5rMD;EAAA;ICpLE,0BAAA;IhEo3MC;EACF","file":"bootstrap.css","sourcesContent":["/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n select {\n background: #fff !important;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\2a\";\n}\n.glyphicon-plus:before {\n content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #ffffff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #ffffff;\n background-color: #333333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #dddddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #dddddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #dddddd;\n}\n.table .table {\n background-color: #ffffff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #dddddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #ffffff;\n background-image: none;\n border: 1px solid #cccccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n background-color: #eeeeee;\n opacity: 1;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n height: 30px;\n line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n height: 46px;\n line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 14.333333px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n pointer-events: none;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default {\n color: #333333;\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default .badge {\n color: #ffffff;\n background-color: #333333;\n}\n.btn-primary {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #ffffff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.btn-success {\n color: #ffffff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #ffffff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #ffffff;\n}\n.btn-info {\n color: #ffffff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #ffffff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #ffffff;\n}\n.btn-warning {\n color: #ffffff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #ffffff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #ffffff;\n}\n.btn-danger {\n color: #ffffff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #ffffff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #ffffff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n visibility: hidden;\n}\n.collapse.in {\n display: block;\n visibility: visible;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px solid;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #ffffff;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #ffffff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px solid;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-bottom-left-radius: 4px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #dddddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #dddddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #ffffff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n visibility: hidden;\n}\n.tab-content > .active {\n display: block;\n visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #dddddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #dddddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777777;\n}\n.navbar-default .navbar-link:hover {\n color: #333333;\n}\n.navbar-default .btn-link {\n color: #777777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #cccccc;\n}\n.navbar-inverse {\n background-color: #222222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #ffffff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #ffffff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #ffffff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #ffffff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #cccccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n color: #23527c;\n background-color: #eeeeee;\n border-color: #dddddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #ffffff;\n border-color: #dddddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #ffffff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #ffffff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #ffffff;\n line-height: 1;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding: 30px 15px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding: 48px 0;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #ffffff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item {\n color: #555555;\n}\na.list-group-item .list-group-item-heading {\n color: #333333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n text-decoration: none;\n color: #555555;\n background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #ffffff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #dddddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #dddddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #dddddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #dddddd;\n}\n.panel-default {\n border-color: #dddddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #dddddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #dddddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #dddddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000000;\n text-shadow: 0 1px 0 #ffffff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #ffffff;\n border: 1px solid #999999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: #000000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n min-height: 16.42857143px;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n visibility: visible;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-weight: normal;\n line-height: 1.4;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #ffffff;\n text-align: center;\n text-decoration: none;\n background-color: #000000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n background-color: #ffffff;\n background-clip: padding-box;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n white-space: normal;\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #ffffff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #ffffff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #ffffff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #ffffff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000;\n -moz-perspective: 1000;\n perspective: 1000;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #ffffff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #ffffff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #ffffff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -15px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -15px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n //\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: @input-height-base;\n\n &.input-sm,\n .input-group-sm & {\n line-height: @input-height-small;\n }\n\n &.input-lg,\n .input-group-lg & {\n line-height: @input-height-large;\n }\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because <label>s don't inherit their parent's `cursor`.\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n &[disabled],\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used directly on <label>s\n.radio-inline,\n.checkbox-inline {\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used on elements with <label> descendants\n.radio,\n.checkbox {\n &.disabled,\n fieldset[disabled] & {\n label {\n cursor: @cursor-disabled;\n }\n }\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n // Size it appropriately next to real form controls\n padding-top: (@padding-base-vertical + 1);\n padding-bottom: (@padding-base-vertical + 1);\n // Remove default margin from `p`\n margin-bottom: 0;\n\n &.input-lg,\n &.input-sm {\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// The `.form-group-* form-control` variations are sadly duplicated to avoid the\n// issue documented in https://github.com/twbs/bootstrap/issues/15074.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n}\n.form-group-sm {\n .form-control {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n }\n .form-control-static {\n height: @input-height-small;\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n }\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n}\n.form-group-lg {\n .form-control {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n }\n .form-control-static {\n height: @input-height-large;\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n }\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n}\n// Feedback icon (requires .glyphicon classes)\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2; // Ensure icon is above input groups\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: @input-height-large;\n height: @input-height-large;\n line-height: @input-height-large;\n}\n.input-sm + .form-control-feedback {\n width: @input-height-small;\n height: @input-height-small;\n line-height: @input-height-small;\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n// Reposition feedback icon if input has visible label above\n.has-feedback label {\n\n & ~ .form-control-feedback {\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n }\n &.sr-only ~ .form-control-feedback {\n top: 0;\n }\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-static {\n display: inline-block;\n }\n\n .input-group {\n display: inline-table;\n vertical-align: middle;\n\n .input-group-addon,\n .input-group-btn,\n .form-control {\n width: auto;\n }\n }\n\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n\n label {\n padding-left: 0;\n }\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n\n // Re-override the feedback icon.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of radios and checkboxes\n //\n // Labels also get some reset styles, but that is scoped to a media query below.\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n // Reset spacing and right align labels, but scope to media queries so that\n // labels on narrow viewports stack the same as a default form example.\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n right: (@grid-gutter-width / 2);\n }\n\n // Form group sizes\n //\n // Quick utility class for applying `.input-lg` and `.input-sm` styles to the\n // inputs and labels within a `.form-group`.\n .form-group-lg {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: ((@padding-large-vertical * @line-height-large) + 1);\n }\n }\n }\n .form-group-sm {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-small-vertical + 1);\n }\n }\n }\n}\n","// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline,\n &.radio label,\n &.checkbox label,\n &.radio-inline label,\n &.checkbox-inline label {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-border-focus` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n pointer-events: none; // Future-proof disabling of clicks\n .opacity(.65);\n .box-shadow(none);\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n visibility: hidden;\n\n &.in { display: block; visibility: visible; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base solid;\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base solid;\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n border-top-right-radius: @border-radius-base;\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n border-bottom-left-radius: @border-radius-base;\n .border-top-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @border-radius-base;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n visibility: hidden;\n }\n > .active {\n display: block;\n visibility: visible;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n \n .btn-xs & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n \n .list-group-item > & {\n float: right;\n }\n \n .list-group-item > & + & {\n margin-right: 5px;\n }\n \n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding: @jumbotron-padding (@jumbotron-padding / 2);\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n \n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding: (@jumbotron-padding * 1.6) 0;\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: (@font-size-base * 4.5);\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n \n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n \n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Linked list items\n//\n// Use anchor elements instead of `li`s or `div`s to create linked list items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n\n // Modifier class for 16:9 aspect ratio\n &.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n }\n\n // Modifier class for 4:3 aspect ratio\n &.embed-responsive-4by3 {\n padding-bottom: 75%;\n }\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n visibility: visible;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-small;\n font-weight: normal;\n line-height: 1.4;\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n text-decoration: none;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: @line-height-base;\n text-align: left;\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Overrides for proper insertion\n white-space: normal;\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: -15px;\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: -15px;\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css
new file mode 100644
index 000000000..28f154dec
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 000000000..b93a4953f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 000000000..94fb5490a
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,288 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
+<font-face units-per-em="1200" ascent="960" descent="-240" />
+<missing-glyph horiz-adv-x="500" />
+<glyph horiz-adv-x="0" />
+<glyph horiz-adv-x="400" />
+<glyph unicode=" " />
+<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
+<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xa0;" />
+<glyph unicode="&#xa5;" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
+<glyph unicode="&#x2000;" horiz-adv-x="650" />
+<glyph unicode="&#x2001;" horiz-adv-x="1300" />
+<glyph unicode="&#x2002;" horiz-adv-x="650" />
+<glyph unicode="&#x2003;" horiz-adv-x="1300" />
+<glyph unicode="&#x2004;" horiz-adv-x="433" />
+<glyph unicode="&#x2005;" horiz-adv-x="325" />
+<glyph unicode="&#x2006;" horiz-adv-x="216" />
+<glyph unicode="&#x2007;" horiz-adv-x="216" />
+<glyph unicode="&#x2008;" horiz-adv-x="162" />
+<glyph unicode="&#x2009;" horiz-adv-x="260" />
+<glyph unicode="&#x200a;" horiz-adv-x="72" />
+<glyph unicode="&#x202f;" horiz-adv-x="260" />
+<glyph unicode="&#x205f;" horiz-adv-x="325" />
+<glyph unicode="&#x20ac;" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
+<glyph unicode="&#x20bd;" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
+<glyph unicode="&#x2212;" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#x231b;" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#x2601;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
+<glyph unicode="&#x26fa;" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
+<glyph unicode="&#x2709;" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
+<glyph unicode="&#x270f;" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
+<glyph unicode="&#xe001;" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
+<glyph unicode="&#xe002;" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
+<glyph unicode="&#xe003;" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
+<glyph unicode="&#xe005;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
+<glyph unicode="&#xe006;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
+<glyph unicode="&#xe007;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
+<glyph unicode="&#xe008;" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
+<glyph unicode="&#xe009;" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
+<glyph unicode="&#xe010;" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe011;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" />
+<glyph unicode="&#xe012;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe013;" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
+<glyph unicode="&#xe014;" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
+<glyph unicode="&#xe015;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe016;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe017;" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
+<glyph unicode="&#xe018;" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe019;" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
+<glyph unicode="&#xe020;" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
+<glyph unicode="&#xe021;" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
+<glyph unicode="&#xe022;" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
+<glyph unicode="&#xe023;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe024;" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
+<glyph unicode="&#xe025;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe026;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
+<glyph unicode="&#xe027;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
+<glyph unicode="&#xe028;" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
+<glyph unicode="&#xe029;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
+<glyph unicode="&#xe030;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
+<glyph unicode="&#xe031;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
+<glyph unicode="&#xe032;" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe033;" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
+<glyph unicode="&#xe034;" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
+<glyph unicode="&#xe035;" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
+<glyph unicode="&#xe036;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
+<glyph unicode="&#xe037;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
+<glyph unicode="&#xe038;" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
+<glyph unicode="&#xe039;" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
+<glyph unicode="&#xe040;" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
+<glyph unicode="&#xe041;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
+<glyph unicode="&#xe042;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
+<glyph unicode="&#xe043;" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
+<glyph unicode="&#xe044;" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe045;" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
+<glyph unicode="&#xe046;" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
+<glyph unicode="&#xe047;" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
+<glyph unicode="&#xe048;" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
+<glyph unicode="&#xe049;" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
+<glyph unicode="&#xe050;" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
+<glyph unicode="&#xe051;" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
+<glyph unicode="&#xe052;" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe053;" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe054;" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe055;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe056;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe057;" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe058;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe059;" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
+<glyph unicode="&#xe060;" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
+<glyph unicode="&#xe062;" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
+<glyph unicode="&#xe063;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
+<glyph unicode="&#xe064;" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
+<glyph unicode="&#xe065;" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
+<glyph unicode="&#xe066;" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
+<glyph unicode="&#xe067;" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
+<glyph unicode="&#xe068;" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
+<glyph unicode="&#xe069;" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe070;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe071;" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
+<glyph unicode="&#xe072;" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
+<glyph unicode="&#xe073;" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe074;" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe075;" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
+<glyph unicode="&#xe076;" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe077;" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe078;" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe079;" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
+<glyph unicode="&#xe080;" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
+<glyph unicode="&#xe081;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
+<glyph unicode="&#xe082;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
+<glyph unicode="&#xe083;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
+<glyph unicode="&#xe084;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
+<glyph unicode="&#xe085;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe086;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe087;" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
+<glyph unicode="&#xe088;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe089;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe090;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
+<glyph unicode="&#xe091;" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
+<glyph unicode="&#xe092;" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
+<glyph unicode="&#xe093;" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
+<glyph unicode="&#xe094;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe095;" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
+<glyph unicode="&#xe096;" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
+<glyph unicode="&#xe097;" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
+<glyph unicode="&#xe101;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe102;" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
+<glyph unicode="&#xe103;" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
+<glyph unicode="&#xe104;" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
+<glyph unicode="&#xe105;" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
+<glyph unicode="&#xe106;" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
+<glyph unicode="&#xe107;" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
+<glyph unicode="&#xe108;" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
+<glyph unicode="&#xe109;" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
+<glyph unicode="&#xe110;" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
+<glyph unicode="&#xe111;" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
+<glyph unicode="&#xe112;" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
+<glyph unicode="&#xe113;" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
+<glyph unicode="&#xe114;" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
+<glyph unicode="&#xe115;" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe116;" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
+<glyph unicode="&#xe117;" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
+<glyph unicode="&#xe118;" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
+<glyph unicode="&#xe119;" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe120;" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
+<glyph unicode="&#xe121;" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
+<glyph unicode="&#xe122;" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
+<glyph unicode="&#xe123;" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
+<glyph unicode="&#xe124;" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
+<glyph unicode="&#xe125;" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe126;" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
+<glyph unicode="&#xe127;" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe128;" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe129;" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe130;" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
+<glyph unicode="&#xe131;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
+<glyph unicode="&#xe132;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
+<glyph unicode="&#xe133;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
+<glyph unicode="&#xe134;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe135;" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
+<glyph unicode="&#xe136;" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
+<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
+<glyph unicode="&#xe138;" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
+<glyph unicode="&#xe139;" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
+<glyph unicode="&#xe140;" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
+<glyph unicode="&#xe141;" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
+<glyph unicode="&#xe142;" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
+<glyph unicode="&#xe143;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
+<glyph unicode="&#xe144;" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
+<glyph unicode="&#xe145;" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
+<glyph unicode="&#xe146;" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
+<glyph unicode="&#xe148;" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
+<glyph unicode="&#xe149;" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
+<glyph unicode="&#xe150;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe151;" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
+<glyph unicode="&#xe152;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
+<glyph unicode="&#xe153;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
+<glyph unicode="&#xe154;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
+<glyph unicode="&#xe155;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
+<glyph unicode="&#xe156;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
+<glyph unicode="&#xe157;" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
+<glyph unicode="&#xe158;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
+<glyph unicode="&#xe159;" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
+<glyph unicode="&#xe160;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
+<glyph unicode="&#xe161;" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
+<glyph unicode="&#xe162;" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
+<glyph unicode="&#xe163;" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
+<glyph unicode="&#xe164;" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
+<glyph unicode="&#xe165;" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
+<glyph unicode="&#xe166;" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe167;" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe168;" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
+<glyph unicode="&#xe169;" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe170;" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe171;" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
+<glyph unicode="&#xe172;" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
+<glyph unicode="&#xe173;" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe174;" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
+<glyph unicode="&#xe175;" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe176;" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe177;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
+<glyph unicode="&#xe178;" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
+<glyph unicode="&#xe179;" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
+<glyph unicode="&#xe180;" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
+<glyph unicode="&#xe181;" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
+<glyph unicode="&#xe182;" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
+<glyph unicode="&#xe183;" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
+<glyph unicode="&#xe184;" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe185;" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
+<glyph unicode="&#xe186;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
+<glyph unicode="&#xe187;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
+<glyph unicode="&#xe188;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
+<glyph unicode="&#xe189;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
+<glyph unicode="&#xe190;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
+<glyph unicode="&#xe191;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
+<glyph unicode="&#xe192;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
+<glyph unicode="&#xe193;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
+<glyph unicode="&#xe194;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
+<glyph unicode="&#xe195;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
+<glyph unicode="&#xe197;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe198;" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
+<glyph unicode="&#xe199;" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
+<glyph unicode="&#xe200;" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
+<glyph unicode="&#xe201;" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
+<glyph unicode="&#xe202;" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
+<glyph unicode="&#xe203;" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
+<glyph unicode="&#xe204;" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
+<glyph unicode="&#xe205;" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
+<glyph unicode="&#xe206;" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
+<glyph unicode="&#xe209;" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
+<glyph unicode="&#xe210;" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe211;" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe212;" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe213;" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe214;" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe215;" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe216;" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
+<glyph unicode="&#xe218;" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
+<glyph unicode="&#xe219;" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
+<glyph unicode="&#xe221;" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe223;" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
+<glyph unicode="&#xe224;" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
+<glyph unicode="&#xe225;" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe226;" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
+<glyph unicode="&#xe227;" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
+<glyph unicode="&#xe230;" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
+<glyph unicode="&#xe231;" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
+<glyph unicode="&#xe232;" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
+<glyph unicode="&#xe233;" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
+<glyph unicode="&#xe234;" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
+<glyph unicode="&#xe235;" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
+<glyph unicode="&#xe236;" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe237;" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
+<glyph unicode="&#xe238;" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe239;" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
+<glyph unicode="&#xe240;" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
+<glyph unicode="&#xe241;" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
+<glyph unicode="&#xe242;" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe243;" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
+<glyph unicode="&#xe244;" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
+<glyph unicode="&#xe245;" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
+<glyph unicode="&#xe246;" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
+<glyph unicode="&#xe247;" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe248;" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
+<glyph unicode="&#xe249;" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe250;" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
+<glyph unicode="&#xe251;" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
+<glyph unicode="&#xe252;" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
+<glyph unicode="&#xe253;" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
+<glyph unicode="&#xe254;" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
+<glyph unicode="&#xe255;" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
+<glyph unicode="&#xe256;" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
+<glyph unicode="&#xe257;" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
+<glyph unicode="&#xe258;" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
+<glyph unicode="&#xe259;" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
+<glyph unicode="&#xe260;" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
+<glyph unicode="&#xf8ff;" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
+<glyph unicode="&#x1f511;" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
+<glyph unicode="&#x1f6aa;" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
+</font>
+</defs></svg> \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 000000000..1413fc609
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 000000000..9e612858f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2 b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2
new file mode 100644
index 000000000..64539b54c
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js
new file mode 100644
index 000000000..4139b6fc3
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js
@@ -0,0 +1,2306 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+if (typeof jQuery === 'undefined') {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+ 'use strict';
+ var version = $.fn.jquery.split(' ')[0].split('.')
+ if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
+ }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.2
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap')
+
+ var transEndEventNames = {
+ WebkitTransition : 'webkitTransitionEnd',
+ MozTransition : 'transitionend',
+ OTransition : 'oTransitionEnd otransitionend',
+ transition : 'transitionend'
+ }
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] }
+ }
+ }
+
+ return false // explicit for ie8 ( ._.)
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function (duration) {
+ var called = false
+ var $el = this
+ $(this).one('bsTransitionEnd', function () { called = true })
+ var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+ setTimeout(callback, duration)
+ return this
+ }
+
+ $(function () {
+ $.support.transition = transitionEnd()
+
+ if (!$.support.transition) return
+
+ $.event.special.bsTransitionEnd = {
+ bindType: $.support.transition.end,
+ delegateType: $.support.transition.end,
+ handle: function (e) {
+ if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+ }
+ }
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.2
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // ALERT CLASS DEFINITION
+ // ======================
+
+ var dismiss = '[data-dismiss="alert"]'
+ var Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.VERSION = '3.3.2'
+
+ Alert.TRANSITION_DURATION = 150
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = $(selector)
+
+ if (e) e.preventDefault()
+
+ if (!$parent.length) {
+ $parent = $this.closest('.alert')
+ }
+
+ $parent.trigger(e = $.Event('close.bs.alert'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ // detach from parent, fire event then clean up data
+ $parent.detach().trigger('closed.bs.alert').remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent
+ .one('bsTransitionEnd', removeElement)
+ .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+ removeElement()
+ }
+
+
+ // ALERT PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.alert')
+
+ if (!data) $this.data('bs.alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.alert
+
+ $.fn.alert = Plugin
+ $.fn.alert.Constructor = Alert
+
+
+ // ALERT NO CONFLICT
+ // =================
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ // ALERT DATA-API
+ // ==============
+
+ $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.2
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // BUTTON PUBLIC CLASS DEFINITION
+ // ==============================
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Button.DEFAULTS, options)
+ this.isLoading = false
+ }
+
+ Button.VERSION = '3.3.2'
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ var $el = this.$element
+ var val = $el.is('input') ? 'val' : 'html'
+ var data = $el.data()
+
+ state = state + 'Text'
+
+ if (data.resetText == null) $el.data('resetText', $el[val]())
+
+ // push to event loop to allow forms to submit
+ setTimeout($.proxy(function () {
+ $el[val](data[state] == null ? this.options[state] : data[state])
+
+ if (state == 'loadingText') {
+ this.isLoading = true
+ $el.addClass(d).attr(d, d)
+ } else if (this.isLoading) {
+ this.isLoading = false
+ $el.removeClass(d).removeAttr(d)
+ }
+ }, this), 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var changed = true
+ var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+ if ($parent.length) {
+ var $input = this.$element.find('input')
+ if ($input.prop('type') == 'radio') {
+ if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
+ else $parent.find('.active').removeClass('active')
+ }
+ if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
+ } else {
+ this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+ }
+
+ if (changed) this.$element.toggleClass('active')
+ }
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.button')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ var old = $.fn.button
+
+ $.fn.button = Plugin
+ $.fn.button.Constructor = Button
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(document)
+ .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ Plugin.call($btn, 'toggle')
+ e.preventDefault()
+ })
+ .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.2
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CAROUSEL CLASS DEFINITION
+ // =========================
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.paused =
+ this.sliding =
+ this.interval =
+ this.$active =
+ this.$items = null
+
+ this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+ this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+ .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+ .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+ }
+
+ Carousel.VERSION = '3.3.2'
+
+ Carousel.TRANSITION_DURATION = 600
+
+ Carousel.DEFAULTS = {
+ interval: 5000,
+ pause: 'hover',
+ wrap: true,
+ keyboard: true
+ }
+
+ Carousel.prototype.keydown = function (e) {
+ if (/input|textarea/i.test(e.target.tagName)) return
+ switch (e.which) {
+ case 37: this.prev(); break
+ case 39: this.next(); break
+ default: return
+ }
+
+ e.preventDefault()
+ }
+
+ Carousel.prototype.cycle = function (e) {
+ e || (this.paused = false)
+
+ this.interval && clearInterval(this.interval)
+
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+ return this
+ }
+
+ Carousel.prototype.getItemIndex = function (item) {
+ this.$items = item.parent().children('.item')
+ return this.$items.index(item || this.$active)
+ }
+
+ Carousel.prototype.getItemForDirection = function (direction, active) {
+ var activeIndex = this.getItemIndex(active)
+ var willWrap = (direction == 'prev' && activeIndex === 0)
+ || (direction == 'next' && activeIndex == (this.$items.length - 1))
+ if (willWrap && !this.options.wrap) return active
+ var delta = direction == 'prev' ? -1 : 1
+ var itemIndex = (activeIndex + delta) % this.$items.length
+ return this.$items.eq(itemIndex)
+ }
+
+ Carousel.prototype.to = function (pos) {
+ var that = this
+ var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+ if (activeIndex == pos) return this.pause().cycle()
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+ }
+
+ Carousel.prototype.pause = function (e) {
+ e || (this.paused = true)
+
+ if (this.$element.find('.next, .prev').length && $.support.transition) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+
+ this.interval = clearInterval(this.interval)
+
+ return this
+ }
+
+ Carousel.prototype.next = function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ Carousel.prototype.prev = function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ Carousel.prototype.slide = function (type, next) {
+ var $active = this.$element.find('.item.active')
+ var $next = next || this.getItemForDirection(type, $active)
+ var isCycling = this.interval
+ var direction = type == 'next' ? 'left' : 'right'
+ var that = this
+
+ if ($next.hasClass('active')) return (this.sliding = false)
+
+ var relatedTarget = $next[0]
+ var slideEvent = $.Event('slide.bs.carousel', {
+ relatedTarget: relatedTarget,
+ direction: direction
+ })
+ this.$element.trigger(slideEvent)
+ if (slideEvent.isDefaultPrevented()) return
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+ $nextIndicator && $nextIndicator.addClass('active')
+ }
+
+ var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ $active
+ .one('bsTransitionEnd', function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () {
+ that.$element.trigger(slidEvent)
+ }, 0)
+ })
+ .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+ } else {
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger(slidEvent)
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+
+ // CAROUSEL PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.carousel')
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ var action = typeof option == 'string' ? option : options.slide
+
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = Plugin
+ $.fn.carousel.Constructor = Carousel
+
+
+ // CAROUSEL NO CONFLICT
+ // ====================
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+
+ // CAROUSEL DATA-API
+ // =================
+
+ var clickHandler = function (e) {
+ var href
+ var $this = $(this)
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+ if (!$target.hasClass('carousel')) return
+ var options = $.extend({}, $target.data(), $this.data())
+ var slideIndex = $this.attr('data-slide-to')
+ if (slideIndex) options.interval = false
+
+ Plugin.call($target, options)
+
+ if (slideIndex) {
+ $target.data('bs.carousel').to(slideIndex)
+ }
+
+ e.preventDefault()
+ }
+
+ $(document)
+ .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+ .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+ $(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ Plugin.call($carousel, $carousel.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.2
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Collapse.DEFAULTS, options)
+ this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]')
+ this.transitioning = null
+
+ if (this.options.parent) {
+ this.$parent = this.getParent()
+ } else {
+ this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+ }
+
+ if (this.options.toggle) this.toggle()
+ }
+
+ Collapse.VERSION = '3.3.2'
+
+ Collapse.TRANSITION_DURATION = 350
+
+ Collapse.DEFAULTS = {
+ toggle: true,
+ trigger: '[data-toggle="collapse"]'
+ }
+
+ Collapse.prototype.dimension = function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ Collapse.prototype.show = function () {
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ var activesData
+ var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+ if (actives && actives.length) {
+ activesData = actives.data('bs.collapse')
+ if (activesData && activesData.transitioning) return
+ }
+
+ var startEvent = $.Event('show.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ if (actives && actives.length) {
+ Plugin.call(actives, 'hide')
+ activesData || actives.data('bs.collapse', null)
+ }
+
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')[dimension](0)
+ .attr('aria-expanded', true)
+
+ this.$trigger
+ .removeClass('collapsed')
+ .attr('aria-expanded', true)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse in')[dimension]('')
+ this.transitioning = 0
+ this.$element
+ .trigger('shown.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+ this.$element
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+ }
+
+ Collapse.prototype.hide = function () {
+ if (this.transitioning || !this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('hide.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var dimension = this.dimension()
+
+ this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse in')
+ .attr('aria-expanded', false)
+
+ this.$trigger
+ .addClass('collapsed')
+ .attr('aria-expanded', false)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.transitioning = 0
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse')
+ .trigger('hidden.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ this.$element
+ [dimension](0)
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+ }
+
+ Collapse.prototype.toggle = function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ Collapse.prototype.getParent = function () {
+ return $(this.options.parent)
+ .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+ .each($.proxy(function (i, element) {
+ var $element = $(element)
+ this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+ }, this))
+ .end()
+ }
+
+ Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+ var isOpen = $element.hasClass('in')
+
+ $element.attr('aria-expanded', isOpen)
+ $trigger
+ .toggleClass('collapsed', !isOpen)
+ .attr('aria-expanded', isOpen)
+ }
+
+ function getTargetFromTrigger($trigger) {
+ var href
+ var target = $trigger.attr('data-target')
+ || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+ return $(target)
+ }
+
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.collapse')
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data && options.toggle && option == 'show') options.toggle = false
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = Plugin
+ $.fn.collapse.Constructor = Collapse
+
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+ var $this = $(this)
+
+ if (!$this.attr('data-target')) e.preventDefault()
+
+ var $target = getTargetFromTrigger($this)
+ var data = $target.data('bs.collapse')
+ var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })
+
+ Plugin.call($target, option)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.2
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.3.2'
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this
+ .trigger('focus')
+ .attr('aria-expanded', 'true')
+
+ $parent
+ .toggleClass('open')
+ .trigger('shown.bs.dropdown', relatedTarget)
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.divider):visible a'
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index(e.target)
+
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.2
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // MODAL CLASS DEFINITION
+ // ======================
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$body = $(document.body)
+ this.$element = $(element)
+ this.$backdrop =
+ this.isShown = null
+ this.scrollbarWidth = 0
+
+ if (this.options.remote) {
+ this.$element
+ .find('.modal-content')
+ .load(this.options.remote, $.proxy(function () {
+ this.$element.trigger('loaded.bs.modal')
+ }, this))
+ }
+ }
+
+ Modal.VERSION = '3.3.2'
+
+ Modal.TRANSITION_DURATION = 300
+ Modal.BACKDROP_TRANSITION_DURATION = 150
+
+ Modal.DEFAULTS = {
+ backdrop: true,
+ keyboard: true,
+ show: true
+ }
+
+ Modal.prototype.toggle = function (_relatedTarget) {
+ return this.isShown ? this.hide() : this.show(_relatedTarget)
+ }
+
+ Modal.prototype.show = function (_relatedTarget) {
+ var that = this
+ var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.checkScrollbar()
+ this.setScrollbar()
+ this.$body.addClass('modal-open')
+
+ this.escape()
+ this.resize()
+
+ this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(that.$body) // don't move modals dom position
+ }
+
+ that.$element
+ .show()
+ .scrollTop(0)
+
+ if (that.options.backdrop) that.adjustBackdrop()
+ that.adjustDialog()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+ transition ?
+ that.$element.find('.modal-dialog') // wait for modal to slide in
+ .one('bsTransitionEnd', function () {
+ that.$element.trigger('focus').trigger(e)
+ })
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ that.$element.trigger('focus').trigger(e)
+ })
+ }
+
+ Modal.prototype.hide = function (e) {
+ if (e) e.preventDefault()
+
+ e = $.Event('hide.bs.modal')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+ this.resize()
+
+ $(document).off('focusin.bs.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+ .off('click.dismiss.bs.modal')
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$element
+ .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ this.hideModal()
+ }
+
+ Modal.prototype.enforceFocus = function () {
+ $(document)
+ .off('focusin.bs.modal') // guard against infinite focus loop
+ .on('focusin.bs.modal', $.proxy(function (e) {
+ if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+ this.$element.trigger('focus')
+ }
+ }, this))
+ }
+
+ Modal.prototype.escape = function () {
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+ e.which == 27 && this.hide()
+ }, this))
+ } else if (!this.isShown) {
+ this.$element.off('keydown.dismiss.bs.modal')
+ }
+ }
+
+ Modal.prototype.resize = function () {
+ if (this.isShown) {
+ $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+ } else {
+ $(window).off('resize.bs.modal')
+ }
+ }
+
+ Modal.prototype.hideModal = function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.$body.removeClass('modal-open')
+ that.resetAdjustments()
+ that.resetScrollbar()
+ that.$element.trigger('hidden.bs.modal')
+ })
+ }
+
+ Modal.prototype.removeBackdrop = function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ Modal.prototype.backdrop = function (callback) {
+ var that = this
+ var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .prependTo(this.$element)
+ .on('click.dismiss.bs.modal', $.proxy(function (e) {
+ if (e.target !== e.currentTarget) return
+ this.options.backdrop == 'static'
+ ? this.$element[0].focus.call(this.$element[0])
+ : this.hide.call(this)
+ }, this))
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop
+ .one('bsTransitionEnd', callback)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ var callbackRemove = function () {
+ that.removeBackdrop()
+ callback && callback()
+ }
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$backdrop
+ .one('bsTransitionEnd', callbackRemove)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callbackRemove()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+
+ // these following methods are used to handle overflowing modals
+
+ Modal.prototype.handleUpdate = function () {
+ if (this.options.backdrop) this.adjustBackdrop()
+ this.adjustDialog()
+ }
+
+ Modal.prototype.adjustBackdrop = function () {
+ this.$backdrop
+ .css('height', 0)
+ .css('height', this.$element[0].scrollHeight)
+ }
+
+ Modal.prototype.adjustDialog = function () {
+ var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+ this.$element.css({
+ paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+ paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+ })
+ }
+
+ Modal.prototype.resetAdjustments = function () {
+ this.$element.css({
+ paddingLeft: '',
+ paddingRight: ''
+ })
+ }
+
+ Modal.prototype.checkScrollbar = function () {
+ this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight
+ this.scrollbarWidth = this.measureScrollbar()
+ }
+
+ Modal.prototype.setScrollbar = function () {
+ var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+ if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+ }
+
+ Modal.prototype.resetScrollbar = function () {
+ this.$body.css('padding-right', '')
+ }
+
+ Modal.prototype.measureScrollbar = function () { // thx walsh
+ var scrollDiv = document.createElement('div')
+ scrollDiv.className = 'modal-scrollbar-measure'
+ this.$body.append(scrollDiv)
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+ this.$body[0].removeChild(scrollDiv)
+ return scrollbarWidth
+ }
+
+
+ // MODAL PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option, _relatedTarget) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.modal')
+ var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option](_relatedTarget)
+ else if (options.show) data.show(_relatedTarget)
+ })
+ }
+
+ var old = $.fn.modal
+
+ $.fn.modal = Plugin
+ $.fn.modal.Constructor = Modal
+
+
+ // MODAL NO CONFLICT
+ // =================
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ // MODAL DATA-API
+ // ==============
+
+ $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ var href = $this.attr('href')
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+ var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ if ($this.is('a')) e.preventDefault()
+
+ $target.one('show.bs.modal', function (showEvent) {
+ if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+ $target.one('hidden.bs.modal', function () {
+ $this.is(':visible') && $this.trigger('focus')
+ })
+ })
+ Plugin.call($target, option, this)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.2
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TOOLTIP PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Tooltip = function (element, options) {
+ this.type =
+ this.options =
+ this.enabled =
+ this.timeout =
+ this.hoverState =
+ this.$element = null
+
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.VERSION = '3.3.2'
+
+ Tooltip.TRANSITION_DURATION = 150
+
+ Tooltip.DEFAULTS = {
+ animation: true,
+ placement: 'top',
+ selector: false,
+ template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ trigger: 'hover focus',
+ title: '',
+ delay: 0,
+ html: false,
+ container: false,
+ viewport: {
+ selector: 'body',
+ padding: 0
+ }
+ }
+
+ Tooltip.prototype.init = function (type, element, options) {
+ this.enabled = true
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
+
+ var triggers = this.options.trigger.split(' ')
+
+ for (var i = triggers.length; i--;) {
+ var trigger = triggers[i]
+
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ Tooltip.prototype.getDefaults = function () {
+ return Tooltip.DEFAULTS
+ }
+
+ Tooltip.prototype.getOptions = function (options) {
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay,
+ hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ Tooltip.prototype.getDelegateOptions = function () {
+ var options = {}
+ var defaults = this.getDefaults()
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ })
+
+ return options
+ }
+
+ Tooltip.prototype.enter = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (self && self.$tip && self.$tip.is(':visible')) {
+ self.hoverState = 'in'
+ return
+ }
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'in'
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ Tooltip.prototype.leave = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'out'
+
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ Tooltip.prototype.show = function () {
+ var e = $.Event('show.bs.' + this.type)
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+
+ var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+ if (e.isDefaultPrevented() || !inDom) return
+ var that = this
+
+ var $tip = this.tip()
+
+ var tipId = this.getUID(this.type)
+
+ this.setContent()
+ $tip.attr('id', tipId)
+ this.$element.attr('aria-describedby', tipId)
+
+ if (this.options.animation) $tip.addClass('fade')
+
+ var placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ var autoToken = /\s?auto?\s?/i
+ var autoPlace = autoToken.test(placement)
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .addClass(placement)
+ .data('bs.' + this.type, this)
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+ var pos = this.getPosition()
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (autoPlace) {
+ var orgPlacement = placement
+ var $container = this.options.container ? $(this.options.container) : this.$element.parent()
+ var containerDim = this.getPosition($container)
+
+ placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
+ placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
+ placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
+ placement
+
+ $tip
+ .removeClass(orgPlacement)
+ .addClass(placement)
+ }
+
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+ this.applyPlacement(calculatedOffset, placement)
+
+ var complete = function () {
+ var prevHoverState = that.hoverState
+ that.$element.trigger('shown.bs.' + that.type)
+ that.hoverState = null
+
+ if (prevHoverState == 'out') that.leave(that)
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+ }
+ }
+
+ Tooltip.prototype.applyPlacement = function (offset, placement) {
+ var $tip = this.tip()
+ var width = $tip[0].offsetWidth
+ var height = $tip[0].offsetHeight
+
+ // manually read margins because getBoundingClientRect includes difference
+ var marginTop = parseInt($tip.css('margin-top'), 10)
+ var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+ // we must check for NaN for ie 8/9
+ if (isNaN(marginTop)) marginTop = 0
+ if (isNaN(marginLeft)) marginLeft = 0
+
+ offset.top = offset.top + marginTop
+ offset.left = offset.left + marginLeft
+
+ // $.fn.offset doesn't round pixel values
+ // so we use setOffset directly with our own function B-0
+ $.offset.setOffset($tip[0], $.extend({
+ using: function (props) {
+ $tip.css({
+ top: Math.round(props.top),
+ left: Math.round(props.left)
+ })
+ }
+ }, offset), 0)
+
+ $tip.addClass('in')
+
+ // check to see if placing tip in new offset caused the tip to resize itself
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ offset.top = offset.top + height - actualHeight
+ }
+
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+ if (delta.left) offset.left += delta.left
+ else offset.top += delta.top
+
+ var isVertical = /top|bottom/.test(placement)
+ var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+ var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+ $tip.offset(offset)
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+ }
+
+ Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
+ this.arrow()
+ .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+ .css(isHorizontal ? 'top' : 'left', '')
+ }
+
+ Tooltip.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ Tooltip.prototype.hide = function (callback) {
+ var that = this
+ var $tip = this.tip()
+ var e = $.Event('hide.bs.' + this.type)
+
+ function complete() {
+ if (that.hoverState != 'in') $tip.detach()
+ that.$element
+ .removeAttr('aria-describedby')
+ .trigger('hidden.bs.' + that.type)
+ callback && callback()
+ }
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+
+ this.hoverState = null
+
+ return this
+ }
+
+ Tooltip.prototype.fixTitle = function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ Tooltip.prototype.hasContent = function () {
+ return this.getTitle()
+ }
+
+ Tooltip.prototype.getPosition = function ($element) {
+ $element = $element || this.$element
+
+ var el = $element[0]
+ var isBody = el.tagName == 'BODY'
+
+ var elRect = el.getBoundingClientRect()
+ if (elRect.width == null) {
+ // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+ elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+ }
+ var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
+ var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+ var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+ return $.extend({}, elRect, scroll, outerDims, elOffset)
+ }
+
+ Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+ }
+
+ Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+ var delta = { top: 0, left: 0 }
+ if (!this.$viewport) return delta
+
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+ var viewportDimensions = this.getPosition(this.$viewport)
+
+ if (/right|left/.test(placement)) {
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
+ delta.top = viewportDimensions.top - topEdgeOffset
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+ }
+ } else {
+ var leftEdgeOffset = pos.left - viewportPadding
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+ delta.left = viewportDimensions.left - leftEdgeOffset
+ } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+ }
+ }
+
+ return delta
+ }
+
+ Tooltip.prototype.getTitle = function () {
+ var title
+ var $e = this.$element
+ var o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ Tooltip.prototype.getUID = function (prefix) {
+ do prefix += ~~(Math.random() * 1000000)
+ while (document.getElementById(prefix))
+ return prefix
+ }
+
+ Tooltip.prototype.tip = function () {
+ return (this.$tip = this.$tip || $(this.options.template))
+ }
+
+ Tooltip.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+ }
+
+ Tooltip.prototype.enable = function () {
+ this.enabled = true
+ }
+
+ Tooltip.prototype.disable = function () {
+ this.enabled = false
+ }
+
+ Tooltip.prototype.toggleEnabled = function () {
+ this.enabled = !this.enabled
+ }
+
+ Tooltip.prototype.toggle = function (e) {
+ var self = this
+ if (e) {
+ self = $(e.currentTarget).data('bs.' + this.type)
+ if (!self) {
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+ $(e.currentTarget).data('bs.' + this.type, self)
+ }
+ }
+
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+ }
+
+ Tooltip.prototype.destroy = function () {
+ var that = this
+ clearTimeout(this.timeout)
+ this.hide(function () {
+ that.$element.off('.' + that.type).removeData('bs.' + that.type)
+ })
+ }
+
+
+ // TOOLTIP PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tooltip')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = Plugin
+ $.fn.tooltip.Constructor = Tooltip
+
+
+ // TOOLTIP NO CONFLICT
+ // ===================
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.2
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.VERSION = '3.3.2'
+
+ Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right',
+ trigger: 'click',
+ content: '',
+ template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+ this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+ ](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.popover
+
+ $.fn.popover = Plugin
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.2
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // SCROLLSPY CLASS DEFINITION
+ // ==========================
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+
+ this.$body = $('body')
+ this.$scrollElement = $(element).is('body') ? $(window) : $(element)
+ this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
+ this.selector = (this.options.target || '') + ' .nav li > a'
+ this.offsets = []
+ this.targets = []
+ this.activeTarget = null
+ this.scrollHeight = 0
+
+ this.$scrollElement.on('scroll.bs.scrollspy', process)
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.VERSION = '3.3.2'
+
+ ScrollSpy.DEFAULTS = {
+ offset: 10
+ }
+
+ ScrollSpy.prototype.getScrollHeight = function () {
+ return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+ }
+
+ ScrollSpy.prototype.refresh = function () {
+ var offsetMethod = 'offset'
+ var offsetBase = 0
+
+ if (!$.isWindow(this.$scrollElement[0])) {
+ offsetMethod = 'position'
+ offsetBase = this.$scrollElement.scrollTop()
+ }
+
+ this.offsets = []
+ this.targets = []
+ this.scrollHeight = this.getScrollHeight()
+
+ var self = this
+
+ this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ var href = $el.data('target') || $el.attr('href')
+ var $href = /^#./.test(href) && $(href)
+
+ return ($href
+ && $href.length
+ && $href.is(':visible')
+ && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ ScrollSpy.prototype.process = function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ var scrollHeight = this.getScrollHeight()
+ var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
+ var offsets = this.offsets
+ var targets = this.targets
+ var activeTarget = this.activeTarget
+ var i
+
+ if (this.scrollHeight != scrollHeight) {
+ this.refresh()
+ }
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+ }
+
+ if (activeTarget && scrollTop < offsets[0]) {
+ this.activeTarget = null
+ return this.clear()
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate(targets[i])
+ }
+ }
+
+ ScrollSpy.prototype.activate = function (target) {
+ this.activeTarget = target
+
+ this.clear()
+
+ var selector = this.selector +
+ '[data-target="' + target + '"],' +
+ this.selector + '[href="' + target + '"]'
+
+ var active = $(selector)
+ .parents('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active
+ .closest('li.dropdown')
+ .addClass('active')
+ }
+
+ active.trigger('activate.bs.scrollspy')
+ }
+
+ ScrollSpy.prototype.clear = function () {
+ $(this.selector)
+ .parentsUntil(this.options.target, '.active')
+ .removeClass('active')
+ }
+
+
+ // SCROLLSPY PLUGIN DEFINITION
+ // ===========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.scrollspy')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = Plugin
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+
+ // SCROLLSPY NO CONFLICT
+ // =====================
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ // SCROLLSPY DATA-API
+ // ==================
+
+ $(window).on('load.bs.scrollspy.data-api', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ Plugin.call($spy, $spy.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.2
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.VERSION = '3.3.2'
+
+ Tab.TRANSITION_DURATION = 150
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.data('target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var $previous = $ul.find('.active:last a')
+ var hideEvent = $.Event('hide.bs.tab', {
+ relatedTarget: $this[0]
+ })
+ var showEvent = $.Event('show.bs.tab', {
+ relatedTarget: $previous[0]
+ })
+
+ $previous.trigger(hideEvent)
+ $this.trigger(showEvent)
+
+ if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+ var $target = $(selector)
+
+ this.activate($this.closest('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $previous.trigger({
+ type: 'hidden.bs.tab',
+ relatedTarget: $this[0]
+ })
+ $this.trigger({
+ type: 'shown.bs.tab',
+ relatedTarget: $previous[0]
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', false)
+
+ element
+ .addClass('active')
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu')) {
+ element
+ .closest('li.dropdown')
+ .addClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+ }
+
+ callback && callback()
+ }
+
+ $active.length && transition ?
+ $active
+ .one('bsTransitionEnd', next)
+ .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tab
+
+ $.fn.tab = Plugin
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ var clickHandler = function (e) {
+ e.preventDefault()
+ Plugin.call($(this), 'show')
+ }
+
+ $(document)
+ .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+ .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.2
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // AFFIX CLASS DEFINITION
+ // ======================
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, Affix.DEFAULTS, options)
+
+ this.$target = $(this.options.target)
+ .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
+
+ this.$element = $(element)
+ this.affixed =
+ this.unpin =
+ this.pinnedOffset = null
+
+ this.checkPosition()
+ }
+
+ Affix.VERSION = '3.3.2'
+
+ Affix.RESET = 'affix affix-top affix-bottom'
+
+ Affix.DEFAULTS = {
+ offset: 0,
+ target: window
+ }
+
+ Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ var targetHeight = this.$target.height()
+
+ if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+ if (this.affixed == 'bottom') {
+ if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+ return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+ }
+
+ var initializing = this.affixed == null
+ var colliderTop = initializing ? scrollTop : position.top
+ var colliderHeight = initializing ? targetHeight : height
+
+ if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+ if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+ return false
+ }
+
+ Affix.prototype.getPinnedOffset = function () {
+ if (this.pinnedOffset) return this.pinnedOffset
+ this.$element.removeClass(Affix.RESET).addClass('affix')
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ return (this.pinnedOffset = position.top - scrollTop)
+ }
+
+ Affix.prototype.checkPositionWithEventLoop = function () {
+ setTimeout($.proxy(this.checkPosition, this), 1)
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var height = this.$element.height()
+ var offset = this.options.offset
+ var offsetTop = offset.top
+ var offsetBottom = offset.bottom
+ var scrollHeight = $('body').height()
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+ var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+ if (this.affixed != affix) {
+ if (this.unpin != null) this.$element.css('top', '')
+
+ var affixType = 'affix' + (affix ? '-' + affix : '')
+ var e = $.Event(affixType + '.bs.affix')
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+ this.$element
+ .removeClass(Affix.RESET)
+ .addClass(affixType)
+ .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+ }
+
+ if (affix == 'bottom') {
+ this.$element.offset({
+ top: scrollHeight - height - offsetBottom
+ })
+ }
+ }
+
+
+ // AFFIX PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.affix')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.affix
+
+ $.fn.affix = Plugin
+ $.fn.affix.Constructor = Affix
+
+
+ // AFFIX NO CONFLICT
+ // =================
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ // AFFIX DATA-API
+ // ==============
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ var data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+ if (data.offsetTop != null) data.offset.top = data.offsetTop
+
+ Plugin.call($spy, data)
+ })
+ })
+
+}(jQuery);
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js
new file mode 100644
index 000000000..c6d36920b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$backdrop=this.isShown=null,this.scrollbarWidth=0,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.options.backdrop&&d.adjustBackdrop(),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$element.find(".modal-dialog").one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').prependTo(this.$element).on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.options.backdrop&&this.adjustBackdrop(),this.adjustDialog()},c.prototype.adjustBackdrop=function(){this.$backdrop.css("height",0).css("height",this.$element[0].scrollHeight)},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){this.bodyIsOverflowing=document.body.scrollHeight>document.documentElement.clientHeight,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=this.tip(),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.2",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.2",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()
+}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.2",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a("body").height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js
new file mode 100644
index 000000000..bf6aa8060
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js
@@ -0,0 +1,13 @@
+// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
+require('../../js/transition.js')
+require('../../js/alert.js')
+require('../../js/button.js')
+require('../../js/carousel.js')
+require('../../js/collapse.js')
+require('../../js/dropdown.js')
+require('../../js/modal.js')
+require('../../js/tooltip.js')
+require('../../js/popover.js')
+require('../../js/scrollspy.js')
+require('../../js/tab.js')
+require('../../js/affix.js') \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+name: MooDialog.Confirm
+description: Creates an Confirm Dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
+...
+*/
+
+
+MooDialog.Confirm = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ cancelText: 'Cancel',
+ focus: true,
+ textPClass: 'MooDialogConfirm'
+ },
+
+ initialize: function(msg, fn, fn1, options){
+ this.parent(options);
+ var emptyFn = function(){},
+ self = this;
+
+ var buttons = [
+ {fn: fn || emptyFn, txt: this.options.okText},
+ {fn: fn1 || emptyFn, txt: this.options.cancelText}
+ ].map(function(button){
+ return new Element('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: button.txt
+ });
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(buttons)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if(this.options.focus) this.addEvent('show', function(){
+ buttons[1].focus();
+ });
+
+ }
+});
+
+
+Element.implement({
+
+ confirmLinkClick: function(msg, options){
+ this.addEvent('click', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ location.href = this.get('href');
+ }.bind(this), null, options)
+ });
+ return this;
+ },
+
+ confirmFormSubmit: function(msg, options){
+ this.addEvent('submit', function(e){
+ e.stop();
+ new MooDialog.Confirm(msg, function(){
+ this.submit();
+ }.bind(this), null, options)
+ }.bind(this));
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+name: MooDialog.Fx
+description: Overwrite the default events so the Dialogs are using Fx on open and close
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Fx.Tween, Overlay]
+provides: MooDialog.Fx
+...
+*/
+
+
+MooDialog.implement('options', {
+
+ duration: 400,
+ closeOnOverlayClick: true,
+
+ onInitialize: function(wrapper){
+ this.fx = new Fx.Tween(wrapper, {
+ property: 'opacity',
+ duration: this.options.duration
+ }).set(0);
+ this.overlay = new Overlay(this.options.inject, {
+ duration: this.options.duration
+ });
+ if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ this.fx.start(1).chain(function(){
+ this.fireEvent('show');
+ }.bind(this));
+ },
+
+ onBeforeClose: function(wrapper){
+ this.overlay.close();
+ this.fx.start(0).chain(function(){
+ this.fireEvent('hide');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+name: Overlay
+
+authors:
+ - David Walsh (http://davidwalsh.name)
+
+license:
+ - MIT-style license
+
+requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
+
+provides:
+ - Overlay
+...
+*/
+
+var Overlay = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ id: 'overlay',
+ color: '#000',
+ duration: 500,
+ opacity: 0.5,
+ zIndex: 5000/*,
+ onClick: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ initialize: function(container, options){
+ this.setOptions(options);
+ this.container = document.id(container);
+
+ this.bound = {
+ 'window': {
+ resize: this.resize.bind(this),
+ scroll: this.scroll.bind(this)
+ },
+ overlayClick: this.overlayClick.bind(this),
+ tweenStart: this.tweenStart.bind(this),
+ tweenComplete: this.tweenComplete.bind(this)
+ };
+
+ this.build().attach();
+ },
+
+ build: function(){
+ this.overlay = new Element('div', {
+ id: this.options.id,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ return this;
+ }.protect(),
+
+ attach: function(){
+ window.addEvents(this.bound.window);
+ this.overlay.addEvent('click', this.bound.overlayClick);
+ this.tween.addEvents({
+ onStart: this.bound.tweenStart,
+ onComplete: this.bound.tweenComplete
+ });
+ return this;
+ },
+
+ detach: function(){
+ var args = Array.prototype.slice.call(arguments);
+ args.each(function(item){
+ if(item == 'window') window.removeEvents(this.bound.window);
+ if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
+ }, this);
+ return this;
+ },
+
+ overlayClick: function(){
+ this.fireEvent('click');
+ return this;
+ },
+
+ tweenStart: function(){
+ this.overlay.setStyles({
+ width: '100%',
+ height: this.container.getScrollSize().y,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ return this;
+ },
+
+ resize: function(){
+ this.fireEvent('resize');
+ this.overlay.setStyle('height', this.container.getScrollSize().y);
+ return this;
+ },
+
+ scroll: function(){
+ this.fireEvent('scroll');
+ if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ z-index: 50000;
+
+ background: #eef5f8;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .content {
+ height: 100px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios'){
+ UA[1] = 'chrome';
+ }
+
+ platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.name == 'ie'){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ window = this.Window = document = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e){
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
+ return node.hasAttribute(attribute);
+ } : function(node, attribute){
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector){
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll){
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e){
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError){}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ }
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return classes(this.className).contains(className);
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function(){
+
+var _keys = {};
+var normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1, message: 2 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e){}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',
+ withCredentials: false,*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (!ready) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ compensateScroll: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+ this.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(offsetParent);
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ this.mouse.start = null;
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+/*
+---
+
+script: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+/*
+---
+
+script: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(function(){
+
+var Swiff = this.Swiff = new Class({
+
+ Implements: Options,
+
+ options: {
+ id: null,
+ height: 1,
+ width: 1,
+ container: null,
+ properties: {},
+ params: {
+ quality: 'high',
+ allowScriptAccess: 'always',
+ wMode: 'window',
+ swLiveConnect: true
+ },
+ callBacks: {},
+ vars: {}
+ },
+
+ toElement: function(){
+ return this.object;
+ },
+
+ initialize: function(path, options){
+ this.instance = 'Swiff_' + String.uniqueID();
+
+ this.setOptions(options);
+ options = this.options;
+ var id = this.id = options.id || this.instance;
+ var container = document.id(options.container);
+
+ Swiff.CallBacks[this.instance] = {};
+
+ var params = options.params, vars = options.vars, callBacks = options.callBacks;
+ var properties = Object.append({height: options.height, width: options.width}, options.properties);
+
+ var self = this;
+
+ for (var callBack in callBacks){
+ Swiff.CallBacks[this.instance][callBack] = (function(option){
+ return function(){
+ return option.apply(self.object, arguments);
+ };
+ })(callBacks[callBack]);
+ vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+ }
+
+ params.flashVars = Object.toQueryString(vars);
+ if ('ActiveXObject' in window){
+ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+ params.movie = path;
+ } else {
+ properties.type = 'application/x-shockwave-flash';
+ }
+ properties.data = path;
+
+ var build = '<object id="' + id + '"';
+ for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+ build += '>';
+ for (var param in params){
+ if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+ }
+ build += '</object>';
+ this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+ },
+
+ replaces: function(element){
+ element = document.id(element, true);
+ element.parentNode.replaceChild(this.toElement(), element);
+ return this;
+ },
+
+ inject: function(element){
+ document.id(element, true).appendChild(this.toElement());
+ return this;
+ },
+
+ remote: function(){
+ return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
+ }
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+ var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+ return eval(rs);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/Next/tml/admin.html b/pyload/webui/themes/Next/tml/admin.html
new file mode 100644
index 000000000..5c71cbac5
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/admin.html
@@ -0,0 +1,100 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/admin.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+ <div class="btn-group">
+ <a href="#" id="quit-pyload" class="btn btn-default"><span class="glyphicon glyphicon-off"></span> {{_("Quit pyLoad")}}</a>
+ <a href="#" id="restart-pyload" class="btn btn-default"><span class="glyphicon glyphicon-repeat"></span> {{_("Restart pyLoad")}}</a>
+</div>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+<br>
+<br>
+ <form action="" method="POST" >
+ <table class="settable table" style="width:50%;">
+ <thead>
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password btn btn-default btn-xs" href="#" id="change_pw|{{name}}"><span class="glyphicon glyphicon-pencil"></span> {{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} checked="True" {% endif %}></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="btn btn-primary" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" style="z-index: 2">
+ <form id="password_form" class="from-group" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h3>{{ _("Change Password") }}</h3>
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+
+<div class="form-group">
+ <label for="user_login">{{ _("User") }}</label>
+ <input class="form-control" id="user_login" name="user_login" type="text"/>
+ <p class="help-block">{{ _("Your username.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_current_password">{{ _("Current password") }}</label>
+ <input class="form-control" id="login_current_password" name="login_current_password" type="password"/>
+ <p class="help-block">{{ _("The password for this account.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_new_password">{{ _("New password") }}</label>
+ <input class="form-control" id="login_new_password" name="login_new_password" type="password"/>
+ <p class="help-block">{{ _("The new password.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_new_password2">{{ _("New password (repeat)") }}</label>
+ <input class="form-control" id="login_new_password2" name="login_new_password2" type="password" />
+ <p class="help-block">{{ _("Please repeat the new password.") }}</p>
+ </div>
+
+
+
+ <button class="btn btn-primary" id="login_password_button" type="submit" style="float: right">{{ _("Submit") }}</button>
+ <button class="btn btn-default" id="login_password_reset" style="margin-right: 5px; float: right" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/base.html b/pyload/webui/themes/Next/tml/base.html
new file mode 100644
index 000000000..81c1f3008
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/base.html
@@ -0,0 +1,199 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+<link rel="stylesheet" href="/lib/Bootstrap/css/bootstrap.css">
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/base.js"></script>
+
+
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+
+
+
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <nav class="navbar navbar-default">
+ <div class="container-fluid">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" style="height:30px;"/></a>
+ </div>
+
+ <a href="/"></a>
+
+ <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+ <ul class="nav navbar-nav">
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><span class="glyphicon glyphicon-home"></span> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><span class="glyphicon glyphicon-tasks"></span> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""> <span class="glyphicon glyphicon-download"></span> {{_("Downloads")}}</a>
+ </li>
+{# <li {{ selected('filemanager') }}>#}
+{# <a href="/filemanager/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("FileManager")}}</a>#}
+{# </li>#}
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-list"></span> {{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-wrench"></span> {{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+
+ </ul>
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-user"></span> {{user.name}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow"><span class="glyphicon glyphicon-cog"></span></a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-info-sign"></span></a></li>
+
+ </ul>
+ </div><!-- /.navbar-collapse -->
+ </div><!-- /.container-fluid -->
+
+ </div>
+</nav>
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;">
+ <button id="action_play" class="btn btn-default" href="#"><span class="glyphicon glyphicon-play"></span>&nbsp;</button>
+ <button id="action_stop" type="button" class="btn btn-default"><span class="glyphicon glyphicon-stop"></span>&nbsp;</button>
+ <button id="action_cancel" type="button" class="btn btn-default"><span class="glyphicon glyphicon-remove"></span>&nbsp;</button>
+ <button id="action_add" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span>&nbsp;</button>
+</div>
+
+
+{% endif %}
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<button id="action_add" type="button" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-barcode"></span><span> {{_("Captcha waiting")}}</span></button>
+</span>
+
+
+{% if perms.LIST %}
+
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-right:10px; float:right;">
+ <button id="action_play" class="btn btn-default"><span >{{_("Download:")}}</span>&nbsp;<span class="label label-{% if status.download %}success{% else %}danger{% endif %}">{% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button>
+ <button id="action_stop" type="button" class="btn btn-default"><span>{{_("Reconnect:")}}</span>&nbsp;<span class="label label-{% if status.reconnect %}success{% else %}danger{% endif %}">{% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button>
+ <button id="action_cancel" type="button" class="btn btn-default"><span class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></span></button>
+ <button id="action_add" type="button" class="btn btn-default"><span class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></span></button>
+</div>
+
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" style="width: 98%; margin-left:10px; margin-rigth:10px;" lang="en" dir="ltr">
+
+<h3>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h3>
+
+{% block statusbar %}
+{% endblock %}
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot" style="with: 98%; margin-left: 10px; margin-right:10px">&copy; 2008-2015 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div style="display: none;">
+ {% include "/tml/window.html" %}
+ {% include "/tml/captcha.html" %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+</body>
+</html>
diff --git a/pyload/webui/themes/Next/tml/captcha.html b/pyload/webui/themes/Next/tml/captcha.html
new file mode 100644
index 000000000..2b449aa86
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/captcha.html
@@ -0,0 +1,40 @@
+<!-- Captcha box -->
+<div id="cap_box" >
+
+ <form id="cap_form" class="form-group" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h3>{{_("Captcha reading")}}</h3>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <div class="form-group">
+ <label>{{_("Captcha")}}</label>
+ <span ></br>
+ <img id="cap_textual_img" style="border: 1px solid #bbb; padding: 3px 3px 3px 3px;" src="">
+ </span>
+ </div>
+ <div class="form-group">
+ <label>{{_("Text")}}</label>
+ <input class="form-control" id="cap_result" name="cap_result" type="text" size="20" />
+ <p class="small">{{_("Input the text on the captcha.")}}</p>
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button class="btn btn-primary" id="cap_submit" type="submit" style="float: right; margin-left: 5px;">{{_("Submit")}}</button>
+ <button class="btn btn-default" id="cap_reset" type="reset" style="float: right">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/Next/tml/downloads.html b/pyload/webui/themes/Next/tml/downloads.html
new file mode 100644
index 000000000..0643ed2f8
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul style="list-style-type: none;">
+ {% for folder in files.folder %}
+ <li style="list-style-type: none;">
+ <span style="margin-right: 5px" class="glyphicon glyphicon-folder-close"></span>{{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li style="list-style-type: none;"><span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li style="list-style-type: none;"> <span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/filemanager.html b/pyload/webui/themes/Next/tml/filemanager.html
new file mode 100644
index 000000000..c940f3f79
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/filemanager.html
@@ -0,0 +1,80 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/js/filemanager.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var fmUI = new FilemanagerUI("url",1);
+});
+</script>
+{% endblock %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+
+{% block subtitle %}
+{{_("FileManager")}}
+{% endblock %}
+
+{% macro display_file(file) %}
+ <li class="file">
+ <input type="hidden" name="path" class="path" value="{{ file.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ file.name }}" />
+ <span>
+ <b>{{ file.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ </span>
+ </span>
+ </li>
+{%- endmacro %}
+
+{% macro display_folder(fld, open = false) -%}
+ <li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
+ <span>
+ <b>{{ fld.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ {% if (fld.folders|length + fld.files|length) > 0 %}
+ {% if open %}
+ <ul>
+ {% else %}
+ <ul style="display:none">
+ {% endif %}
+ {% for child in fld.folders %}
+ {{ display_folder(child) }}
+ {% endfor %}
+ {% for child in fld.files %}
+ {{ display_file(child) }}
+ {% endfor %}
+ </ul>
+ {% else %}
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+ {% endif %}
+ </li>
+{%- endmacro %}
+
+{% block content %}
+
+<div style="clear:both"><!-- --></div>
+
+<ul id="directories-list">
+{{ display_folder(root, true) }}
+</ul>
+
+{% include "/tml/rename_directory.html" %}
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/folder.html b/pyload/webui/themes/Next/tml/folder.html
new file mode 100644
index 000000000..d01fc4972
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Next/tml/home.html b/pyload/webui/themes/Next/tml/home.html
new file mode 100644
index 000000000..2ff1ad58b
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/home.html
@@ -0,0 +1,277 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0,
+ }
+ }),
+ status: new Element('td', {
+ 'html': '&nbsp;',
+ }),
+ statusspan: new Element('span', {
+ 'html': item.statusmsg,
+ 'class': 'label label-default',
+ 'styles':{
+
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('span',{
+ 'html': '',
+ 'class': 'glyphicon glyphicon-remove',
+ 'styles':{
+ 'margin-left': '3px',
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':'',
+ 'styles':{
+ 'border-top-color': '#fff',
+ }
+ }),
+ progress: new Element('div', {
+ 'html':'',
+ 'class':'progress',
+ 'styles':{
+ 'margin-bottom': '0px',
+ }
+ }),
+ pgb: new Element('div', {
+ 'html':'',
+ 'class':'progress-bar progress-bar-striped active',
+ 'role':'progress',
+ 'styles':{
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+
+ };
+
+
+ this.elements.status.adopt(this.elements.statusspan);
+ this.elements.progress.adopt(this.elements.pgb);
+ this.elements.tr.adopt(this.elements.status,this.elements.name,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.progress));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.statusspan.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if (item.statusmsg == "waiting") {
+ this.elements.statusspan.set('class', 'label label-warning')
+ } else if (item.statusmsg == "starting") {
+ this.elements.statusspan.set('class', 'label label-info')
+ } else if (item.statusmsg == "downloading") {
+ this.elements.statusspan.set('class', 'label label-success')
+ } else if (item.stausmsg == "extracting") {
+ this.elements.statusspan.set('class', 'label label-primary')
+ } else {
+ this.elements.statusspan.set('class', 'label label-default')
+ }
+ if(!operafix)
+ {
+
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),80,70].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(50/200*item.percent),0,200].hsbToRgb().rgbToHex(),
+ });
+ }
+
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+
+
+</script>
+{% endblock %}
+
+{% block subtitle %}{{_("Active Downloads")}}{% endblock %}
+{% block content %}
+<table class="table" style="width:100%;">
+ <thead>
+ <tr class="header">
+ <th>{{_("Status")}}</th>
+ <th>{{_("Name")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+</br>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_status"><span class="label label-{% if link.status == 'downloading' %}success{% endif %}{% if link.status == 'extracting' %}primary{% endif %}{% if link.status == 'starting' %}warning{% else %}default{% endif %}">{{ link.status }}</span></td>
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/info.html b/pyload/webui/themes/Next/tml/info.html
new file mode 100644
index 000000000..f6c3a91e1
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript">
+ window.addEvent("domready", function() {
+ var ul = new Element('ul#twitter_update_list');
+ var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.js][type=text/javascript]');
+ var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
+ $("twitter").adopt(ul, script1, script2);
+ });
+ </script>
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+ <div id="twitter"></div>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td><b>{{ _("Python:") }}</b></td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("OS:") }}</b></td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("pyLoad version:") }}</b></td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Installation Folder:") }}</b></td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Config Folder:") }}</b></td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Download Folder:") }}</b></td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Free Space:") }}</b></td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Language:") }}</b></td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Webinterface Port:") }}</b></td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Remote Interface Port:") }}</b></td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/login.html b/pyload/webui/themes/Next/tml/login.html
new file mode 100644
index 000000000..93d50d64d
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span>{{_("Username")}}</span>
+ <input type="text" size="20" name="username" />
+ </label>
+ <br />
+ <label>
+ <span>{{_("Password")}}</span>
+ <input type="password" size="20" name="password" />
+ </label>
+ <br />
+ <input type="submit" value="Login" class="button" />
+ </fieldset>
+ </div>
+</form>
+
+{% if errors %}
+<p>{{_("Your username and password didn't match. Please try again.")}}</p>
+ {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/logout.html b/pyload/webui/themes/Next/tml/logout.html
new file mode 100644
index 000000000..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/logs.html b/pyload/webui/themes/Next/tml/logs.html
new file mode 100644
index 000000000..f0f25a9dd
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/css/log.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}"><span class="glyphicon glyphicon-fast-backward"></span></a> <a href="{{ "/logs/" + iprev|string }}"><span class="glyphicon glyphicon-step-backward"></span></a> <a href="{{ "/logs/" + inext|string }}"><span class="glyphicon glyphicon-step-forward"></span></a> <a href="/logs/"><span class="glyphicon glyphicon-fast-forward"></span></a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td class="{{color_template}}{{line.level}}">{{line.date}}</td><td class="loglevel loglevel{{color_template}}{{line.level}}"><span>{{line.level}}</span></td><td class="{{color_template}}{{line.level}}">{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input style="float:left; width:80%;" class="form-control" type="text" name="from" size="15" value="{{from}}"/>
+ <input style="float:left; width:19%; margin-left: 1%;" class="btn btn-primary" type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/pathchooser.html b/pyload/webui/themes/Next/tml/pathchooser.html
new file mode 100644
index 000000000..9f61d282d
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html>
diff --git a/pyload/webui/themes/Next/tml/queue.html b/pyload/webui/themes/Next/tml/queue.html
new file mode 100644
index 000000000..0e343a754
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/queue.html
@@ -0,0 +1,109 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block pageactions %}
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;">
+ <button id="del_finished" class="btn btn-default"><span>{{_("Delete Finished")}}</span></button>
+ <button id="restart_failed" class="btn btn-default"><span>{{_("Restart Failed")}}</span></button>
+</div>
+{% endblock %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" >
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="float:left; width: 50%; cursor: pointer">
+ <span class="glyphicon glyphicon-folder-close"></span>
+ <span class="name" style="font-size: 16px; font-weight: bold;"><em class="package_drag" style="font-style:normal">{{package.name }}</em></span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <span class="glyphicon glyphicon-trash" title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-repeat" title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-pencil" title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-transfer" title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /></span>
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" class="progress" style="float:left; width: 50%; margin-top: -5px; color:#fff; font-weight: 700; font-size:12px;">
+ <div class="progress-bar" role="progressbar" style="width: {{ progress }}%; height: 100%; position: relative; z-index: 1;">
+ <label>{{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <label style="right: 30px; position: absolute; z-index: 2; color:#fff;">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+
+ <div id="children_{{package.pid}}" style="display: none; margin-bottom: 15px;" class="children">
+ <span class="child_secrow" style="margin-bottom: 30px; margin-top: 5px;">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" style="z-index: 2">
+ <form id="pack_form" class="from-group" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h3>{{_("Edit Package")}}</h3>
+ <p>{{_("Edit the package detais below.")}}</p>
+
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+
+ <div class="form-group">
+ <label for="pack_name">{{_("Name")}}</label>
+ <input class="form-control" id="pack_name" name="pack_name" type="text" />
+ <p class="help-block">{{_("The name of the package.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="pack_folder">{{_("Folder")}}</label>
+ <input class="form-control" id="pack_folder" name="pack_folder" type="text" />
+ <p class="help-block">{{_("Name of subfolder for these downloads.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="pack_pws">{{_("Password")}}</label>
+ <textarea class="form-control" style=" width: 100%;" rows="3" name="pack_pws" id="pack_pws"></textarea>
+ <p class="help-block">{{_("List of passwords used for unrar.")}}</p>
+ </div>
+ <button class="btn btn-primary" style="float: right; margin-left: 5px;" type="submit">{{_("Submit")}}</button>
+ <button class="btn btn-default" id="pack_reset" style="float: right;" type="reset" >{{_("Reset")}}</button>
+
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/settings.html b/pyload/webui/themes/Next/tml/settings.html
new file mode 100644
index 000000000..5acab4f1f
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/settings.html
@@ -0,0 +1,212 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/settings.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="nav nav-tabs">
+ <li role="presentation" class"active"><a href="#">{{ _("General") }}</a></li>
+ <li role="presentation"><a href="#">{{ _("Plugins") }}</a></li>
+ <li role="presentation"><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs" style="width: 20%; float:left;">
+ <li class>
+ <div class="panel panel-default" >
+ <div class="panel-body">
+ <ul id="general-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ </li>
+ </ul>
+
+ <form style="float: left; width:40%; margin-left: 20%; diplay:block; position: fixed; overflow: auto;" id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input class="btn btn-primary" style="float:right; margin-right: 10px;" id="general|submit" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs" style="width: 20%; float:left; hight:300px;">
+ <li class>
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <ul id="plugin-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ {% endfor %}
+ </ul>
+ <div>
+ </div>
+ </li>
+ </ul>
+
+
+ <form style="float: left; width:40%; margin-left: 10px;" id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content" style:"position: static;">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input class="btn btn-primary" style="float:right; margin-right: 10px;" id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide table">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:#424242;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" value="{{account.password}}" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.time}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.limitdl}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ <button id="account_add" style="margin-left: 5px;" type="submit" class="btn btn-default">&nbsp;<span class="glyphicon glyphicon-plus"></button>
+
+ <button id="account_submit" type="submit" class="btn btn-primary" >{{_("Submit")}}</button>
+
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h3>{{_("Add Account")}}</h3>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+
+<div class="form-group">
+<label for="account_login">{{_("Login")}}</label>
+<input class="form-control" id="account_login" name="account_login" type="text" />
+<p >{{_("Your username.")}}</p>
+</div>
+<div class="form-group">
+<label for="account_password">{{_("Password")}}</label>
+<input class="form-control" id="account_password" name="account_password" type="password" size="20" />
+<p >{{_("The password for this account.")}}</p>
+</div>
+<div class="form-group">
+<label for="account_type">{{_("Type")}}</label>
+<p>{{_("Choose the hoster for your account.")}}</p>
+</div>
+<div class="form-group">
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+</div>
+<button class="btn btn-primary" style="float: right; margin-left: 5px;" id="account_add_button" type="submit">{{_("Add")}}</button>
+<button class="btn btn-default" style="float: right;" id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/settings_item.html b/pyload/webui/themes/Next/tml/settings_item.html
new file mode 100644
index 000000000..72566950f
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/settings_item.html
@@ -0,0 +1,50 @@
+<table class="settable table">
+
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in sorted_conf(section) %}
+ {% if okey not in ("desc","outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:#424242;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input style="float: right;" class="form-control btn btn-primary" name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input style="float: right;" class="form-control btn btn-primary" name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+
+</table>
diff --git a/pyload/webui/themes/Next/tml/window.html b/pyload/webui/themes/Next/tml/window.html
new file mode 100644
index 000000000..87c78f96c
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/window.html
@@ -0,0 +1,46 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="from-group">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h3>{{_("Add Package")}}</h3>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<div class="form-group">
+ <label for="add_name">{{_("Name")}}</label>
+ <input id="add_name" class="form-control" name="add_name" type="text" />
+ <p class="help-block">{{_("The name of the new package.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="add_links">{{_("Links")}}</label>
+ </div>
+ <div>
+ <textarea class="form-control" rows="5" style="width: 100%" name="add_links" id="add_links"></textarea>
+</div>
+<div class="form-group">
+ <p class="help-block">{{_("The name of the new package.")}} {{ _("Filter urls") }} <span class=" glyphicon glyphicon-filter" onclick="parseUri()"></span></p>
+ </div>
+ <div class="form-group">
+ <label for="add_password">{{_("Password")}}</label>
+ <input id="add_password" class="form-control" name="add_password" type="text">
+ <p class="help-block">{{_("Password for RAR-Archive")}}</p>
+ </div>
+ <div class="form-group">
+ <label>{{_("File")}}</label>
+ <input type="file" name="add_file" id="add_file"/>
+ <p class="help-block">{{_("Upload a container.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="add_dest">{{_("Destination")}}</label>
+ <span class="cont">
+ {{_("Queue")}}
+ <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
+ {{_("Collector")}}
+ <input type="radio" name="add_dest" id="add_dest2" value="0"/>
+ </span>
+ </div>
+ <button type="submit" class="btn btn-primary" style="float: right; margin-right: 5px;">{{_("Add Package")}}</button>
+ <button id="add_reset" class="btn btn-default" style="float: right; margin-right: 5px;" type="reset">{{_("Reset")}}</button>
+</form>
+
+
+
+</div>
diff --git a/scripts/Readme.txt b/scripts/Readme.txt
deleted file mode 100644
index 1c326801d..000000000
--- a/scripts/Readme.txt
+++ /dev/null
@@ -1,25 +0,0 @@
- #############################
- ### pyLoad Script Support ###
- #############################
-
-pyLoad is able to start any kind of scripts at given events.
-Simply put your script in a suitable folder and pyLoad will execute it at the given events and pass some arguments to them.
-
---> Note: Scripts, which starts with # will be ignored!
-For Example: #converter.sh will not be executed.
-
---> Note: You have to restart pyload when you change script names or locations.
-
-Below you see the list of arguments, which are passed to the scripts.
-
-## Argument list ##
-
-download_preparing: pluginname url
-
-download_finished: pluginname url filename filelocation
-
-package_finshed: packagename packagelocation
-
-before_reconnect: oldip
-
-after_reconnect: newip \ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100755
index 000000000..3f239c68e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+
+import pyload
+
+from os import path
+from setuptools import setup
+
+
+PROJECT_DIR = path.abspath(path.join(__file__, ".."))
+
+setup(
+ name="pyload",
+
+ version=pyload.__version__,
+
+ description=pyload.__description__,
+
+ long_description=open(path.join(PROJECT_DIR, "README.md")).read(),
+
+ keywords=["pyload", "download", "download-manager", "downloader", "jdownloader", "pycurl", "one-click-hoster"],
+
+ url=pyload.__website__,
+
+ download_url="https://github.com/pyload/pyload/releases",
+
+ license=pyload.__license__,
+
+ author="pyLoad Team",
+
+ author_email="support@pyload.org",
+
+ platforms=['Any'],
+
+ # package_dir={'pyload': 'src'},
+
+ packages=['pyload'],
+
+ # package_data=find_package_data(),
+
+ # data_files=[],
+
+ include_package_data=True,
+
+ exclude_package_data={'pyload': ["docs*", "locale*", "tests*"]}, #: exluced from build but not from sdist
+
+ install_requires=[
+ "Beaker >= 1.6",
+ "bitmath",
+ "bottle >= 0.10.0",
+ "colorama",
+ "Getch",
+ "jinja2",
+ "markupsafe",
+ "MultipartPostHandler",
+ "pycurl",
+ "rename_process",
+ "SafeEval",
+ "thrift >= 0.8.0",
+ "wsgiserver"
+ ],
+
+ extras_require={
+ 'Few plugins dependencies': ["BeautifulSoup >= 3.2, < 3.3"],
+ 'Captcha recognition' : ["PIL"],
+ 'Trash support' : ["Send2Trash"],
+ 'Colored log' : ["colorlog"],
+ 'Lightweight webserver' : ["bjoern"],
+ 'RSS support' : ["feedparser"],
+ 'SSL support' : ["pyOpenSSL"],
+ 'RSDF/CCF/DLC support' : ["pycrypto"],
+ 'JSON speedup' : ["simplejson"]
+ },
+
+ # setup_requires=["setuptools_hg"],
+
+ # test_suite='nose.collector',
+
+ # tests_require=['nose', 'websocket-client >= 0.8.0', 'requests >= 1.2.2'],
+
+ entry_points={
+ 'console_scripts': [
+ 'pyload = pyload.Core:main',
+ 'pyload-cli = pyload.Cli:main'
+ ]
+ },
+
+ zip_safe=False,
+
+ classifiers=[
+ "Development Status :: %(code)s - %(status)s" % {'code' : pyload.__status_code__,
+ 'status': pyload.__status__},
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: %s" % pyload.__license__,
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.5",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Topic :: Internet :: WWW/HTTP"
+ ]
+)
diff --git a/systemCheck.py b/systemCheck.py
deleted file mode 100644
index 60fe0313b..000000000
--- a/systemCheck.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import os
-import subprocess
-import sys
-
-#from module import InitHomeDir
-
-#very ugly prints, but at least it works with python 3
-
-def main():
- print("##### System Information #####\n")
- print("Platform:", sys.platform)
- print("Operating System:", os.name)
- print("Python:", sys.version.replace("\n", "")+ "\n")
-
- try:
- import pycurl
- print("pycurl:", pycurl.version)
- except:
- print("pycurl:", "missing")
-
- try:
- import Crypto
- print("py-crypto:", Crypto.__version__)
- except:
- print("py-crypto:", "missing")
-
-
- try:
- import OpenSSL
- print("OpenSSL:", OpenSSL.version.__version__)
- except:
- print("OpenSSL:", "missing")
-
- try:
- import Image
- print("image libary:", Image.VERSION)
- except:
- print("image libary:", "missing")
-
- try:
- import PyQt4.QtCore
- print("pyqt:", PyQt4.QtCore.PYQT_VERSION_STR)
- except:
- print("pyqt:", "missing")
-
- print("\n\n##### System Status #####")
- print("\n## pyLoadCore ##")
-
- core_err = []
- core_info = []
-
- if sys.version_info > (2, 8):
- core_err.append("Your python version is to new, Please use Python 2.6/2.7")
-
- if sys.version_info < (2, 5):
- core_err.append("Your python version is to old, Please use at least Python 2.5")
-
- try:
- import pycurl
- except:
- core_err.append("Please install py-curl to use pyLoad.")
-
-
- try:
- from pycurl import AUTOREFERER
- except:
- core_err.append("Your py-curl version is to old, please upgrade!")
-
- try:
- import Image
- except:
- core_err.append("Please install py-imaging/pil to use Hoster, which uses captchas.")
-
- pipe = subprocess.PIPE
- try:
- p = subprocess.call(["tesseract"], stdout=pipe, stderr=pipe)
- except:
- core_err.append("Please install tesseract to use Hoster, which uses captchas.")
-
- try:
- import OpenSSL
- except:
- core_info.append("Install OpenSSL if you want to create a secure connection to the core.")
-
- if core_err:
- print("The system check has detected some errors:\n")
- for err in core_err:
- print(err)
- else:
- print("No Problems detected, pyLoadCore should work fine.")
-
- if core_info:
- print("\nPossible improvements for pyload:\n")
- for line in core_info:
- print(line)
-
-
- print("\n## pyLoadGui ##")
-
- gui_err = []
-
- try:
- import PyQt4
- except:
- gui_err.append("GUI won't work without pyqt4 !!")
-
- if gui_err:
- print("The system check has detected some errors:\n")
- for err in gui_err:
- print(err)
- else:
- print("No Problems detected, pyLoadGui should work fine.")
-
-
- print("\n## Webinterface ##")
-
- web_err = []
- web_info = []
-
- try:
- import flup
- except:
- web_info.append("Install Flup to use FastCGI or optional webservers.")
-
-
- if web_err:
- print("The system check has detected some errors:\n")
- for err in web_err:
- print(err)
- else:
- print("No Problems detected, Webinterface should work fine.")
-
- if web_info:
- print("\nPossible improvements for webinterface:\n")
- for line in web_info:
- print(line)
-
-
-if __name__ == "__main__":
- main()
-
- raw_input("Press Enter to Exit.")
diff --git a/testlinks.txt b/testlinks.txt
deleted file mode 100644
index 428cf63ea..000000000
--- a/testlinks.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-sha1:
- 961486646bf3c1d5d7a45ec32bb62e1bc4f2d894 random.bin
-md5:
- d76505d0869f9f928a17d42d66326307 random.bin
-
-please save bandwith on our server,
-use this link only for remote uploads to new hoster
-http://get.pyload.org/static/random.bin
----------------------------------------
-http://netload.in/datei9XirAJZs79/random.bin.htm
-http://ul.to/file/o41isx
-http://rapidshare.com/files/445996776/random.bin
-http://dl.free.fr/d4aL5dyXY
-http://www.duckload.com/dl/QggW2
-http://files.mail.ru/32EW66
-http://www.fileserve.com/file/MxjZXjX
-http://www.4shared.com/file/-O5CBhQV/random.html
-http://hotfile.com/dl/101569859/2e01f04/random.bin.html
-http://www.megaupload.com/?d=1JZLOP3B
-http://www.share.cx/files/235687689252/random.bin.html
-http://www.share-online.biz/download.php?id=PTCOX1GL6XAH
-http://www.shragle.com/files/f899389b/random.bin
-http://www10.zippyshare.com/v/76557688/file.html
-http://yourfiles.to/?d=312EC6E911
-http://depositfiles.com/files/k8la98953
-http://uploading.com/files/3896f5a1/random.bin/
diff --git a/tests/APIExerciser.py b/tests/APIExerciser.py
new file mode 100644
index 000000000..f4b082479
--- /dev/null
+++ b/tests/APIExerciser.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import string
+from threading import Thread
+from random import choice, sample, randint
+from time import time
+from math import floor
+import gc
+from traceback import print_exc, format_exc
+
+from pyload.remote.thriftbackend.ThriftClient import ThriftClient, Destination
+
+
+def createURLs():
+ """ create some urls, some may fail """
+ urls = []
+ for x in xrange(0, randint(20, 100)):
+ name = "DEBUG_API"
+ if randint(0, 5) == 5:
+ name = "" #: this link will fail
+
+ urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20))))
+
+ return urls
+
+
+AVOID = (0, 3, 8)
+
+idPool = 0
+sumCalled = 0
+
+
+def startApiExerciser(core, n):
+ for _i in xrange(n):
+ APIExerciser(core).start()
+
+
+class APIExerciser(Thread):
+
+ def __init__(self, core, thrift=False, user=None, pw=None):
+ global idPool
+
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+ self.count = 0 #: number of methods
+ self.time = time()
+
+ self.api = ThriftClient(user=user, password=pw) if thrift else core.api
+
+ self.id = idPool
+
+ idPool += 1
+
+ # self.start()
+
+
+ def run(self):
+
+ self.core.log.info("API Excerciser started %d" % self.id)
+
+ with open("error.log", "ab") as out:
+ # core errors are not logged of course
+ out.write("\n" + "Starting\n")
+ out.flush()
+
+ while True:
+ try:
+ self.testAPI()
+ except Exception:
+ self.core.log.error("Excerciser %d throw an execption" % self.id)
+ print_exc()
+ out.write(format_exc() + 2 * "\n")
+ out.flush()
+
+ if not self.count % 100:
+ self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count))
+ if not self.count % 1000:
+ out.flush()
+
+ if not sumCalled % 1000: #: not thread safe
+ self.core.log.info("Exercisers tested %d api calls" % sumCalled)
+ persec = sumCalled / (time() - self.time)
+ self.core.log.info("Approx. %.2f calls per second." % persec)
+ self.core.log.info("Approx. %.2f ms per call." % (1000 / persec))
+ self.core.log.info("Collected garbage: %d" % gc.collect())
+ # sleep(random() / 500)
+
+
+ def testAPI(self):
+ global sumCalled
+
+ m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles",
+ "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting",
+ "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices", "getAccounts", "getAllUserData"]
+
+ method = choice(m)
+ # print "Testing:", method
+
+ if hasattr(self, method):
+ res = getattr(self, method)()
+ else:
+ res = getattr(self.api, method)()
+
+ self.count += 1
+ sumCalled += 1
+
+ # print res
+
+
+ def addPackage(self):
+ name = "".join(sample(string.ascii_letters, 10))
+ urls = createURLs()
+
+ self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]))
+
+
+ def deleteFiles(self):
+ info = self.api.getQueueData()
+ if not info:
+ return
+
+ pack = choice(info)
+ fids = pack.links
+
+ if len(fids):
+ fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))]
+ self.api.deleteFiles(fids)
+
+
+ def deletePackages(self):
+ info = choice([self.api.getQueue(), self.api.getCollector()])
+ if not info:
+ return
+
+ pids = [p.pid for p in info]
+ if pids:
+ pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1)))
+ self.api.deletePackages(pids)
+
+
+ def getFileData(self):
+ info = self.api.getQueueData()
+ if info:
+ p = choice(info)
+ if p.links:
+ self.api.getFileData(choice(p.links).fid)
+
+
+ def getPackageData(self):
+ info = self.api.getQueue()
+ if info:
+ self.api.getPackageData(choice(info).pid)
+
+
+ def getAccounts(self):
+ self.api.getAccounts(False)
+
+
+ def getCaptchaTask(self):
+ self.api.getCaptchaTask(False)
diff --git a/tests/clonedigger.sh b/tests/clonedigger.sh
new file mode 100644
index 000000000..e7fd17eb6
--- /dev/null
+++ b/tests/clonedigger.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+PYLOAD="../pyload" #: Check pyload directory
+clonedigger -o cpd.xml --cpd-output --fast --ignore-dir="remote" ${PYLOAD}
diff --git a/tests/code_analysis.sh b/tests/code_analysis.sh
new file mode 100644
index 000000000..aaf6bb6a4
--- /dev/null
+++ b/tests/code_analysis.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+PYLOAD="../pyload" #: Check pyload directory
+
+echo "Running sloccount ..."
+REPORT="sloccount.sc"
+sloccount --duplicates --wide --details ${PYLOAD} > ${REPORT} && echo "Done. Report saved to $REPORT"
+
+echo "Running pep8 ..."
+REPORT="pep8.txt"
+pep8 ${PYLOAD} > ${REPORT} && echo "Done. Report saved to $REPORT"
+
+echo "Running pylint ..."
+REPORT="pylint.txt"
+pylint --reports=no ${PYLOAD} > ${REPORT} && echo "Done. Report saved to $REPORT"
diff --git a/tests/test_api.py b/tests/test_api.py
index f8901f731..1e02d8aa3 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,20 +1,24 @@
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from module.common import APIExerciser
+import APIExerciser
+
from nose.tools import nottest
-class TestApi:
+class TestApi(object):
def __init__(self):
self.api = APIExerciser.APIExerciser(None, True, "TestUser", "pwhere")
+
def test_login(self):
assert self.api.api.login("crapp", "wrong pw") is False
- #takes really long, only test when needed
+ # takes really long, only test when needed
+
+
@nottest
def test_random(self):
-
- for i in range(0, 100):
+ for _i in xrange(0, 100):
self.api.testAPI()
diff --git a/tests/test_json.py b/tests/test_json.py
index ff56e8f5a..a83ef0a24 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
from urllib import urlencode
@@ -8,23 +9,27 @@ from logging import log
url = "http://localhost:8001/api/%s"
-class TestJson:
+
+class TestJson(object):
def call(self, name, post=None):
if not post: post = {}
- post["session"] = self.key
+ post['session'] = self.key
u = urlopen(url % name, data=urlencode(post))
return loads(u.read())
+
def setUp(self):
u = urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "pwhere"}))
self.key = loads(u.read())
assert self.key is not False
+
def test_wronglogin(self):
u = urlopen(url % "login", data=urlencode({"username": "crap", "password": "wrongpw"}))
assert loads(u.read()) is False
+
def test_access(self):
try:
urlopen(url % "getServerVersion")
@@ -33,16 +38,18 @@ class TestJson:
else:
assert False
+
def test_status(self):
ret = self.call("statusServer")
log(1, str(ret))
assert "pause" in ret
assert "queue" in ret
+
def test_unknown_method(self):
try:
self.call("notExisting")
except HTTPError, e:
assert e.code == 404
else:
- assert False \ No newline at end of file
+ assert False