summaryrefslogtreecommitdiffstats
path: root/pyload
diff options
context:
space:
mode:
Diffstat (limited to 'pyload')
-rw-r--r--pyload/Core.py633
-rw-r--r--pyload/InitHomeDir.py64
-rw-r--r--pyload/__init__.py20
-rw-r--r--pyload/api/__init__.py973
-rw-r--r--pyload/api/types.py381
-rw-r--r--pyload/cli/AddPackage.py50
-rw-r--r--pyload/cli/Cli.py571
-rw-r--r--pyload/cli/Handler.py32
-rw-r--r--pyload/cli/ManageFiles.py178
-rw-r--r--pyload/cli/__init__.py4
-rw-r--r--pyload/config/Parser.py333
-rw-r--r--pyload/config/Setup.py538
-rw-r--r--pyload/config/default.conf64
-rw-r--r--pyload/database/DatabaseBackend.py290
-rw-r--r--pyload/database/FileDatabase.py875
-rw-r--r--pyload/database/StorageDatabase.py34
-rw-r--r--pyload/database/UserDatabase.py93
-rw-r--r--pyload/database/__init__.py8
-rw-r--r--pyload/datatype/PyFile.py270
-rw-r--r--pyload/datatype/PyPackage.py64
-rw-r--r--pyload/datatype/__init__.py0
-rw-r--r--pyload/manager/AccountManager.py191
-rw-r--r--pyload/manager/AddonManager.py276
-rw-r--r--pyload/manager/CaptchaManager.py138
-rw-r--r--pyload/manager/PluginManager.py402
-rw-r--r--pyload/manager/RemoteManager.py76
-rw-r--r--pyload/manager/ThreadManager.py300
-rw-r--r--pyload/manager/__init__.py0
-rw-r--r--pyload/manager/event/PullEvents.py104
-rw-r--r--pyload/manager/event/Scheduler.py126
-rw-r--r--pyload/manager/event/__init__.py0
-rw-r--r--pyload/manager/thread/AddonThread.py74
-rw-r--r--pyload/manager/thread/DecrypterThread.py105
-rw-r--r--pyload/manager/thread/DownloadThread.py217
-rw-r--r--pyload/manager/thread/InfoThread.py228
-rw-r--r--pyload/manager/thread/PluginThread.py130
-rw-r--r--pyload/manager/thread/ServerThread.py111
-rw-r--r--pyload/manager/thread/__init__.py0
-rw-r--r--pyload/network/Browser.py132
-rw-r--r--pyload/network/Bucket.py44
-rw-r--r--pyload/network/CookieJar.py34
-rw-r--r--pyload/network/HTTPChunk.py294
-rw-r--r--pyload/network/HTTPDownload.py311
-rw-r--r--pyload/network/HTTPRequest.py302
-rw-r--r--pyload/network/RequestFactory.py121
-rw-r--r--pyload/network/XDCCRequest.py144
-rw-r--r--pyload/network/__init__.py1
-rw-r--r--pyload/plugins/Plugin.py753
-rw-r--r--pyload/plugins/README.md16
-rw-r--r--pyload/plugins/__init__.py0
-rw-r--r--pyload/plugins/account/AlldebridCom.py59
-rw-r--r--pyload/plugins/account/BayfilesCom.py37
-rw-r--r--pyload/plugins/account/BillionuploadsCom.py16
-rw-r--r--pyload/plugins/account/BitshareCom.py32
-rw-r--r--pyload/plugins/account/CatShareNet.py56
-rw-r--r--pyload/plugins/account/CramitIn.py16
-rw-r--r--pyload/plugins/account/CzshareCom.py44
-rw-r--r--pyload/plugins/account/DebridItaliaCom.py44
-rw-r--r--pyload/plugins/account/DepositfilesCom.py35
-rw-r--r--pyload/plugins/account/DevhostSt.py48
-rw-r--r--pyload/plugins/account/DevhostStFolder.py58
-rw-r--r--pyload/plugins/account/DropboxCom.py42
-rw-r--r--pyload/plugins/account/EasybytezCom.py19
-rw-r--r--pyload/plugins/account/EuroshareEu.py41
-rw-r--r--pyload/plugins/account/FastixRu.py38
-rw-r--r--pyload/plugins/account/FastshareCz.py53
-rw-r--r--pyload/plugins/account/File4safeCom.py18
-rw-r--r--pyload/plugins/account/FileParadoxIn.py16
-rw-r--r--pyload/plugins/account/FilecloudIo.py59
-rw-r--r--pyload/plugins/account/FilefactoryCom.py49
-rw-r--r--pyload/plugins/account/FilejungleCom.py49
-rw-r--r--pyload/plugins/account/FileomCom.py16
-rw-r--r--pyload/plugins/account/FilerNet.py50
-rw-r--r--pyload/plugins/account/FilerioCom.py16
-rw-r--r--pyload/plugins/account/FilesMailRu.py28
-rw-r--r--pyload/plugins/account/FileserveCom.py44
-rw-r--r--pyload/plugins/account/FourSharedCom.py33
-rw-r--r--pyload/plugins/account/FreakshareCom.py43
-rw-r--r--pyload/plugins/account/FreeWayMe.py55
-rw-r--r--pyload/plugins/account/FshareVn.py63
-rw-r--r--pyload/plugins/account/Ftp.py17
-rw-r--r--pyload/plugins/account/HellshareCz.py76
-rw-r--r--pyload/plugins/account/Http.py17
-rw-r--r--pyload/plugins/account/HugefilesNet.py16
-rw-r--r--pyload/plugins/account/HundredEightyUploadCom.py16
-rw-r--r--pyload/plugins/account/JunocloudMe.py16
-rw-r--r--pyload/plugins/account/Keep2shareCc.py69
-rw-r--r--pyload/plugins/account/KingfilesNet.py82
-rw-r--r--pyload/plugins/account/LetitbitNet.py34
-rw-r--r--pyload/plugins/account/LinestorageCom.py16
-rw-r--r--pyload/plugins/account/LinksnappyCom.py50
-rw-r--r--pyload/plugins/account/LomafileCom.py16
-rw-r--r--pyload/plugins/account/MegaDebridEu.py39
-rw-r--r--pyload/plugins/account/MegaRapidCz.py59
-rw-r--r--pyload/plugins/account/MegasharesCom.py48
-rw-r--r--pyload/plugins/account/MovReelCom.py19
-rw-r--r--pyload/plugins/account/MultishareCz.py44
-rw-r--r--pyload/plugins/account/MyfastfileCom.py35
-rw-r--r--pyload/plugins/account/NetloadIn.py40
-rw-r--r--pyload/plugins/account/NosuploadCom.py16
-rw-r--r--pyload/plugins/account/NovafileCom.py16
-rw-r--r--pyload/plugins/account/NowVideoAt.py56
-rw-r--r--pyload/plugins/account/OboomCom.py62
-rw-r--r--pyload/plugins/account/OneFichierCom.py55
-rw-r--r--pyload/plugins/account/OverLoadMe.py36
-rw-r--r--pyload/plugins/account/PremiumTo.py34
-rw-r--r--pyload/plugins/account/PremiumizeMe.py49
-rw-r--r--pyload/plugins/account/QuickshareCz.py43
-rw-r--r--pyload/plugins/account/RPNetBiz.py51
-rw-r--r--pyload/plugins/account/RapidfileshareNet.py18
-rw-r--r--pyload/plugins/account/RapidgatorNet.py58
-rw-r--r--pyload/plugins/account/RapiduNet.py46
-rw-r--r--pyload/plugins/account/RarefileNet.py16
-rw-r--r--pyload/plugins/account/RealdebridCom.py36
-rw-r--r--pyload/plugins/account/RehostTo.py41
-rw-r--r--pyload/plugins/account/RyushareCom.py25
-rw-r--r--pyload/plugins/account/SafesharingEu.py16
-rw-r--r--pyload/plugins/account/SecureUploadEu.py16
-rw-r--r--pyload/plugins/account/SendmywayCom.py16
-rw-r--r--pyload/plugins/account/ShareonlineBiz.py45
-rw-r--r--pyload/plugins/account/SimplyPremiumCom.py46
-rw-r--r--pyload/plugins/account/SimplydebridCom.py34
-rw-r--r--pyload/plugins/account/StahnuTo.py34
-rw-r--r--pyload/plugins/account/StreamcloudEu.py16
-rw-r--r--pyload/plugins/account/TurbobitNet.py42
-rw-r--r--pyload/plugins/account/TusfilesNet.py23
-rw-r--r--pyload/plugins/account/UlozTo.py52
-rw-r--r--pyload/plugins/account/UnrestrictLi.py44
-rw-r--r--pyload/plugins/account/UploadcCom.py16
-rw-r--r--pyload/plugins/account/UploadedTo.py60
-rw-r--r--pyload/plugins/account/UploadheroCom.py41
-rw-r--r--pyload/plugins/account/UploadingCom.py63
-rw-r--r--pyload/plugins/account/UptoboxCom.py17
-rw-r--r--pyload/plugins/account/VidPlayNet.py16
-rw-r--r--pyload/plugins/account/XFileSharingPro.py30
-rw-r--r--pyload/plugins/account/YibaishiwuCom.py40
-rw-r--r--pyload/plugins/account/ZeveraCom.py56
-rw-r--r--pyload/plugins/account/__init__.py0
-rw-r--r--pyload/plugins/addon/Checksum.py191
-rw-r--r--pyload/plugins/addon/ClickAndLoad.py71
-rw-r--r--pyload/plugins/addon/DeleteFinished.py79
-rw-r--r--pyload/plugins/addon/DownloadScheduler.py82
-rw-r--r--pyload/plugins/addon/ExternalScripts.py146
-rw-r--r--pyload/plugins/addon/ExtractArchive.py368
-rw-r--r--pyload/plugins/addon/HotFolder.py66
-rw-r--r--pyload/plugins/addon/IRCInterface.py436
-rw-r--r--pyload/plugins/addon/MergeFiles.py91
-rw-r--r--pyload/plugins/addon/MultiHome.py86
-rw-r--r--pyload/plugins/addon/RestartFailed.py45
-rw-r--r--pyload/plugins/addon/SkipRev.py82
-rw-r--r--pyload/plugins/addon/UnSkipOnFail.py92
-rw-r--r--pyload/plugins/addon/UpdateManager.py305
-rw-r--r--pyload/plugins/addon/WindowsPhoneToastNotify.py62
-rw-r--r--pyload/plugins/addon/XMPPInterface.py253
-rw-r--r--pyload/plugins/addon/__init__.py0
-rw-r--r--pyload/plugins/captcha/AdsCaptcha.py71
-rw-r--r--pyload/plugins/captcha/ReCaptcha.py66
-rw-r--r--pyload/plugins/captcha/SolveMedia.py46
-rw-r--r--pyload/plugins/captcha/__init__.py0
-rw-r--r--pyload/plugins/container/CCF.py43
-rw-r--r--pyload/plugins/container/LinkList.py71
-rw-r--r--pyload/plugins/container/RSDF.py56
-rw-r--r--pyload/plugins/container/__init__.py0
-rw-r--r--pyload/plugins/crypter/BitshareCom.py21
-rw-r--r--pyload/plugins/crypter/C1neonCom.py19
-rw-r--r--pyload/plugins/crypter/ChipDe.py29
-rw-r--r--pyload/plugins/crypter/CrockoCom.py20
-rw-r--r--pyload/plugins/crypter/CryptItCom.py19
-rw-r--r--pyload/plugins/crypter/CzshareCom.py32
-rw-r--r--pyload/plugins/crypter/DDLMusicOrg.py51
-rw-r--r--pyload/plugins/crypter/DailymotionBatch.py106
-rw-r--r--pyload/plugins/crypter/DataHu.py40
-rw-r--r--pyload/plugins/crypter/DdlstorageCom.py20
-rw-r--r--pyload/plugins/crypter/DepositfilesCom.py20
-rw-r--r--pyload/plugins/crypter/Dereferer.py26
-rw-r--r--pyload/plugins/crypter/DlProtectCom.py65
-rw-r--r--pyload/plugins/crypter/DontKnowMe.py29
-rw-r--r--pyload/plugins/crypter/DuckCryptInfo.py59
-rw-r--r--pyload/plugins/crypter/DuploadOrg.py19
-rw-r--r--pyload/plugins/crypter/EasybytezCom.py22
-rw-r--r--pyload/plugins/crypter/EmbeduploadCom.py60
-rw-r--r--pyload/plugins/crypter/FilebeerInfo.py19
-rw-r--r--pyload/plugins/crypter/FilecloudIo.py21
-rw-r--r--pyload/plugins/crypter/FilecryptCc.py148
-rw-r--r--pyload/plugins/crypter/FilefactoryCom.py28
-rw-r--r--pyload/plugins/crypter/FilerNet.py26
-rw-r--r--pyload/plugins/crypter/FileserveCom.py38
-rw-r--r--pyload/plugins/crypter/FilesonicCom.py18
-rw-r--r--pyload/plugins/crypter/FilestubeCom.py21
-rw-r--r--pyload/plugins/crypter/FiletramCom.py22
-rw-r--r--pyload/plugins/crypter/FiredriveCom.py19
-rw-r--r--pyload/plugins/crypter/FourChanOrg.py27
-rw-r--r--pyload/plugins/crypter/FreakhareCom.py38
-rw-r--r--pyload/plugins/crypter/FreetexthostCom.py27
-rw-r--r--pyload/plugins/crypter/FshareVn.py20
-rw-r--r--pyload/plugins/crypter/Go4UpCom.py49
-rw-r--r--pyload/plugins/crypter/GooGl.py32
-rw-r--r--pyload/plugins/crypter/HoerbuchIn.py62
-rw-r--r--pyload/plugins/crypter/HotfileCom.py19
-rw-r--r--pyload/plugins/crypter/ILoadTo.py19
-rw-r--r--pyload/plugins/crypter/ImgurComAlbum.py27
-rw-r--r--pyload/plugins/crypter/JunocloudMe.py20
-rw-r--r--pyload/plugins/crypter/LetitbitNet.py33
-rw-r--r--pyload/plugins/crypter/LinkCryptWs.py327
-rw-r--r--pyload/plugins/crypter/LinkSaveIn.py246
-rw-r--r--pyload/plugins/crypter/LinkdecrypterCom.py92
-rw-r--r--pyload/plugins/crypter/LixIn.py62
-rw-r--r--pyload/plugins/crypter/LofCc.py19
-rw-r--r--pyload/plugins/crypter/MBLinkInfo.py20
-rw-r--r--pyload/plugins/crypter/MediafireCom.py58
-rw-r--r--pyload/plugins/crypter/MegaRapidCz.py20
-rw-r--r--pyload/plugins/crypter/MegauploadCom.py18
-rw-r--r--pyload/plugins/crypter/Movie2kTo.py19
-rw-r--r--pyload/plugins/crypter/MultiUpOrg.py38
-rw-r--r--pyload/plugins/crypter/MultiloadCz.py42
-rw-r--r--pyload/plugins/crypter/MultiuploadCom.py18
-rw-r--r--pyload/plugins/crypter/NCryptIn.py315
-rw-r--r--pyload/plugins/crypter/NetfolderIn.py70
-rw-r--r--pyload/plugins/crypter/NosvideoCom.py21
-rw-r--r--pyload/plugins/crypter/OneKhDe.py40
-rw-r--r--pyload/plugins/crypter/OronCom.py19
-rw-r--r--pyload/plugins/crypter/PastebinCom.py21
-rw-r--r--pyload/plugins/crypter/QuickshareCz.py31
-rw-r--r--pyload/plugins/crypter/RSLayerCom.py19
-rw-r--r--pyload/plugins/crypter/RapidfileshareNet.py20
-rw-r--r--pyload/plugins/crypter/RelinkUs.py293
-rw-r--r--pyload/plugins/crypter/SafelinkingNet.py81
-rw-r--r--pyload/plugins/crypter/SecuredIn.py19
-rw-r--r--pyload/plugins/crypter/SexuriaCom.py94
-rw-r--r--pyload/plugins/crypter/ShareLinksBiz.py286
-rw-r--r--pyload/plugins/crypter/SharingmatrixCom.py18
-rw-r--r--pyload/plugins/crypter/SpeedLoadOrg.py19
-rw-r--r--pyload/plugins/crypter/StealthTo.py19
-rw-r--r--pyload/plugins/crypter/TnyCz.py27
-rw-r--r--pyload/plugins/crypter/TrailerzoneInfo.py19
-rw-r--r--pyload/plugins/crypter/TurbobitNet.py44
-rw-r--r--pyload/plugins/crypter/TusfilesNet.py45
-rw-r--r--pyload/plugins/crypter/UlozTo.py46
-rw-r--r--pyload/plugins/crypter/UploadableCh.py24
-rw-r--r--pyload/plugins/crypter/UploadedTo.py34
-rw-r--r--pyload/plugins/crypter/WiiReloadedOrg.py19
-rw-r--r--pyload/plugins/crypter/WuploadCom.py18
-rw-r--r--pyload/plugins/crypter/XFileSharingPro.py47
-rw-r--r--pyload/plugins/crypter/XupPl.py25
-rw-r--r--pyload/plugins/crypter/YoutubeBatch.py148
-rw-r--r--pyload/plugins/crypter/__init__.py1
-rw-r--r--pyload/plugins/hook/AlldebridCom.py27
-rw-r--r--pyload/plugins/hook/BypassCaptcha.py138
-rw-r--r--pyload/plugins/hook/Captcha9kw.py258
-rw-r--r--pyload/plugins/hook/CaptchaBrotherhood.py171
-rw-r--r--pyload/plugins/hook/DeathByCaptcha.py218
-rw-r--r--pyload/plugins/hook/DebridItaliaCom.py27
-rw-r--r--pyload/plugins/hook/EasybytezCom.py39
-rw-r--r--pyload/plugins/hook/ExpertDecoders.py97
-rw-r--r--pyload/plugins/hook/FastixRu.py28
-rw-r--r--pyload/plugins/hook/FreeWayMe.py25
-rw-r--r--pyload/plugins/hook/ImageTyperz.py156
-rw-r--r--pyload/plugins/hook/LinkdecrypterCom.py65
-rw-r--r--pyload/plugins/hook/LinksnappyCom.py27
-rw-r--r--pyload/plugins/hook/MegaDebridEu.py30
-rw-r--r--pyload/plugins/hook/MultishareCz.py27
-rw-r--r--pyload/plugins/hook/MyfastfileCom.py30
-rw-r--r--pyload/plugins/hook/OverLoadMe.py29
-rw-r--r--pyload/plugins/hook/PremiumTo.py38
-rw-r--r--pyload/plugins/hook/PremiumizeMe.py54
-rw-r--r--pyload/plugins/hook/RPNetBiz.py52
-rw-r--r--pyload/plugins/hook/RealdebridCom.py27
-rw-r--r--pyload/plugins/hook/RehostTo.py41
-rw-r--r--pyload/plugins/hook/SimplyPremiumCom.py29
-rw-r--r--pyload/plugins/hook/SimplydebridCom.py22
-rw-r--r--pyload/plugins/hook/UnrestrictLi.py30
-rw-r--r--pyload/plugins/hook/XFileSharingPro.py101
-rw-r--r--pyload/plugins/hook/ZeveraCom.py22
-rw-r--r--pyload/plugins/hook/__init__.py0
-rw-r--r--pyload/plugins/hoster/AlldebridCom.py87
-rw-r--r--pyload/plugins/hoster/BayfilesCom.py87
-rw-r--r--pyload/plugins/hoster/BezvadataCz.py94
-rw-r--r--pyload/plugins/hoster/BillionuploadsCom.py24
-rw-r--r--pyload/plugins/hoster/BitshareCom.py157
-rw-r--r--pyload/plugins/hoster/BoltsharingCom.py18
-rw-r--r--pyload/plugins/hoster/CatShareNet.py67
-rw-r--r--pyload/plugins/hoster/CloudzerNet.py20
-rw-r--r--pyload/plugins/hoster/CramitIn.py24
-rw-r--r--pyload/plugins/hoster/CrockoCom.py70
-rw-r--r--pyload/plugins/hoster/CyberlockerCh.py18
-rw-r--r--pyload/plugins/hoster/CzshareCom.py152
-rw-r--r--pyload/plugins/hoster/DailymotionCom.py125
-rw-r--r--pyload/plugins/hoster/DataHu.py42
-rw-r--r--pyload/plugins/hoster/DataportCz.py55
-rw-r--r--pyload/plugins/hoster/DateiTo.py82
-rw-r--r--pyload/plugins/hoster/DdlstorageCom.py19
-rw-r--r--pyload/plugins/hoster/DebridItaliaCom.py53
-rw-r--r--pyload/plugins/hoster/DepositfilesCom.py123
-rw-r--r--pyload/plugins/hoster/DlFreeFr.py136
-rw-r--r--pyload/plugins/hoster/DodanePl.py18
-rw-r--r--pyload/plugins/hoster/DuploadOrg.py18
-rw-r--r--pyload/plugins/hoster/EasybytezCom.py26
-rw-r--r--pyload/plugins/hoster/EdiskCz.py56
-rw-r--r--pyload/plugins/hoster/EgoFilesCom.py18
-rw-r--r--pyload/plugins/hoster/EnteruploadCom.py18
-rw-r--r--pyload/plugins/hoster/EpicShareNet.py18
-rw-r--r--pyload/plugins/hoster/EuroshareEu.py67
-rw-r--r--pyload/plugins/hoster/ExtabitCom.py79
-rw-r--r--pyload/plugins/hoster/FastixRu.py76
-rw-r--r--pyload/plugins/hoster/FastshareCz.py78
-rw-r--r--pyload/plugins/hoster/FileApeCom.py18
-rw-r--r--pyload/plugins/hoster/FileParadoxIn.py25
-rw-r--r--pyload/plugins/hoster/FileSharkPl.py140
-rw-r--r--pyload/plugins/hoster/FileStoreTo.py37
-rw-r--r--pyload/plugins/hoster/FilebeerInfo.py18
-rw-r--r--pyload/plugins/hoster/FilecloudIo.py125
-rw-r--r--pyload/plugins/hoster/FilefactoryCom.py90
-rw-r--r--pyload/plugins/hoster/FilejungleCom.py29
-rw-r--r--pyload/plugins/hoster/FileomCom.py35
-rw-r--r--pyload/plugins/hoster/FilepostCom.py130
-rw-r--r--pyload/plugins/hoster/FilepupNet.py51
-rw-r--r--pyload/plugins/hoster/FilerNet.py81
-rw-r--r--pyload/plugins/hoster/FilerioCom.py25
-rw-r--r--pyload/plugins/hoster/FilesMailRu.py106
-rw-r--r--pyload/plugins/hoster/FileserveCom.py217
-rw-r--r--pyload/plugins/hoster/FileshareInUa.py18
-rw-r--r--pyload/plugins/hoster/FilesonicCom.py19
-rw-r--r--pyload/plugins/hoster/FilezyNet.py18
-rw-r--r--pyload/plugins/hoster/FiredriveCom.py18
-rw-r--r--pyload/plugins/hoster/FlyFilesNet.py45
-rw-r--r--pyload/plugins/hoster/FourSharedCom.py61
-rw-r--r--pyload/plugins/hoster/FreakshareCom.py176
-rw-r--r--pyload/plugins/hoster/FreeWayMe.py36
-rw-r--r--pyload/plugins/hoster/FreevideoCz.py18
-rw-r--r--pyload/plugins/hoster/FshareVn.py125
-rw-r--r--pyload/plugins/hoster/Ftp.py79
-rw-r--r--pyload/plugins/hoster/GamefrontCom.py90
-rw-r--r--pyload/plugins/hoster/GigapetaCom.py67
-rw-r--r--pyload/plugins/hoster/GooIm.py39
-rw-r--r--pyload/plugins/hoster/HellshareCz.py48
-rw-r--r--pyload/plugins/hoster/HellspyCz.py18
-rw-r--r--pyload/plugins/hoster/HotfileCom.py21
-rw-r--r--pyload/plugins/hoster/HugefilesNet.py27
-rw-r--r--pyload/plugins/hoster/HundredEightyUploadCom.py27
-rw-r--r--pyload/plugins/hoster/IFileWs.py18
-rw-r--r--pyload/plugins/hoster/IcyFilesCom.py18
-rw-r--r--pyload/plugins/hoster/IfileIt.py67
-rw-r--r--pyload/plugins/hoster/IfolderRu.py76
-rw-r--r--pyload/plugins/hoster/JumbofilesCom.py38
-rw-r--r--pyload/plugins/hoster/JunocloudMe.py28
-rw-r--r--pyload/plugins/hoster/Keep2shareCc.py135
-rw-r--r--pyload/plugins/hoster/KickloadCom.py18
-rw-r--r--pyload/plugins/hoster/LemUploadsCom.py18
-rw-r--r--pyload/plugins/hoster/LetitbitNet.py142
-rw-r--r--pyload/plugins/hoster/LinksnappyCom.py76
-rw-r--r--pyload/plugins/hoster/LoadTo.py75
-rw-r--r--pyload/plugins/hoster/LomafileCom.py30
-rw-r--r--pyload/plugins/hoster/LuckyShareNet.py73
-rw-r--r--pyload/plugins/hoster/MediafireCom.py124
-rw-r--r--pyload/plugins/hoster/MegaCoNz.py171
-rw-r--r--pyload/plugins/hoster/MegaDebridEu.py94
-rw-r--r--pyload/plugins/hoster/MegaFilesSe.py18
-rw-r--r--pyload/plugins/hoster/MegaRapidCz.py71
-rw-r--r--pyload/plugins/hoster/MegacrypterCom.py56
-rw-r--r--pyload/plugins/hoster/MegareleaseOrg.py19
-rw-r--r--pyload/plugins/hoster/MegasharesCom.py113
-rw-r--r--pyload/plugins/hoster/MegauploadCom.py18
-rw-r--r--pyload/plugins/hoster/MegavideoCom.py19
-rw-r--r--pyload/plugins/hoster/MovReelCom.py26
-rw-r--r--pyload/plugins/hoster/MultishareCz.py80
-rw-r--r--pyload/plugins/hoster/MyfastfileCom.py47
-rw-r--r--pyload/plugins/hoster/MyvideoDe.py49
-rw-r--r--pyload/plugins/hoster/NahrajCz.py18
-rw-r--r--pyload/plugins/hoster/NarodRu.py60
-rw-r--r--pyload/plugins/hoster/NetloadIn.py294
-rw-r--r--pyload/plugins/hoster/NosuploadCom.py43
-rw-r--r--pyload/plugins/hoster/NovafileCom.py31
-rw-r--r--pyload/plugins/hoster/NowDownloadSx.py64
-rw-r--r--pyload/plugins/hoster/NowVideoSx.py44
-rw-r--r--pyload/plugins/hoster/OboomCom.py145
-rw-r--r--pyload/plugins/hoster/OneFichierCom.py71
-rw-r--r--pyload/plugins/hoster/OronCom.py19
-rw-r--r--pyload/plugins/hoster/OverLoadMe.py84
-rw-r--r--pyload/plugins/hoster/PandaplaNet.py18
-rw-r--r--pyload/plugins/hoster/PornhostCom.py80
-rw-r--r--pyload/plugins/hoster/PornhubCom.py89
-rw-r--r--pyload/plugins/hoster/PotloadCom.py18
-rw-r--r--pyload/plugins/hoster/PremiumTo.py81
-rw-r--r--pyload/plugins/hoster/PremiumizeMe.py56
-rw-r--r--pyload/plugins/hoster/PromptfileCom.py45
-rw-r--r--pyload/plugins/hoster/PrzeklejPl.py18
-rw-r--r--pyload/plugins/hoster/QuickshareCz.py94
-rw-r--r--pyload/plugins/hoster/RPNetBiz.py85
-rw-r--r--pyload/plugins/hoster/RapidfileshareNet.py31
-rw-r--r--pyload/plugins/hoster/RapidgatorNet.py199
-rw-r--r--pyload/plugins/hoster/RapiduNet.py82
-rw-r--r--pyload/plugins/hoster/RarefileNet.py28
-rw-r--r--pyload/plugins/hoster/RealdebridCom.py94
-rw-r--r--pyload/plugins/hoster/RedtubeCom.py62
-rw-r--r--pyload/plugins/hoster/RehostTo.py44
-rw-r--r--pyload/plugins/hoster/RemixshareCom.py61
-rw-r--r--pyload/plugins/hoster/RgHostNet.py26
-rw-r--r--pyload/plugins/hoster/RyushareCom.py81
-rw-r--r--pyload/plugins/hoster/SafesharingEu.py25
-rw-r--r--pyload/plugins/hoster/SecureUploadEu.py23
-rw-r--r--pyload/plugins/hoster/SendmywayCom.py24
-rw-r--r--pyload/plugins/hoster/SendspaceCom.py60
-rw-r--r--pyload/plugins/hoster/Share4webCom.py22
-rw-r--r--pyload/plugins/hoster/Share76Com.py18
-rw-r--r--pyload/plugins/hoster/ShareFilesCo.py18
-rw-r--r--pyload/plugins/hoster/SharebeesCom.py18
-rw-r--r--pyload/plugins/hoster/ShareonlineBiz.py191
-rw-r--r--pyload/plugins/hoster/ShareplaceCom.py89
-rw-r--r--pyload/plugins/hoster/SharingmatrixCom.py19
-rw-r--r--pyload/plugins/hoster/ShragleCom.py19
-rw-r--r--pyload/plugins/hoster/SimplyPremiumCom.py82
-rw-r--r--pyload/plugins/hoster/SimplydebridCom.py64
-rw-r--r--pyload/plugins/hoster/SockshareCom.py20
-rw-r--r--pyload/plugins/hoster/SoundcloudCom.py57
-rw-r--r--pyload/plugins/hoster/SpeedLoadOrg.py18
-rw-r--r--pyload/plugins/hoster/SpeedfileCz.py18
-rw-r--r--pyload/plugins/hoster/SpeedyshareCom.py51
-rw-r--r--pyload/plugins/hoster/StorageTo.py18
-rw-r--r--pyload/plugins/hoster/StreamCz.py71
-rw-r--r--pyload/plugins/hoster/StreamcloudEu.py31
-rw-r--r--pyload/plugins/hoster/TurbobitNet.py173
-rw-r--r--pyload/plugins/hoster/TurbouploadCom.py18
-rw-r--r--pyload/plugins/hoster/TusfilesNet.py35
-rw-r--r--pyload/plugins/hoster/TwoSharedCom.py41
-rw-r--r--pyload/plugins/hoster/UlozTo.py164
-rw-r--r--pyload/plugins/hoster/UloziskoSk.py72
-rw-r--r--pyload/plugins/hoster/UnibytesCom.py74
-rw-r--r--pyload/plugins/hoster/UnrestrictLi.py91
-rw-r--r--pyload/plugins/hoster/UpleaCom.py60
-rw-r--r--pyload/plugins/hoster/UploadStationCom.py19
-rw-r--r--pyload/plugins/hoster/UploadableCh.py90
-rw-r--r--pyload/plugins/hoster/UploadboxCom.py18
-rw-r--r--pyload/plugins/hoster/UploadedTo.py245
-rw-r--r--pyload/plugins/hoster/UploadhereCom.py18
-rw-r--r--pyload/plugins/hoster/UploadheroCom.py81
-rw-r--r--pyload/plugins/hoster/UploadingCom.py104
-rw-r--r--pyload/plugins/hoster/UploadkingCom.py18
-rw-r--r--pyload/plugins/hoster/UpstoreNet.py73
-rw-r--r--pyload/plugins/hoster/UptoboxCom.py34
-rw-r--r--pyload/plugins/hoster/VeehdCom.py81
-rw-r--r--pyload/plugins/hoster/VeohCom.py53
-rw-r--r--pyload/plugins/hoster/VidPlayNet.py26
-rw-r--r--pyload/plugins/hoster/VimeoCom.py75
-rw-r--r--pyload/plugins/hoster/Vipleech4uCom.py18
-rw-r--r--pyload/plugins/hoster/WarserverCz.py18
-rw-r--r--pyload/plugins/hoster/WebshareCz.py62
-rw-r--r--pyload/plugins/hoster/WrzucTo.py52
-rw-r--r--pyload/plugins/hoster/WuploadCom.py19
-rw-r--r--pyload/plugins/hoster/X7To.py18
-rw-r--r--pyload/plugins/hoster/XFileSharingPro.py57
-rw-r--r--pyload/plugins/hoster/XHamsterCom.py129
-rw-r--r--pyload/plugins/hoster/XVideosCom.py28
-rw-r--r--pyload/plugins/hoster/Xdcc.py207
-rw-r--r--pyload/plugins/hoster/YibaishiwuCom.py55
-rw-r--r--pyload/plugins/hoster/YoupornCom.py60
-rw-r--r--pyload/plugins/hoster/YourfilesTo.py87
-rw-r--r--pyload/plugins/hoster/YoutubeCom.py185
-rw-r--r--pyload/plugins/hoster/ZDF.py59
-rw-r--r--pyload/plugins/hoster/ZShareNet.py19
-rw-r--r--pyload/plugins/hoster/ZeveraCom.py42
-rw-r--r--pyload/plugins/hoster/ZippyshareCom.py65
-rw-r--r--pyload/plugins/hoster/__init__.py0
-rw-r--r--pyload/plugins/internal/AbstractExtractor.py109
-rw-r--r--pyload/plugins/internal/Account.py305
-rw-r--r--pyload/plugins/internal/Addon.py169
-rw-r--r--pyload/plugins/internal/BasePlugin.py106
-rw-r--r--pyload/plugins/internal/Captcha.py51
-rw-r--r--pyload/plugins/internal/Container.py66
-rw-r--r--pyload/plugins/internal/Crypter.py107
-rw-r--r--pyload/plugins/internal/DeadCrypter.py32
-rw-r--r--pyload/plugins/internal/DeadHoster.py32
-rw-r--r--pyload/plugins/internal/Hoster.py21
-rw-r--r--pyload/plugins/internal/MultiHoster.py207
-rw-r--r--pyload/plugins/internal/OCR.py315
-rw-r--r--pyload/plugins/internal/SimpleCrypter.py152
-rw-r--r--pyload/plugins/internal/SimpleHoster.py532
-rw-r--r--pyload/plugins/internal/UnRar.py221
-rw-r--r--pyload/plugins/internal/UnZip.py41
-rw-r--r--pyload/plugins/internal/UpdateManager.py300
-rw-r--r--pyload/plugins/internal/XFSAccount.py155
-rw-r--r--pyload/plugins/internal/XFSCrypter.py29
-rw-r--r--pyload/plugins/internal/XFSHoster.py345
-rw-r--r--pyload/plugins/internal/__init__.py0
-rw-r--r--pyload/plugins/ocr/GigasizeCom.py24
-rw-r--r--pyload/plugins/ocr/LinksaveIn.py158
-rw-r--r--pyload/plugins/ocr/NetloadIn.py29
-rw-r--r--pyload/plugins/ocr/ShareonlineBiz.py39
-rw-r--r--pyload/plugins/ocr/__init__.py0
-rw-r--r--pyload/remote/ClickAndLoadBackend.py156
-rw-r--r--pyload/remote/SocketBackend.py25
-rw-r--r--pyload/remote/ThriftBackend.py42
-rw-r--r--pyload/remote/__init__.py3
-rw-r--r--pyload/remote/socketbackend/__init__.py0
-rw-r--r--pyload/remote/socketbackend/create_ttypes.py83
-rw-r--r--pyload/remote/thriftbackend/Processor.py77
-rw-r--r--pyload/remote/thriftbackend/Protocol.py30
-rw-r--r--pyload/remote/thriftbackend/Socket.py129
-rw-r--r--pyload/remote/thriftbackend/ThriftClient.py87
-rw-r--r--pyload/remote/thriftbackend/ThriftTest.py91
-rw-r--r--pyload/remote/thriftbackend/Transport.py37
-rw-r--r--pyload/remote/thriftbackend/__init__.py0
-rw-r--r--pyload/remote/thriftbackend/pyload.thrift337
-rw-r--r--pyload/remote/thriftbackend/thriftgen/__init__.py0
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote570
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py5533
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/__init__.py3
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/constants.py10
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py834
-rw-r--r--pyload/utils/JsEngine.py242
-rw-r--r--pyload/utils/__init__.py241
-rw-r--r--pyload/utils/packagetools.py155
-rw-r--r--pyload/utils/printer.py15
-rw-r--r--pyload/utils/pylgettext.py60
-rw-r--r--pyload/webui/__init__.py131
-rw-r--r--pyload/webui/app/__init__.py3
-rw-r--r--pyload/webui/app/api.py101
-rw-r--r--pyload/webui/app/cnl.py167
-rw-r--r--pyload/webui/app/json.py311
-rw-r--r--pyload/webui/app/pyload.py530
-rw-r--r--pyload/webui/app/utils.py123
-rw-r--r--pyload/webui/filters.py61
-rw-r--r--pyload/webui/middlewares.py132
-rw-r--r--pyload/webui/servers/lighttpd_default.conf154
-rw-r--r--pyload/webui/servers/nginx_default.conf87
-rw-r--r--pyload/webui/themes/bright/css/MooDialog.css85
-rw-r--r--pyload/webui/themes/bright/css/bright.css866
-rw-r--r--pyload/webui/themes/bright/css/log.css72
-rw-r--r--pyload/webui/themes/bright/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/bright/css/window.css70
-rw-r--r--pyload/webui/themes/bright/img/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/bright/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/bright/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/bright/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/bright/img/arrow_refresh.pngbin0 -> 119032 bytes
-rw-r--r--pyload/webui/themes/bright/img/arrow_right.pngbin0 -> 136967 bytes
-rw-r--r--pyload/webui/themes/bright/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/bright/img/cog.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_add.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_add_blue.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_cancel.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_cancel_blue.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_pause.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_pause_blue.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_play.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_play_blue.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_stop.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/bright/img/control_stop_blue.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/tab-background.pngbin0 -> 179 bytes
-rw-r--r--pyload/webui/themes/bright/img/default/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/bright/img/delete.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/bright/img/error.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/bright/img/folder.pngbin0 -> 134669 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-login.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-collector.pngbin0 -> 134985 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-config.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-development.pngbin0 -> 135818 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-download.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-home.pngbin0 -> 139387 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-index.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-news.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-queue.pngbin0 -> 136269 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-menu-wiki.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/bright/img/head-search-noshadow.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/bright/img/notice.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/bright/img/package_go.pngbin0 -> 136299 bytes
-rw-r--r--pyload/webui/themes/bright/img/page-tools-backlinks.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/page-tools-edit.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/page-tools-revisions.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/pencil.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/reconnect.pngbin0 -> 3063 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_None.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_downloading.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_failed.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_finished.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_offline.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_proc.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_queue.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/status_waiting.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/success.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/bright/img/user-actions-logout.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/user-actions-profile.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/bright/img/user-info.pngbin0 -> 3080 bytes
-rw-r--r--pyload/webui/themes/bright/js/render/admin.coffee58
-rw-r--r--pyload/webui/themes/bright/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/bright/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/bright/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/bright/js/render/filemanager.js291
-rw-r--r--pyload/webui/themes/bright/js/render/package.js376
-rw-r--r--pyload/webui/themes/bright/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/bright/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/bright/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/bright/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/bright/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/bright/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/bright/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/bright/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/bright/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/bright/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/bright/js/static/purr.js309
-rw-r--r--pyload/webui/themes/bright/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/bright/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/bright/js/static/tinytab.min.js1
-rw-r--r--pyload/webui/themes/bright/tml/admin.html98
-rw-r--r--pyload/webui/themes/bright/tml/base.html180
-rw-r--r--pyload/webui/themes/bright/tml/captcha.html42
-rw-r--r--pyload/webui/themes/bright/tml/downloads.html29
-rw-r--r--pyload/webui/themes/bright/tml/filemanager.html78
-rw-r--r--pyload/webui/themes/bright/tml/folder.html15
-rw-r--r--pyload/webui/themes/bright/tml/home.html266
-rw-r--r--pyload/webui/themes/bright/tml/info.html81
-rw-r--r--pyload/webui/themes/bright/tml/login.html36
-rw-r--r--pyload/webui/themes/bright/tml/logout.html9
-rw-r--r--pyload/webui/themes/bright/tml/logs.html41
-rw-r--r--pyload/webui/themes/bright/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/bright/tml/queue.html104
-rw-r--r--pyload/webui/themes/bright/tml/settings.html204
-rw-r--r--pyload/webui/themes/bright/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/bright/tml/window.html46
-rw-r--r--pyload/webui/themes/dark/css/MooDialog.css94
-rw-r--r--pyload/webui/themes/dark/css/dark.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/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/dark/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/dark/img/dark-bg.jpgbin0 -> 40930 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/arrow_refresh.pngbin0 -> 685 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/arrow_right.pngbin0 -> 349 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/cog.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_add.pngbin0 -> 446 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_add_blue.pngbin0 -> 845 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_cancel.pngbin0 -> 3349 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_cancel_blue.pngbin0 -> 787 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_pause.pngbin0 -> 598 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_pause_blue.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_play.pngbin0 -> 592 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_play_blue.pngbin0 -> 717 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_stop.pngbin0 -> 403 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_stop_blue.pngbin0 -> 695 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/delete.pngbin0 -> 715 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/error.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/folder.pngbin0 -> 537 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-login.pngbin0 -> 1288 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-collector.pngbin0 -> 1953 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-config.pngbin0 -> 1802 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-development.pngbin0 -> 876 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-download.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-home.pngbin0 -> 920 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-index.pngbin0 -> 482 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-news.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-queue.pngbin0 -> 2629 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-wiki.pngbin0 -> 1204 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-search-noshadow.pngbin0 -> 1187 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/notice.pngbin0 -> 778 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/package_go.pngbin0 -> 898 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-backlinks.pngbin0 -> 540 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-edit.pngbin0 -> 574 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-revisions.pngbin0 -> 603 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/pencil.pngbin0 -> 450 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/reconnect.pngbin0 -> 755 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_None.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_downloading.pngbin0 -> 943 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_failed.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_finished.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_offline.pngbin0 -> 700 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_proc.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_queue.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_waiting.pngbin0 -> 889 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/success.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-actions-logout.pngbin0 -> 799 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-actions-profile.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-info.pngbin0 -> 3963 bytes
-rw-r--r--pyload/webui/themes/dark/img/pyload-logo.pngbin0 -> 6947 bytes
-rw-r--r--pyload/webui/themes/dark/img/tab-background.pngbin0 -> 3044 bytes
-rw-r--r--pyload/webui/themes/dark/js/render/admin.coffee58
-rw-r--r--pyload/webui/themes/dark/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/dark/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/dark/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/dark/js/render/package.js376
-rw-r--r--pyload/webui/themes/dark/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/dark/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/dark/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/dark/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/dark/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/dark/js/static/purr.js309
-rw-r--r--pyload/webui/themes/dark/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/dark/js/static/tinytab.min.js1
-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/MooDialog.css91
-rw-r--r--pyload/webui/themes/default/css/default.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/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-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.pngbin0 -> 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.pngbin0 -> 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.pngbin0 -> 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/render/admin.coffee58
-rw-r--r--pyload/webui/themes/default/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/default/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/default/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/default/js/render/package.js376
-rw-r--r--pyload/webui/themes/default/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/default/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/default/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/default/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/default/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/default/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/default/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/default/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/default/js/static/purr.js309
-rw-r--r--pyload/webui/themes/default/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/default/js/static/tinytab.min.js1
-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.css84
-rw-r--r--pyload/webui/themes/flat/css/flat.css863
-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.css73
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-warning.pngbin0 -> 1651 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/button.pngbin0 -> 569 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/default/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/tab-background.pngbin0 -> 179 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/flat/img/delete.pngbin0 -> 117658 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/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-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/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/pencil.pngbin0 -> 138112 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/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/render/admin.coffee58
-rw-r--r--pyload/webui/themes/flat/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/flat/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/flat/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/flat/js/render/package.js376
-rw-r--r--pyload/webui/themes/flat/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/flat/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/flat/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/flat/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/flat/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/flat/js/static/purr.js309
-rw-r--r--pyload/webui/themes/flat/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/flat/js/static/tinytab.min.js1
-rw-r--r--pyload/webui/themes/flat/tml/admin.html98
-rw-r--r--pyload/webui/themes/flat/tml/base.html177
-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/folder.html15
-rw-r--r--pyload/webui/themes/flat/tml/home.html263
-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
948 files changed, 101809 insertions, 0 deletions
diff --git a/pyload/Core.py b/pyload/Core.py
new file mode 100644
index 000000000..e6f4a0b5a
--- /dev/null
+++ b/pyload/Core.py
@@ -0,0 +1,633 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay, sebnapi, spoob
+# @version: v0.4.10
+
+CURRENT_VERSION = '0.4.10'
+
+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 import InitHomeDir
+from pyload.manager.AccountManager import AccountManager
+from pyload.manager.CaptchaManager import CaptchaManager
+from pyload.config.Parser import ConfigParser
+from pyload.manager.PluginManager import PluginManager
+from pyload.manager.event.PullEvents import PullManager
+from pyload.network.RequestFactory import RequestFactory
+from pyload.manager.thread.ServerThread import WebServer
+from pyload.manager.event.Scheduler import Scheduler
+from pyload.utils.JsEngine import JsEngine
+from pyload import remote
+from pyload.manager.RemoteManager 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", 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 pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ s.set_user()
+ exit()
+ elif option in ("-s", "--setup"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ s.start()
+ exit()
+ elif option == "--changedir":
+ from pyload.config.Setup import SetupAssistant as 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-2014 the pyLoad Team" % CURRENT_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()
+ 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 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 = CURRENT_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(pypath, 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['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 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['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 ./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.AddonManager import AddonManager
+ from pyload.manager.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.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["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.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['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 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 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.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 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():
+ 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/InitHomeDir.py b/pyload/InitHomeDir.py
new file mode 100644
index 000000000..8ec9a92ce
--- /dev/null
+++ b/pyload/InitHomeDir.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# @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, "pyload", "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:
+ for aa in argv:
+ if aa.startswith("--configdir="):
+ configdir = aa.replace("--configdir=", "", 1).strip()
+elif path.exists(path.join(pypath, "pyload", "config", "configdir")):
+ f = open(path.join(pypath, "pyload", "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/pyload/__init__.py b/pyload/__init__.py
new file mode 100644
index 000000000..154ab22b6
--- /dev/null
+++ b/pyload/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+__all__ = ["__status_code__", "__status__", "__version_info__", "__version__", "__author_name__", "__author_mail__", "__license__"]
+
+__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
+
+__version_info__ = (0, 4, 10)
+__version__ = '.'.join(map(str, __version_info__))
+
+__author_name__ = "pyLoad Team"
+__author_mail__ = "admin@pyload.org"
+
+__license__ = "GNU Affero General Public License v3"
diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py
new file mode 100644
index 000000000..76cba0d50
--- /dev/null
+++ b/pyload/api/__init__.py
@@ -0,0 +1,973 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from base64 import standard_b64encode
+from os.path import join
+from time import time
+import re
+
+from urlparse import urlparse
+
+from pyload.datatype.PyFile import PyFile
+from pyload.utils.packagetools import parseNames
+from network.RequestFactory import getURL
+from remote import activated
+from utils import compare_time, freeSpace, html_unescape, save_filename
+
+if activated:
+ try:
+ from lib.thrift.protocol import TBase
+ from remote.thriftbackend.thriftgen.pyload.ttypes import *
+ from 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 category:
+ :param option:
+ :param value: new config value
+ :param section: 'plugin' or 'core
+ """
+ self.core.addonManager.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 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['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 = urlparse(html_unescape(name)).path.split("/")[-1]
+ else:
+ folder = ""
+
+ folder = save_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
+ """
+ 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
+ """
+ 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
+ """
+ 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
+ """
+ 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 []
+
+ @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=None):
+ """Changes pw/options for specific account."""
+ self.core.accountManager.updateAccount(plugin, account, password, options or {})
+
+ @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["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..81385bf9f
--- /dev/null
+++ b/pyload/api/types.py
@@ -0,0 +1,381 @@
+# -*- 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..f7e30b64e
--- /dev/null
+++ b/pyload/cli/AddPackage.py
@@ -0,0 +1,50 @@
+# -*- 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..1ae57c290
--- /dev/null
+++ b/pyload/cli/Cli.py
@@ -0,0 +1,571 @@
+# -*- 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, abspath, 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
+
+if os.name == "nt":
+ enc = "cp850"
+else:
+ enc = "utf8"
+
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+from pyload import InitHomeDir
+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 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 "pyLoad CLI Copyright (c) 2008-2014 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..4aab3f0e2
--- /dev/null
+++ b/pyload/cli/Handler.py
@@ -0,0 +1,32 @@
+# -*- 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..ca1070113
--- /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, 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 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, 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/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..65e15ca85
--- /dev/null
+++ b/pyload/config/Parser.py
@@ -0,0 +1,333 @@
+# -*- 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, 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"):
+ 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, "pyload", "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 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"""
+
+ 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 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 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]
+
+
+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..7f159c01a
--- /dev/null
+++ b/pyload/config/Setup.py
@@ -0,0 +1,538 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+import pyload.utils.pylgettext as gettext
+
+from getpass import getpass
+from os import makedirs
+from os.path import abspath, dirname, exists, join
+from subprocess import PIPE, call
+
+from pyload.utils import get_console_encoding, versiontuple
+
+
+class SetupAssistant(object):
+ """ pyLoads initial setup configuration assistant """
+
+ 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 setup language", "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 pyload.manager.thread import ServerThread
+ # ServerThread.setup = self
+ # import pyload.webui as webinterface
+ # webinterface.run_simple()
+ # return False
+ # except Exception, e:
+ # print "Setup failed with this error: ", e
+ # print "Falling back to commandline setup."
+
+ print
+ print
+ print _("Welcome to the pyLoad Configuration Assistant.")
+ 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"))
+
+ string = ""
+
+ for av in avail:
+ string += ", " + av
+
+ print _("AVAILABLE FEATURES:") + string[1:]
+ 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.")
+
+ print
+ con = self.ask(_("Continue with setup?"), self.yes, bool=True)
+
+ if not con:
+ return False
+
+ print
+ print
+ print _("CURRENT CONFIG PATH: %s") % abspath("")
+ 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.")
+ path = self.ask(_("Do you want to change the config path?"), self.no, bool=True)
+ if path:
+ print
+ 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:
+ 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"""
+
+ print _("## System Information ##")
+ print
+ print _("Platform: %s") % sys.platform
+ print _("Operating System: %s") % os.name
+ print _("Python: %s") % sys.version.replace("\n", "")
+ print
+ print
+
+ print _("## System Check ##")
+ print
+
+ 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("PIL.Image")
+ self.print_dep("PIL/Pillow", 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
+
+ try:
+ import jinja2
+
+ v = jinja2.__version__
+ if v and versiontuple(v) < (2, 5, 0):
+ jinja = False
+ else:
+ jinja = True
+ except Exception:
+ jinja = False
+
+ jinja = self.print_dep("jinja2", jinja)
+
+ beaker = self.check_module("beaker")
+ self.print_dep("beaker", beaker)
+
+ bjoern = self.check_module("bjoern")
+ self.print_dep("bjoern", bjoern)
+
+ web = sqlite and beaker
+
+ from pyload.utils.JsEngine import JsEngine
+ js = True if JsEngine.find() else False
+ self.print_dep(_("JS engine"), js)
+
+ if not jinja:
+ print
+ print
+ print _("WARNING: 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 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 from outside your local network (ex. internet).")
+ print
+ 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.no, bool=True)
+
+ print
+ langs = self.config.getMetaData("general", "language")
+ self.config["general"]["language"] = self.ask(_("Choose pyLoad language"), "en", langs["type"].split(";"))
+
+ print
+ self.config["general"]["download_folder"] = self.ask(_("Download folder"), "Downloads")
+ print
+ self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3")
+ print
+ 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 _("## 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 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 is not working, if you notice problems with the webinterface")
+ print _("come back here and change the builtin server to the threaded one here.")
+
+ if os.name == "nt":
+ servers = ["builtin", "threaded", "fastcgi"]
+ default = "threaded"
+ else:
+ servers = ["builtin", "threaded", "fastcgi", "lightweight"]
+ default = "lightweight" if self.check_module("bjoern") else "builtin"
+
+ self.config["webinterface"]["server"] = self.ask(_("Server"), default, 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.")
+
+ 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 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 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 config path, current configuration will not be transfered!")
+ path = self.ask(_("CONFIG PATH"), abspath(""))
+ try:
+ path = join(pypath, path)
+ if not exists(path):
+ makedirs(path)
+ f = open(join(pypath, "pyload", "config", "configdir"), "wb")
+ f.write(path)
+ f.close()
+ print
+ print
+ print _("pyLoad config path changed, setup will now close!")
+ print
+ print
+ raw_input(_("Press Enter to exit."))
+ sys.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 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:
+ # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS)
+ sys.stdout.write(_("Password: "))
+ p1 = getpass("")
+
+ if len(p1) < pwlen:
+ print _("Password too short! Use at least %s symbols." % pwlen)
+ continue
+ elif not p1.isalnum():
+ print _("Password must be alphanumeric.")
+ 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"
+ 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")
+ continue
+
+ if not answers:
+ return input
+
+ else:
+ if input in answers:
+ return input
+ else:
+ print _("Invalid Input")
diff --git a/pyload/config/default.conf b/pyload/config/default.conf
new file mode 100644
index 000000000..d1f39f481
--- /dev/null
+++ b/pyload/config/default.conf
@@ -0,0 +1,64 @@
+version: 1
+
+remote - "Remote":
+ int port : "Port" = 7227
+ ip listenaddr : "Address" = 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 - "Web UI":
+ 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
+ default;dark;flat theme : "Theme" = 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
+ 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/pyload/database/DatabaseBackend.py b/pyload/database/DatabaseBackend.py
new file mode 100644
index 000000000..4b63dd284
--- /dev/null
+++ b/pyload/database/DatabaseBackend.py
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+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 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 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"):
+ 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 Exception:
+ 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 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]
+ 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 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/FileDatabase.py b/pyload/database/FileDatabase.py
new file mode 100644
index 000000000..592b05b6a
--- /dev/null
+++ b/pyload/database/FileDatabase.py
@@ -0,0 +1,875 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from threading import RLock
+from time import time
+
+from pyload.utils import formatSize, lock
+from pyload.manager.event.PullEvents import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
+from pyload.datatype.PyPackage import PyPackage
+from pyload.datatype.PyFile import PyFile
+from pyload.database import style, DatabaseBackend
+
+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("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.addonManager.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.addonManager.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.addonManager.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 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 = []
+ 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(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, (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 range(len(links))]
+ links = [(x[0], x[0], (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': 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)
diff --git a/pyload/database/StorageDatabase.py b/pyload/database/StorageDatabase.py
new file mode 100644
index 000000000..75e166d39
--- /dev/null
+++ b/pyload/database/StorageDatabase.py
@@ -0,0 +1,34 @@
+# -*- 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,))
+ 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/pyload/database/UserDatabase.py b/pyload/database/UserDatabase.py
new file mode 100644
index 000000000..7e89d7df4
--- /dev/null
+++ b/pyload/database/UserDatabase.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from hashlib import sha1
+import random
+
+from DatabaseBackend import DatabaseBackend
+from DatabaseBackend import 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 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/pyload/database/__init__.py b/pyload/database/__init__.py
new file mode 100644
index 000000000..5f287a47f
--- /dev/null
+++ b/pyload/database/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from DatabaseBackend import DatabaseBackend
+from DatabaseBackend import style
+
+from FileDatabase import FileHandler
+from UserDatabase import UserMethods
+from StorageDatabase import StorageMethods
diff --git a/pyload/datatype/PyFile.py b/pyload/datatype/PyFile.py
new file mode 100644
index 000000000..173203a8d
--- /dev/null
+++ b/pyload/datatype/PyFile.py
@@ -0,0 +1,270 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from pyload.manager.event.PullEvents 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", "plugin",
+ "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.plugin = 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/PyPackage.py b/pyload/datatype/PyPackage.py
new file mode 100644
index 000000000..dbd8c8c9b
--- /dev/null
+++ b/pyload/datatype/PyPackage.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from pyload.manager.event.PullEvents 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..e69de29bb
--- /dev/null
+++ b/pyload/datatype/__init__.py
diff --git a/pyload/manager/AccountManager.py b/pyload/manager/AccountManager.py
new file mode 100644
index 000000000..cd9d51da1
--- /dev/null
+++ b/pyload/manager/AccountManager.py
@@ -0,0 +1,191 @@
+# -*- 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.PullEvents 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.__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/AddonManager.py b/pyload/manager/AddonManager.py
new file mode 100644
index 000000000..ced0611bc
--- /dev/null
+++ b/pyload/manager/AddonManager.py
@@ -0,0 +1,276 @@
+# -*- 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.AddonThread import AddonThread
+from pyload.manager.PluginManager import literal_eval
+from 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
+ ===================== ============== ==================================
+ 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):
+ __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)
+
+ 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].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 = 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.addonPlugins:
+ try:
+ # hookClass = getattr(plugin, plugin.__name__)
+ if self.core.config.getPlugin(pluginname, "activated"):
+ pluginClass = self.core.pluginManager.loadClass("addon", 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 addons: %s") % ", ".join(sorted(active)))
+ self.core.log.info(_("Deactivated addons: %s") % ", ".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, plugin):
+ # check if already loaded
+ for inst in self.plugins:
+ if inst.__name__ == plugin:
+ return
+
+ pluginClass = self.core.pluginManager.loadClass("addon", plugin)
+
+ if not pluginClass:
+ return
+
+ self.core.log.debug("Plugin loaded: %s" % plugin)
+
+ plugin = pluginClass(self.core, self)
+ self.plugins.append(plugin)
+ self.pluginMap[pluginClass.__name__] = plugin
+
+ # call core Ready
+ t = Thread(target=plugin.coreReady)
+ t.setDaemon(True)
+ t.start()
+
+
+ def deactivateAddon(self, plugin):
+ addon = None
+ for inst in self.plugins:
+ if inst.__name__ == plugin:
+ addon = inst
+
+ if not addon:
+ return
+
+ self.core.log.debug("Plugin unloaded: %s" % plugin)
+
+ addon.unload()
+
+ #remove periodic call
+ self.core.log.debug("Removed callback %s" % self.core.scheduler.removeJob(addon.cb))
+ self.plugins.remove(addon)
+ del self.pluginMap[addon.__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():
+ plugin.downloadFinished(pyfile)
+
+ self.dispatchEvent("downloadFinished", pyfile)
+
+ @lock
+ @try_catch
+ def downloadFailed(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFailed(pyfile)
+
+ self.dispatchEvent("downloadFailed", pyfile)
+
+ @lock
+ def packageFinished(self, package):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ 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):
+ 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 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.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/CaptchaManager.py b/pyload/manager/CaptchaManager.py
new file mode 100644
index 000000000..1a1d2f6bf
--- /dev/null
+++ b/pyload/manager/CaptchaManager.py
@@ -0,0 +1,138 @@
+# -*- 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.newCaptchaTask(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/PluginManager.py b/pyload/manager/PluginManager.py
new file mode 100644
index 000000000..aa3668657
--- /dev/null
+++ b/pyload/manager/PluginManager.py
@@ -0,0 +1,402 @@
+# -*- 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 SafeEval import const_eval as literal_eval
+
+
+class PluginManager(object):
+ ROOT = "pyload.plugins."
+ USERROOT = "userplugins."
+ TYPES = []
+
+ 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 initTYPES(self):
+ rootdir = join(pypath, "pyload", "plugins")
+ userdir = "userplugins"
+
+ tmpset = set()
+ for p in (rootdir, userdir):
+ tmpset += set([d for d in listdir(p) if isdir(join(p, d))])
+
+ self.TYPES = list(tmpset)
+
+
+ def createIndex(self):
+ """create information for all plugins available"""
+
+ sys.path.append(abspath(""))
+
+ self.initTYPES()
+
+ if not self.TYPES:
+ self.log.critical(_("No TYPES, no fun!"))
+
+ for type in set(self.TYPES):
+ self.plugins[type] = self.parse(type)
+ setattr(self, "%sPlugins" % type, self.plugins[type])
+
+ self.plugins['addon'] = self.addonPlugins.extend(self.hookPlugins)
+
+ 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", "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("_"):
+
+ try:
+ with open(join(pfolder, f)) as data:
+ content = data.read()
+
+ except IOError, e:
+ self.core.log.error(str(e))
+ continue
+
+ if f.endswith("_25.pyc") and version_info[0:2] != (2, 5): #@TODO: Remove in 0.4.10
+ continue
+
+ elif f.endswith("_26.pyc") and version_info[0:2] != (2, 6): #@TODO: Remove in 0.4.10
+ continue
+
+ elif f.endswith("_27.pyc") and version_info[0:2] != (2, 7): #@TODO: Remove in 0.4.10
+ 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
+
+ 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'] = True if rootplugins else False
+ 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(name)
+ 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", False if folder in ("addon", "hook") else True])
+
+ self.core.config.addPluginConfig(name, 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(name, 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
+
+ if last and last[2]['re'].match(url):
+ res.append((url, last[0], last[1]))
+ continue
+
+ for type in self.TYPES:
+ for name, plugin in self.plugins[type]:
+
+ m = None
+ 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': name, 'type': type})
+
+ if m:
+ res.append((url, type, name))
+ last = (type, name, plugin)
+ break
+ else:
+ res.append((url, "internal", "BasePlugin"))
+
+ return res
+
+
+ def findPlugin(self, type, name):
+ if type not in self.plugins:
+ return None
+
+ elif 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 plugin: [%(type)s] %(name)s" % {'name': plugin, '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 True if self.reloadPlugins(type_plugin) else False
diff --git a/pyload/manager/RemoteManager.py b/pyload/manager/RemoteManager.py
new file mode 100644
index 000000000..910881164
--- /dev/null
+++ b/pyload/manager/RemoteManager.py
@@ -0,0 +1,76 @@
+# -*- 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["remote"]["listenaddr"]
+ port = self.core.config["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/ThreadManager.py b/pyload/manager/ThreadManager.py
new file mode 100644
index 000000000..b403fbf8d
--- /dev/null
+++ b/pyload/manager/ThreadManager.py
@@ -0,0 +1,300 @@
+# -*- 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 import DecrypterThread, DownloadThread, InfoThread
+from pyload.datatype.PyFile 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 range(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["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.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['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE)
+ except Exception:
+ self.core.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.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 range(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.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.core.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.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..e69de29bb
--- /dev/null
+++ b/pyload/manager/__init__.py
diff --git a/pyload/manager/event/PullEvents.py b/pyload/manager/event/PullEvents.py
new file mode 100644
index 000000000..20897290e
--- /dev/null
+++ b/pyload/manager/event/PullEvents.py
@@ -0,0 +1,104 @@
+# -*- 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/event/Scheduler.py b/pyload/manager/event/Scheduler.py
new file mode 100644
index 000000000..fd428a956
--- /dev/null
+++ b/pyload/manager/event/Scheduler.py
@@ -0,0 +1,126 @@
+# -*- 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..e69de29bb
--- /dev/null
+++ b/pyload/manager/event/__init__.py
diff --git a/pyload/manager/thread/AddonThread.py b/pyload/manager/thread/AddonThread.py
new file mode 100644
index 000000000..c699a93f9
--- /dev/null
+++ b/pyload/manager/thread/AddonThread.py
@@ -0,0 +1,74 @@
+# -*- 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.PyFile import PyFile
+from pyload.manager.thread.PluginThread import PluginThread
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_join
+from pyload.api import OnlineStatus
+
+
+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/DecrypterThread.py b/pyload/manager/thread/DecrypterThread.py
new file mode 100644
index 000000000..1395fcd2f
--- /dev/null
+++ b/pyload/manager/thread/DecrypterThread.py
@@ -0,0 +1,105 @@
+# -*- 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.PyFile import PyFile
+from pyload.manager.thread.PluginThread import PluginThread
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_join
+from pyload.api import OnlineStatus
+
+
+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") % pyfile.name)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ except NotImplementedError:
+ self.m.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.log.warning(_("Download is offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.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.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ if self.m.core.debug:
+ print_exc()
+ return
+
+ except Retry:
+ self.m.log.info(_("Retrying %s") % pyfile.name)
+ retry = True
+ return self.run()
+
+ except Exception, e:
+ pyfile.setStatus("failed")
+ self.m.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/DownloadThread.py b/pyload/manager/thread/DownloadThread.py
new file mode 100644
index 000000000..7b093f8f7
--- /dev/null
+++ b/pyload/manager/thread/DownloadThread.py
@@ -0,0 +1,217 @@
+# -*- 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.PyFile import PyFile
+from pyload.manager.thread.PluginThread import PluginThread
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_join
+from pyload.api import OnlineStatus
+
+
+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.addonManager.downloadPreparing(pyfile)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ self.m.log.info(_("Download finished: %s") % pyfile.name)
+ self.m.core.addonManager.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 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.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
+
+ 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.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.addonManager.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.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/InfoThread.py b/pyload/manager/thread/InfoThread.py
new file mode 100644
index 000000000..f8ff34f7b
--- /dev/null
+++ b/pyload/manager/thread/InfoThread.py
@@ -0,0 +1,228 @@
+# -*- 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.PyFile import PyFile
+from pyload.manager.thread.PluginThread import PluginThread
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_join
+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 data:
+ try:
+ plugins[plugintype][pluginname].append(url)
+ except Exception:
+ plugins[plugintype][pluginname] = [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 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/PluginThread.py b/pyload/manager/thread/PluginThread.py
new file mode 100644
index 000000000..b27210e31
--- /dev/null
+++ b/pyload/manager/thread/PluginThread.py
@@ -0,0 +1,130 @@
+# -*- 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.PyFile import PyFile
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_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), safe_join(pyfile.pluginname, f))
+ except Exception:
+ pass
+
+ info = zipfile.ZipInfo(safe_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()
diff --git a/pyload/manager/thread/ServerThread.py b/pyload/manager/thread/ServerThread.py
new file mode 100644
index 000000000..a8b95cd56
--- /dev/null
+++ b/pyload/manager/thread/ServerThread.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+
+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 pyload.webui as webinterface
+ global webinterface
+
+ reset = False
+
+ if self.https and (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 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 to pyload/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"
+ else:
+ 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 == "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})
+ 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/pyload/manager/thread/__init__.py b/pyload/manager/thread/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/manager/thread/__init__.py
diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py
new file mode 100644
index 000000000..e78d24688
--- /dev/null
+++ b/pyload/network/Browser.py
@@ -0,0 +1,132 @@
+# -*- 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
diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py
new file mode 100644
index 000000000..047329ad8
--- /dev/null
+++ b/pyload/network/Bucket.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from time import time
+from threading import Lock
+
+class Bucket(object):
+ 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/pyload/network/CookieJar.py b/pyload/network/CookieJar.py
new file mode 100644
index 000000000..4e877b605
--- /dev/null
+++ b/pyload/network/CookieJar.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from time import time
+
+class CookieJar(object):
+ 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/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py
new file mode 100644
index 000000000..3312d10d6
--- /dev/null
+++ b/pyload/network/HTTPChunk.py
@@ -0,0 +1,294 @@
+# -*- 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 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 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.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..559806459
--- /dev/null
+++ b/pyload/network/HTTPDownload.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+# @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 pyload.plugins.Plugin import Abort
+from pyload.utils import safe_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={}, 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 = True
+ 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 = safe_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 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
diff --git a/pyload/network/HTTPRequest.py b/pyload/network/HTTPRequest.py
new file mode 100644
index 000000000..2de6ff52b
--- /dev/null
+++ b/pyload/network/HTTPRequest.py
@@ -0,0 +1,302 @@
+# -*- 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.plugins.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, 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, 60)
+ self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
+ 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; 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, 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/RequestFactory.py b/pyload/network/RequestFactory.py
new file mode 100644
index 000000000..fd3ca21fe
--- /dev/null
+++ b/pyload/network/RequestFactory.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+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(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["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["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/pyload/network/XDCCRequest.py b/pyload/network/XDCCRequest.py
new file mode 100644
index 000000000..004f2d07f
--- /dev/null
+++ b/pyload/network/XDCCRequest.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# @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 pyload.plugins.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, 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/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/plugins/Plugin.py b/pyload/plugins/Plugin.py
new file mode 100644
index 000000000..ec13c54f4
--- /dev/null
+++ b/pyload/plugins/Plugin.py
@@ -0,0 +1,753 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+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 traceback import print_exc
+from urlparse import urlparse
+
+from pyload.utils import fs_decode, fs_encode, safe_filename, safe_join
+
+
+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(a).strip() for a in args if a])
+ logger = getattr(self.core.log, type)
+ logger("%s: %s" % (self.__name__, 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)
+
+
+ #: 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.__name__, option, value)
+
+
+ #: Deprecated method
+ def getConf(self, option):
+ """ see `getConfig` """
+ return self.getConfig(option)
+
+
+ def getConfig(self, option):
+ """ Returns config value for current plugin
+
+ :param option:
+ :return:
+ """
+ return self.core.config.getPlugin(self.__name__, option)
+
+
+ def setStorage(self, key, value):
+ """ Saves a value persistently to the database """
+ self.core.db.setStorage(self.__name__, key, value)
+
+
+ def store(self, key, value):
+ """ same as `setStorage` """
+ self.core.db.setStorage(self.__name__, key, value)
+
+
+ def getStorage(self, key=None, default=None):
+ """ Retrieves saved value or dict of all saved entries if key is None """
+ if key:
+ 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"
+ __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.__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
+
+ #: 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['download']['chunks']
+ return min(self.core.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 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.__name__, id, imgtype)), "wb") as tmpCaptcha:
+ tmpCaptcha.write(img)
+
+ has_plugin = self.__name__ in self.core.pluginManager.ocrPlugins
+
+ if self.core.captcha:
+ Ocr = self.core.pluginManager.loadClass("ocr", self.__name__)
+ 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 = 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 = safe_join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
+ try:
+ if not exists(join("tmp", self.__name__)):
+ makedirs(join("tmp", self.__name__))
+
+ 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 = 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")
+
+ download_folder = self.core.config['general']['download_folder']
+
+ location = safe_join(download_folder, self.pyfile.package().folder)
+
+ if not exists(location):
+ try:
+ makedirs(location, int(self.core.config['permission']['folder'], 8))
+
+ if self.core.config['permission']['change_dl'] and os.name != "nt":
+ uid = getpwnam(self.core.config['permission']['user'])[2]
+ gid = getgrnam(self.core.config['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("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 newname:
+ newname = 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['permission']['change_file']:
+ try:
+ chmod(fs_filename, int(self.core.config['permission']['file'], 8))
+ except Exception, e:
+ self.logWarning(_("Setting file mode failed"), e)
+
+ if self.core.config['permission']['change_dl'] and os.name != "nt":
+ try:
+ uid = getpwnam(self.core.config['permission']['user'])[2]
+ gid = getgrnam(self.core.config['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['general']['download_folder']
+ location = safe_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.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/plugins/README.md b/pyload/plugins/README.md
new file mode 100644
index 000000000..fa2a4c5b2
--- /dev/null
+++ b/pyload/plugins/README.md
@@ -0,0 +1,16 @@
+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/pyload/plugins/__init__.py b/pyload/plugins/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/__init__.py
diff --git a/pyload/plugins/account/AlldebridCom.py b/pyload/plugins/account/AlldebridCom.py
new file mode 100644
index 000000000..a3e8ad659
--- /dev/null
+++ b/pyload/plugins/account/AlldebridCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+import xml.dom.minidom as dom
+
+from time import time
+from urllib import urlencode
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugins.internal.Account import Account
+
+
+class AlldebridCom(Account):
+ __name__ = "AlldebridCom"
+ __type__ = "account"
+ __version__ = "0.22"
+
+ __description__ = """AllDebrid.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("http://www.alldebrid.com/account/")
+ soup = BeautifulSoup(page)
+ #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() + 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)
+ page = req.load("http://www.alldebrid.com/api.php",
+ get={'action': "info_user", 'login': user, 'pw': data['password']})
+ self.logDebug(page)
+ xml = dom.parseString(page)
+ exp_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):
+ urlparams = urlencode({'action': 'login', 'login_login': user, 'login_password': data['password']})
+ page = req.load("http://www.alldebrid.com/register/?%s" % urlparams)
+
+ if "This login doesn't exist" in page:
+ self.wrongPassword()
+
+ if "The password is not valid" in page:
+ self.wrongPassword()
+
+ if "Invalid captcha" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/BayfilesCom.py b/pyload/plugins/account/BayfilesCom.py
new file mode 100644
index 000000000..34163d0cc
--- /dev/null
+++ b/pyload/plugins/account/BayfilesCom.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class BayfilesCom(Account):
+ __name__ = "BayfilesCom"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """Bayfiles.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ for _i in xrange(2):
+ res = json_loads(req.load("http://api.bayfiles.com/v1/account/info"))
+ self.logDebug(res)
+ if not res['error']:
+ break
+ self.logWarning(res['error'])
+ self.relogin(user)
+
+ return {"premium": bool(res['premium']), "trafficleft": -1,
+ "validuntil": res['expires'] if res['expires'] >= int(time()) else -1}
+
+
+ def login(self, user, data, req):
+ res = json_loads(req.load("http://api.bayfiles.com/v1/account/login/%s/%s" % (user, data['password'])))
+ self.logDebug(res)
+ if res['error']:
+ self.logError(res['error'])
+ self.wrongPassword()
diff --git a/pyload/plugins/account/BillionuploadsCom.py b/pyload/plugins/account/BillionuploadsCom.py
new file mode 100644
index 000000000..b1f621ecb
--- /dev/null
+++ b/pyload/plugins/account/BillionuploadsCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/BitshareCom.py b/pyload/plugins/account/BitshareCom.py
new file mode 100644
index 000000000..44b2818cf
--- /dev/null
+++ b/pyload/plugins/account/BitshareCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+
+class BitshareCom(Account):
+ __name__ = "BitshareCom"
+ __type__ = "account"
+ __version__ = "0.12"
+
+ __description__ = """Bitshare account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Paul King", None)]
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://bitshare.com/mysettings.html")
+
+ if "\"http://bitshare.com/myupgrade.html\">Free" in page:
+ return {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ if not '<input type="checkbox" name="directdownload" checked="checked" />' in page:
+ self.logWarning(_("Activate direct Download in your Bitshare Account"))
+
+ return {"validuntil": -1, "trafficleft": -1, "premium": True}
+
+
+ def login(self, user, data, req):
+ page = req.load("http://bitshare.com/login.html",
+ post={"user": user, "password": data['password'], "submit": "Login"}, cookies=True)
+ if "login" in req.lastEffectiveURL:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/CatShareNet.py b/pyload/plugins/account/CatShareNet.py
new file mode 100644
index 000000000..e172d6aee
--- /dev/null
+++ b/pyload/plugins/account/CatShareNet.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class CatShareNet(Account):
+ __name__ = "CatShareNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """CatShareNet account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("prOq", None)]
+
+
+ PREMIUM_PATTERN = r'class="nav-collapse collapse pull-right">[\s\w<>=-."/:]*\sz.</a></li>\s*<li><a href="/premium">.*\s*<span style="color: red">(.*?)</span>[\s\w<>/]*href="/logout"'
+ VALID_UNTIL_PATTERN = r'<div class="span6 pull-right">[\s\w<>=-":;]*<span style="font-size:13px;">.*?<strong>(.*?)</strong></span>'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = -1
+
+ html = req.load("http://catshare.net/", decode=True)
+
+ try:
+ m = re.search(self.PREMIUM_PATTERN, html)
+ if "Premium" in m.group(1):
+ premium = True
+ except Exception:
+ pass
+
+ try:
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ expiredate = m.group(1)
+ if "-" not in expiredate:
+ validuntil = mktime(strptime(expiredate, "%d.%m.%Y"))
+ except Exception:
+ pass
+
+ return {'premium': premium, 'trafficleft': -1, '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"})
+
+ if not '<a href="/logout">Wyloguj</a>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/CramitIn.py b/pyload/plugins/account/CramitIn.py
new file mode 100644
index 000000000..42b7cc7b2
--- /dev/null
+++ b/pyload/plugins/account/CramitIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/CzshareCom.py b/pyload/plugins/account/CzshareCom.py
new file mode 100644
index 000000000..786832e8d
--- /dev/null
+++ b/pyload/plugins/account/CzshareCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class CzshareCom(Account):
+ __name__ = "CzshareCom"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __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):
+ html = req.load("http://sdilej.cz/prehled_kreditu/")
+
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ if m is None:
+ return {"validuntil": 0, "trafficleft": 0}
+ else:
+ credits = float(m.group(1).replace(' ', '').replace(',', '.'))
+ credits = credits * 1024 ** {'KiB': 0, 'MiB': 1, 'GiB': 2}[m.group(2)]
+ validuntil = mktime(strptime(m.group(3), '%d.%m.%y %H:%M'))
+ return {"validuntil": validuntil, "trafficleft": credits}
+
+
+ def login(self, user, data, req):
+ html = req.load('https://sdilej.cz/index.php', post={
+ "Prihlasit": "Prihlasit",
+ "login-password": data['password'],
+ "login-name": user
+ })
+
+ if '<div class="login' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/DebridItaliaCom.py b/pyload/plugins/account/DebridItaliaCom.py
new file mode 100644
index 000000000..41f796327
--- /dev/null
+++ b/pyload/plugins/account/DebridItaliaCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class DebridItaliaCom(Account):
+ __name__ = "DebridItaliaCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __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 = int(mktime(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']})
+
+ if 'NO' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/DepositfilesCom.py b/pyload/plugins/account/DepositfilesCom.py
new file mode 100644
index 000000000..688fcc505
--- /dev/null
+++ b/pyload/plugins/account/DepositfilesCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime
+
+from pyload.plugins.internal.Account import Account
+
+
+class DepositfilesCom(Account):
+ __name__ = "DepositfilesCom"
+ __type__ = "account"
+ __version__ = "0.30"
+
+ __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 = int(mktime(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']})
+ if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/DevhostSt.py b/pyload/plugins/account/DevhostSt.py
new file mode 100644
index 000000000..07eaf339a
--- /dev/null
+++ b/pyload/plugins/account/DevhostSt.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/mM8
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DevhostSt(SimpleHoster):
+ __name__ = "DevhostSt"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}'
+
+ __description__ = """d-h.st hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'>Filename:</span> <div title="(?P<N>.+?)"'
+ SIZE_PATTERN = r'>Size:</span> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>File Not Found<'
+ LINK_PATTERN = r'id="downloadfile" href="(.+?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ dl_url = m.group(1)
+ self.download(dl_url, disposition=True)
+
+ check = self.checkDownload({'html': re.compile("html")})
+ if check == "html":
+ self.error(_("Downloaded file is an html page"))
+
+
+getInfo = create_getInfo(DevhostSt)
diff --git a/pyload/plugins/account/DevhostStFolder.py b/pyload/plugins/account/DevhostStFolder.py
new file mode 100644
index 000000000..51142fde9
--- /dev/null
+++ b/pyload/plugins/account/DevhostStFolder.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/users/shine/?fld_id=37263#files
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DevhostStFolder(SimpleCrypter):
+ __name__ = "DevhostStFolder"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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 getFileInfo(self):
+ if re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ try:
+ id = re.match(self.__pattern__, self.pyfile.url).group('ID')
+ if id == "0":
+ raise
+
+ p = r'href="(.+?)">Back to \w+<'
+ m = re.search(p, self.html)
+ html = self.load(urljoin("http://d-h.st", m.group(1)),
+ cookies=False)
+
+ p = '\?fld_id=%s.*?">(.+?)<' % id
+ m = re.search(p, html)
+ name = folder = m.group(1)
+
+ except Exception, e:
+ self.logDebug(e)
+ name = folder = re.match(self.__pattern__, self.pyfile.url).group('USER')
+
+ return {'name': name, 'folder': folder}
+
+
+ def getLinks(self):
+ return [urljoin("http://d-h.st", link) for link in re.findall(self.LINK_PATTERN, self.html)]
diff --git a/pyload/plugins/account/DropboxCom.py b/pyload/plugins/account/DropboxCom.py
new file mode 100644
index 000000000..40d5584a4
--- /dev/null
+++ b/pyload/plugins/account/DropboxCom.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DropboxCom(SimpleHoster):
+ __name__ = "DropboxCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?dropbox\.com/.+'
+
+ __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):
+ self.download(self.pyfile.url, get={'dl': "1"})
+
+ check = self.checkDownload({'html': re.compile("html")})
+ if check == "html":
+ self.error(_("Downloaded file is an html page"))
+
+
+getInfo = create_getInfo(DropboxCom)
diff --git a/pyload/plugins/account/EasybytezCom.py b/pyload/plugins/account/EasybytezCom.py
new file mode 100644
index 000000000..64b59413c
--- /dev/null
+++ b/pyload/plugins/account/EasybytezCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.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/pyload/plugins/account/EuroshareEu.py b/pyload/plugins/account/EuroshareEu.py
new file mode 100644
index 000000000..5ec543433
--- /dev/null
+++ b/pyload/plugins/account/EuroshareEu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class EuroshareEu(Account):
+ __name__ = "EuroshareEu"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __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 = mktime(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/plugins/account/FastixRu.py b/pyload/plugins/account/FastixRu.py
new file mode 100644
index 000000000..829316ce3
--- /dev/null
+++ b/pyload/plugins/account/FastixRu.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class FastixRu(Account):
+ __name__ = "FastixRu"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Fastix account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"}))
+
+ points = page['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):
+ page = req.load("http://fastix.ru/api_v2/",
+ get={'sub': "get_apikey", 'email': user, 'password': data['password']})
+ api = json_loads(page)
+ api = api['apikey']
+ data['api'] = api
+ if "error_code" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FastshareCz.py b/pyload/plugins/account/FastshareCz.py
new file mode 100644
index 000000000..32c897317
--- /dev/null
+++ b/pyload/plugins/account/FastshareCz.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import parseFileSize
+
+
+class FastshareCz(Account):
+ __name__ = "FastshareCz"
+ __type__ = "account"
+ __version__ = "0.05"
+
+ __description__ = """Fastshare.cz account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CREDIT_PATTERN = r'My account\s*\((.+?)\)'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ 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))
+
+ if trafficleft:
+ premium = True
+ validuntil = -1
+ else:
+ premium = False
+
+ 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/plugins/account/File4safeCom.py b/pyload/plugins/account/File4safeCom.py
new file mode 100644
index 000000000..42b67e08f
--- /dev/null
+++ b/pyload/plugins/account/File4safeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class File4safeCom(XFSAccount):
+ __name__ = "File4safeCom"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __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/plugins/account/FileParadoxIn.py b/pyload/plugins/account/FileParadoxIn.py
new file mode 100644
index 000000000..cee7bec2f
--- /dev/null
+++ b/pyload/plugins/account/FileParadoxIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/FilecloudIo.py b/pyload/plugins/account/FilecloudIo.py
new file mode 100644
index 000000000..babef59a2
--- /dev/null
+++ b/pyload/plugins/account/FilecloudIo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class FilecloudIo(Account):
+ __name__ = "FilecloudIo"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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.accounts[user]['password']})
+ rep = json_loads(rep)
+ if rep['status'] == 'ok':
+ break
+ elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password':
+ self.logError(_("Wrong username or password"))
+ return {"valid": False, "premium": False}
+ else:
+ return {"premium": False}
+
+ akey = rep['akey']
+ self.accounts[user]['akey'] = akey # Saved for hoster plugin
+ rep = req.load("http://api.filecloud.io/api-fetch_account_details.api",
+ post={"akey": akey})
+ rep = json_loads(rep)
+
+ if rep['is_premium'] == 1:
+ return {"validuntil": int(rep['premium_until']), "trafficleft": -1}
+ else:
+ return {"premium": False}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("secure.filecloud.io", "lang", "en")
+ 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)
+
+ self.logged_in = True if "you have successfully logged in - filecloud.io" in html else False
+ self.form_data = {}
diff --git a/pyload/plugins/account/FilefactoryCom.py b/pyload/plugins/account/FilefactoryCom.py
new file mode 100644
index 000000000..0d7c1ff0e
--- /dev/null
+++ b/pyload/plugins/account/FilefactoryCom.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pycurl import REFERER
+
+from pyload.plugins.internal.Account import Account
+
+
+class FilefactoryCom(Account):
+ __name__ = "FilefactoryCom"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __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 = mktime(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(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/plugins/account/FilejungleCom.py b/pyload/plugins/account/FilejungleCom.py
new file mode 100644
index 000000000..8abb89788
--- /dev/null
+++ b/pyload/plugins/account/FilejungleCom.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class FilejungleCom(Account):
+ __name__ = "FilejungleCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __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 = mktime(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": ""})
+
+ if re.search(self.LOGIN_FAILED_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FileomCom.py b/pyload/plugins/account/FileomCom.py
new file mode 100644
index 000000000..04f503b3b
--- /dev/null
+++ b/pyload/plugins/account/FileomCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/FilerNet.py b/pyload/plugins/account/FilerNet.py
new file mode 100644
index 000000000..f9b5efdd3
--- /dev/null
+++ b/pyload/plugins/account/FilerNet.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.internal.Account import Account
+
+
+class FilerNet(Account):
+ __name__ = "FilerNet"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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 = int(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/"})
+ if 'Logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FilerioCom.py b/pyload/plugins/account/FilerioCom.py
new file mode 100644
index 000000000..27d8df13f
--- /dev/null
+++ b/pyload/plugins/account/FilerioCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/FilesMailRu.py b/pyload/plugins/account/FilesMailRu.py
new file mode 100644
index 000000000..b42a7c559
--- /dev/null
+++ b/pyload/plugins/account/FilesMailRu.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+
+class FilesMailRu(Account):
+ __name__ = "FilesMailRu"
+ __type__ = "account"
+ __version__ = "0.10"
+
+ __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("@")
+
+ page = req.load("http://swa.mail.ru/cgi-bin/auth", None,
+ {"Domain": domain, "Login": user, "Password": data['password'],
+ "Page": "http://files.mail.ru/"}, cookies=True)
+
+ if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in page: # @TODO seems not to work
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FileserveCom.py b/pyload/plugins/account/FileserveCom.py
new file mode 100644
index 000000000..8c570b575
--- /dev/null
+++ b/pyload/plugins/account/FileserveCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.internal.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)
+
+ page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(page)
+
+ if res['type'] == "premium":
+ validuntil = mktime(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):
+ page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(page)
+
+ if not res['type']:
+ self.wrongPassword()
+
+ #login at fileserv page
+ req.load("http://www.fileserve.com/login.php",
+ post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
+ "loginFormSubmit": "Login"})
diff --git a/pyload/plugins/account/FourSharedCom.py b/pyload/plugins/account/FourSharedCom.py
new file mode 100644
index 000000000..20293fa5d
--- /dev/null
+++ b/pyload/plugins/account/FourSharedCom.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class FourSharedCom(Account):
+ __name__ = "FourSharedCom"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __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"})
+
+ if 'Please log in to access your 4shared account' in res:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FreakshareCom.py b/pyload/plugins/account/FreakshareCom.py
new file mode 100644
index 000000000..12c5277a6
--- /dev/null
+++ b/pyload/plugins/account/FreakshareCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime
+
+from pyload.plugins.internal.Account import Account
+
+
+class FreakshareCom(Account):
+ __name__ = "FreakshareCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __description__ = """Freakshare.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://freakshare.com/")
+
+ validuntil = r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>'
+ validuntil = re.search(validuntil, page, re.M)
+ validuntil = validuntil.group(1).strip()
+ validuntil = mktime(strptime(validuntil, "%d.%m.%Y - %H:%M"))
+
+ traffic = r'Traffic verbleibend:</td>\s*<td>([^<]+)'
+ traffic = re.search(traffic, page, re.M)
+ traffic = traffic.group(1).strip()
+ traffic = self.parseTraffic(traffic)
+
+ return {"validuntil": validuntil, "trafficleft": traffic}
+
+
+ def login(self, user, data, req):
+ req.load("http://freakshare.com/index.php?language=EN")
+
+ page = req.load("http://freakshare.com/login.html", None,
+ {"submit": "Login", "user": user, "pass": data['password']}, cookies=True)
+
+ if ">Wrong Username or Password" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/FreeWayMe.py b/pyload/plugins/account/FreeWayMe.py
new file mode 100644
index 000000000..db879c052
--- /dev/null
+++ b/pyload/plugins/account/FreeWayMe.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class FreeWayMe(Account):
+ __name__ = "FreeWayMe"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __description__ = """FreeWayMe account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ status = self.getAccountStatus(user, req)
+ if not status:
+ return False
+ self.logDebug(status)
+
+ account_info = {"validuntil": -1, "premium": False}
+ if status['premium'] == "Free":
+ account_info['trafficleft'] = int(status['guthaben']) * 1024
+ elif status['premium'] == "Spender":
+ account_info['trafficleft'] = -1
+ elif status['premium'] == "Flatrate":
+ account_info = {"validuntil": int(status['Flatrate']),
+ "trafficleft": -1,
+ "premium": True}
+
+ return account_info
+
+
+ def getpw(self, user):
+ return self.accounts[user]['password']
+
+
+ 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.accounts[user]['password']})
+ self.logDebug("Login: %s" % answer)
+ if answer == "Invalid login":
+ self.wrongPassword()
+ return False
+ return json_loads(answer)
diff --git a/pyload/plugins/account/FshareVn.py b/pyload/plugins/account/FshareVn.py
new file mode 100644
index 000000000..ee8aacaf9
--- /dev/null
+++ b/pyload/plugins/account/FshareVn.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+from pycurl import REFERER
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class FshareVn(Account):
+ __name__ = "FshareVn"
+ __type__ = "account"
+ __version__ = "0.07"
+
+ __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 = mktime(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):
+ req.http.c.setopt(REFERER, "https://www.fshare.vn/login.php")
+
+ html = req.load('https://www.fshare.vn/login.php', post={
+ "login_password": data['password'],
+ "login_useremail": user,
+ "url_refe": "http://www.fshare.vn/index.php"
+ }, 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 float(m.group(1)) * 1024 ** {'k': 0, 'K': 0, 'M': 1, 'G': 2}[m.group(2)] if m else 0
diff --git a/pyload/plugins/account/Ftp.py b/pyload/plugins/account/Ftp.py
new file mode 100644
index 000000000..d439512ce
--- /dev/null
+++ b/pyload/plugins/account/Ftp.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.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/plugins/account/HellshareCz.py b/pyload/plugins/account/HellshareCz.py
new file mode 100644
index 000000000..1b93cf77d
--- /dev/null
+++ b/pyload/plugins/account/HellshareCz.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.internal.Account import Account
+
+
+class HellshareCz(Account):
+ __name__ = "HellshareCz"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __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 = int(credit) * 1024
+ 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/')
+ 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)
+
+ 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"
+ })
+
+ if "<p>You input a wrong user name or wrong password</p>" in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/Http.py b/pyload/plugins/account/Http.py
new file mode 100644
index 000000000..8c0aeb2e4
--- /dev/null
+++ b/pyload/plugins/account/Http.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.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/plugins/account/HugefilesNet.py b/pyload/plugins/account/HugefilesNet.py
new file mode 100644
index 000000000..242ae118c
--- /dev/null
+++ b/pyload/plugins/account/HugefilesNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/HundredEightyUploadCom.py b/pyload/plugins/account/HundredEightyUploadCom.py
new file mode 100644
index 000000000..89d779e52
--- /dev/null
+++ b/pyload/plugins/account/HundredEightyUploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class HundredEightyUploadCom(XFSAccount):
+ __name__ = "HundredEightyUploadCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """180upload.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "180upload.com"
diff --git a/pyload/plugins/account/JunocloudMe.py b/pyload/plugins/account/JunocloudMe.py
new file mode 100644
index 000000000..14fe8ddd7
--- /dev/null
+++ b/pyload/plugins/account/JunocloudMe.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/Keep2shareCc.py b/pyload/plugins/account/Keep2shareCc.py
new file mode 100644
index 000000000..172cedfc2
--- /dev/null
+++ b/pyload/plugins/account/Keep2shareCc.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import gmtime, mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class Keep2shareCc(Account):
+ __name__ = "Keep2shareCc"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Keep2share.cc account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("aeronaut", "aeronaut@pianoguy.de")]
+
+
+ VALID_UNTIL_PATTERN = r'Premium expires: <b>(.+?)</b>'
+ TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):<b><a href="/user/statistic.html">(.+?)</a>'
+
+ LOGIN_FAIL_PATTERN = r'Please fix the following input errors'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ 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)
+
+ try:
+ validuntil = mktime(strptime(expiredate, "%Y.%m.%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > mktime(gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = None
+
+ 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']})
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugins/account/KingfilesNet.py b/pyload/plugins/account/KingfilesNet.py
new file mode 100644
index 000000000..b2d7d122a
--- /dev/null
+++ b/pyload/plugins/account/KingfilesNet.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class KingfilesNet(SimpleHoster):
+ __name__ = "KingfilesNet"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})'
+
+ __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_PATTERN = r'var download_url = \'(.+)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self):
+ # Click the free user button
+ post_data = {'op' : "download1",
+ 'usr_login' : "",
+ 'id' : self.info['pattern']['ID'],
+ 'fname' : self.pyfile.name,
+ 'referer' : "",
+ 'method_free': "+"}
+
+ self.html = self.load(self.pyfile.url, post=post_data, cookies=True, decode=True)
+
+ solvemedia = SolveMedia(self)
+ challenge, response = 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' : self.pyfile.url,
+ 'method_free' : "+",
+ 'method_premium' : "",
+ 'adcopy_response' : response,
+ 'adcopy_challenge': challenge,
+ 'down_direct' : "1"}
+
+ self.html = self.load(self.pyfile.url, post=post_data, cookies=True, decode=True)
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ self.download(m.group(1), cookies=True, disposition=True)
+
+ check = self.checkDownload({'html': re.compile("<html>")})
+ if check == "html":
+ self.error(_("Downloaded file is an html page"))
+
+
+getInfo = create_getInfo(KingfilesNet)
diff --git a/pyload/plugins/account/LetitbitNet.py b/pyload/plugins/account/LetitbitNet.py
new file mode 100644
index 000000000..5bf6de00c
--- /dev/null
+++ b/pyload/plugins/account/LetitbitNet.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+# from pyload.utils import json_loads, json_dumps
+
+
+class LetitbitNet(Account):
+ __name__ = "LetitbitNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __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.accounts[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/plugins/account/LinestorageCom.py b/pyload/plugins/account/LinestorageCom.py
new file mode 100644
index 000000000..351d66d48
--- /dev/null
+++ b/pyload/plugins/account/LinestorageCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class LinestorageCom(XFSAccount):
+ __name__ = "LinestorageCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Linestorage.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "linestorage.com"
diff --git a/pyload/plugins/account/LinksnappyCom.py b/pyload/plugins/account/LinksnappyCom.py
new file mode 100644
index 000000000..028465522
--- /dev/null
+++ b/pyload/plugins/account/LinksnappyCom.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+from hashlib import md5
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class LinksnappyCom(Account):
+ __name__ = "LinksnappyCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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': 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 = int(j['return']['trafficleft']) * 1024
+
+ 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': md5(data['password']).hexdigest()})
+
+ if 'Invalid Account Details' in r:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/LomafileCom.py b/pyload/plugins/account/LomafileCom.py
new file mode 100644
index 000000000..937b24fae
--- /dev/null
+++ b/pyload/plugins/account/LomafileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class LomafileCom(XFSAccount):
+ __name__ = "LomafileCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Lomafile.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "lomafile.com"
diff --git a/pyload/plugins/account/MegaDebridEu.py b/pyload/plugins/account/MegaDebridEu.py
new file mode 100644
index 000000000..390cc232e
--- /dev/null
+++ b/pyload/plugins/account/MegaDebridEu.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.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/plugins/account/MegaRapidCz.py b/pyload/plugins/account/MegaRapidCz.py
new file mode 100644
index 000000000..a75e313ff
--- /dev/null
+++ b/pyload/plugins/account/MegaRapidCz.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime
+from pyload.plugins.internal.Account import Account
+
+
+class MegaRapidCz(Account):
+ __name__ = "MegaRapidCz"
+ __type__ = "account"
+ __version__ = "0.34"
+
+ __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):
+ html = req.load("http://megarapid.cz/mujucet/", decode=True)
+
+ m = re.search(self.LIMITDL_PATTERN, html)
+ if m:
+ data = self.getAccountData(user)
+ data['options']['limitDL'] = [int(m.group(1))]
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ validuntil = mktime(strptime(m.group(1), "%d.%m.%Y - %H:%M"))
+ return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ 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):
+ htm = req.load("http://megarapid.cz/prihlaseni/")
+ if "Heslo:" in htm:
+ start = htm.index('id="inp_hash" name="hash" value="')
+ htm = htm[start + 33:]
+ hashes = htm[0:32]
+ htm = 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/plugins/account/MegasharesCom.py b/pyload/plugins/account/MegasharesCom.py
new file mode 100644
index 000000000..b2f439b45
--- /dev/null
+++ b/pyload/plugins/account/MegasharesCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class MegasharesCom(Account):
+ __name__ = "MegasharesCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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 = mktime(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/plugins/account/MovReelCom.py b/pyload/plugins/account/MovReelCom.py
new file mode 100644
index 000000000..7a00af5d3
--- /dev/null
+++ b/pyload/plugins/account/MovReelCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/MultishareCz.py b/pyload/plugins/account/MultishareCz.py
new file mode 100644
index 000000000..9dc5b1ff0
--- /dev/null
+++ b/pyload/plugins/account/MultishareCz.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class MultishareCz(Account):
+ __name__ = "MultishareCz"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __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/pyload/plugins/account/MyfastfileCom.py b/pyload/plugins/account/MyfastfileCom.py
new file mode 100644
index 000000000..acc09ad8b
--- /dev/null
+++ b/pyload/plugins/account/MyfastfileCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Account):
+ __name__ = "MyfastfileCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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 = int(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/plugins/account/NetloadIn.py b/pyload/plugins/account/NetloadIn.py
new file mode 100644
index 000000000..341803670
--- /dev/null
+++ b/pyload/plugins/account/NetloadIn.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+
+from pyload.plugins.internal.Account import Account
+
+
+class NetloadIn(Account):
+ __name__ = "NetloadIn"
+ __type__ = "account"
+ __version__ = "0.22"
+
+ __description__ = """Netload.in account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org"),
+ ("CryNickSystems", "webmaster@pcProfil.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://netload.in/index.php", get={'id': 2, 'lang': "de"})
+ left = r'>(\d+) (Tag|Tage), (\d+) Stunden<'
+ left = re.search(left, page)
+ if left:
+ validuntil = time() + int(left.group(1)) * 24 * 60 * 60 + int(left.group(3)) * 60 * 60
+ trafficleft = -1
+ premium = True
+ else:
+ validuntil = None
+ premium = False
+ trafficleft = None
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ page = req.load("http://netload.in/index.php", None,
+ {"txtuser": user, "txtpass": data['password'], "txtcheck": "login", "txtlogin": "Login"},
+ cookies=True)
+ if "password or it might be invalid!" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/NosuploadCom.py b/pyload/plugins/account/NosuploadCom.py
new file mode 100644
index 000000000..9c11699ec
--- /dev/null
+++ b/pyload/plugins/account/NosuploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/NovafileCom.py b/pyload/plugins/account/NovafileCom.py
new file mode 100644
index 000000000..cb3a639f5
--- /dev/null
+++ b/pyload/plugins/account/NovafileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/NowVideoAt.py b/pyload/plugins/account/NowVideoAt.py
new file mode 100644
index 000000000..7af8bc4bc
--- /dev/null
+++ b/pyload/plugins/account/NowVideoAt.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import gmtime, mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class NowVideoAt(Account):
+ __name__ = "NowVideoAt"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __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.at/premium.php")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = mktime(strptime(expiredate, "%Y-%b-%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > mktime(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.at/login.php",
+ post={'user': user, 'pass': data['password']})
+
+ if ">Invalid login details" is html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/OboomCom.py b/pyload/plugins/account/OboomCom.py
new file mode 100644
index 000000000..5ee35f973
--- /dev/null
+++ b/pyload/plugins/account/OboomCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from beaker.crypto.pbkdf2 import PBKDF2
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Account import Account
+
+
+class OboomCom(Account):
+ __name__ = "OboomCom"
+ __type__ = "account"
+ __version__ = "0.21"
+
+ __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 = int(userData['premium_unix'])
+
+ traffic = userData['traffic']
+
+ trafficLeft = traffic['current']
+ maxTraffic = traffic['max']
+
+ session = accountData['session']
+
+ return {'premium' : premium,
+ 'validuntil' : validUntil,
+ 'trafficleft': trafficLeft / 1024, #@TODO: Remove / 1024 in 0.4.10
+ 'maxtraffic' : maxTraffic / 1024, #@TODO: Remove / 1024 in 0.4.10
+ 'session' : session}
+
+
+ def login(self, user, data, req):
+ self.loadAccountData(user, req)
diff --git a/pyload/plugins/account/OneFichierCom.py b/pyload/plugins/account/OneFichierCom.py
new file mode 100644
index 000000000..7f1140e67
--- /dev/null
+++ b/pyload/plugins/account/OneFichierCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime
+
+from pycurl import REFERER
+
+from pyload.plugins.internal.Account import Account
+
+
+class OneFichierCom(Account):
+ __name__ = "OneFichierCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __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 = mktime(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(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"})
+
+ if '>Invalid email address' in html or '>Invalid password' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/OverLoadMe.py b/pyload/plugins/account/OverLoadMe.py
new file mode 100644
index 000000000..cda4bc82f
--- /dev/null
+++ b/pyload/plugins/account/OverLoadMe.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class OverLoadMe(Account):
+ __name__ = "OverLoadMe"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Over-Load.me account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("marley", "marley@over-load.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("https://api.over-load.me/account.php", get={"user": user, "auth": data['password']}).strip()
+ data = json_loads(page)
+
+ # Check for premium
+ if data['membership'] == "Free":
+ return {"premium": False}
+
+ account_info = {"validuntil": data['expirationunix'], "trafficleft": -1}
+ return account_info
+
+
+ def login(self, user, data, req):
+ 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/plugins/account/PremiumTo.py b/pyload/plugins/account/PremiumTo.py
new file mode 100644
index 000000000..e94eed6fb
--- /dev/null
+++ b/pyload/plugins/account/PremiumTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+
+class PremiumTo(Account):
+ __name__ = "PremiumTo"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __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):
+ api_r = req.load("http://premium.to/api/straffic.php",
+ get={'username': self.username, 'password': self.password})
+ traffic = sum(map(int, api_r.split(';')))
+
+ return {"trafficleft": int(traffic) / 1024, "validuntil": -1} #@TODO: Remove / 1024 in 0.4.10
+
+
+ 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}).strip()
+
+ if "wrong username" in authcode:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/PremiumizeMe.py b/pyload/plugins/account/PremiumizeMe.py
new file mode 100644
index 000000000..c06baf21d
--- /dev/null
+++ b/pyload/plugins/account/PremiumizeMe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+from pyload.utils import json_loads
+
+
+class PremiumizeMe(Account):
+ __name__ = "PremiumizeMe"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __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.accounts[user]['password']})
+ return json_loads(answer)
diff --git a/pyload/plugins/account/QuickshareCz.py b/pyload/plugins/account/QuickshareCz.py
new file mode 100644
index 000000000..40bf9d06d
--- /dev/null
+++ b/pyload/plugins/account/QuickshareCz.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class QuickshareCz(Account):
+ __name__ = "QuickshareCz"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __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/pyload/plugins/account/RPNetBiz.py b/pyload/plugins/account/RPNetBiz.py
new file mode 100644
index 000000000..70622ff45
--- /dev/null
+++ b/pyload/plugins/account/RPNetBiz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Account):
+ __name__ = "RPNetBiz"
+ __type__ = "account"
+ __version__ = "0.10"
+
+ __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": int(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.accounts[user]['password'],
+ "action": "showAccountInformation"})
+ self.logDebug("JSON data: %s" % res)
+
+ return json_loads(res)
diff --git a/pyload/plugins/account/RapidfileshareNet.py b/pyload/plugins/account/RapidfileshareNet.py
new file mode 100644
index 000000000..503c731fe
--- /dev/null
+++ b/pyload/plugins/account/RapidfileshareNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/RapidgatorNet.py b/pyload/plugins/account/RapidgatorNet.py
new file mode 100644
index 000000000..72a46a928
--- /dev/null
+++ b/pyload/plugins/account/RapidgatorNet.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class RapidgatorNet(Account):
+ __name__ = "RapidgatorNet"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __description__ = """Rapidgator.net account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = 'http://rapidgator.net/api/user'
+
+
+ def loadAccountInfo(self, user, req):
+ try:
+ sid = self.getAccountData(user).get('SID')
+ assert sid
+
+ json = req.load("%s/info?sid=%s" % (self.API_URL, sid))
+ self.logDebug("API:USERINFO", json)
+ json = json_loads(json)
+
+ if json['response_status'] == 200:
+ if "reset_in" in json['response']:
+ self.scheduleRefresh(user, json['response']['reset_in'])
+
+ return {"validuntil": json['response']['expire_date'],
+ "trafficleft": int(json['response']['traffic_left']),
+ "premium": True}
+ else:
+ self.logError(json['response_details'])
+ except Exception, e:
+ self.logError(e)
+
+ return {"validuntil": None, "trafficleft": None, "premium": False}
+
+
+ def login(self, user, data, req):
+ try:
+ json = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
+ self.logDebug("API:LOGIN", json)
+ json = json_loads(json)
+
+ 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/plugins/account/RapiduNet.py b/pyload/plugins/account/RapiduNet.py
new file mode 100644
index 000000000..6837f5c91
--- /dev/null
+++ b/pyload/plugins/account/RapiduNet.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class RapiduNet(Account):
+ __name__ = "RapiduNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Rapidu.net account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("prOq", None)]
+
+
+ ACCOUNT_INFO_PATTERN = '<a href="premium/" style="padding-left: 0px;">Account: <b>(.*?)</b></a>'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+
+ req.load('https://rapidu.net/ajax.php?a=getChangeLang', post={"_go": "", "lang": "en"})
+ self.html = req.load('https://rapidu.net/', decode=True)
+
+ m = re.search(self.ACCOUNT_INFO_PATTERN, self.html)
+ if m:
+ if m.group(1) == "Premium":
+ premium = True
+
+ return {"validuntil": None, "trafficleft": None, "premium": premium}
+
+
+ def login(self, user, data, req):
+ try:
+ json = req.load('https://rapidu.net/ajax.php?a=getUserLogin', post={"_go": "", "login": user, "pass": data['password'], "member": "1"})
+ json = json_loads(json)
+ self.logDebug(json)
+
+ if not json['message'] == "success":
+ self.wrongPassword()
+ except Exception, e:
+ self.logError(e)
+
diff --git a/pyload/plugins/account/RarefileNet.py b/pyload/plugins/account/RarefileNet.py
new file mode 100644
index 000000000..0d5547e57
--- /dev/null
+++ b/pyload/plugins/account/RarefileNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/RealdebridCom.py b/pyload/plugins/account/RealdebridCom.py
new file mode 100644
index 000000000..b7773d9a0
--- /dev/null
+++ b/pyload/plugins/account/RealdebridCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import xml.dom.minidom as dom
+
+from pyload.plugins.internal.Account import Account
+
+
+class RealdebridCom(Account):
+ __name__ = "RealdebridCom"
+ __type__ = "account"
+ __version__ = "0.43"
+
+ __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}
+ page = req.load("https://real-debrid.com/api/account.php")
+ xml = dom.parseString(page)
+ account_info = {"validuntil": int(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
+ "trafficleft": -1}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ self.pin_code = False
+ page = req.load("https://real-debrid.com/ajax/login.php", get={"user": user, "pass": data['password']})
+ if "Your login informations are incorrect" in page:
+ self.wrongPassword()
+ elif "PIN Code required" in page:
+ 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/plugins/account/RehostTo.py b/pyload/plugins/account/RehostTo.py
new file mode 100644
index 000000000..23c1eeda1
--- /dev/null
+++ b/pyload/plugins/account/RehostTo.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+
+class RehostTo(Account):
+ __name__ = "RehostTo"
+ __type__ = "account"
+ __version__ = "0.10"
+
+ __description__ = """Rehost.to account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("http://rehost.to/api.php",
+ get={'cmd': "login", 'user': user, 'pass': data['password']})
+ data = [x.split("=") for x in page.split(",")]
+ ses = data[0][1]
+ long_ses = data[1][1]
+
+ page = req.load("http://rehost.to/api.php",
+ get={'cmd': "get_premium_credits", 'long_ses': long_ses})
+ traffic, valid = page.split(",")
+
+ account_info = {"trafficleft": int(traffic) * 1024,
+ "validuntil": int(valid),
+ "long_ses": long_ses,
+ "ses": ses}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ page = req.load("http://rehost.to/api.php",
+ get={'cmd': "login", 'user': user, 'pass': data['password']})
+
+ if "Login failed." in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/RyushareCom.py b/pyload/plugins/account/RyushareCom.py
new file mode 100644
index 000000000..47ec32c76
--- /dev/null
+++ b/pyload/plugins/account/RyushareCom.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class RyushareCom(XFSAccount):
+ __name__ = "RyushareCom"
+ __type__ = "account"
+ __version__ = "0.05"
+
+ __description__ = """Ryushare.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("trance4us", None)]
+
+
+ HOSTER_DOMAIN = "ryushare.com"
+
+
+ def login(self, user, data, req):
+ req.lastURL = "http://ryushare.com/login.python"
+ html = req.load("http://ryushare.com/login.python",
+ post={"login": user, "password": data['password'], "op": "login"})
+ if 'Incorrect Login or Password' in html or '>Error<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/SafesharingEu.py b/pyload/plugins/account/SafesharingEu.py
new file mode 100644
index 000000000..bdc43bec7
--- /dev/null
+++ b/pyload/plugins/account/SafesharingEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/SecureUploadEu.py b/pyload/plugins/account/SecureUploadEu.py
new file mode 100644
index 000000000..c98ab2de9
--- /dev/null
+++ b/pyload/plugins/account/SecureUploadEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/SendmywayCom.py b/pyload/plugins/account/SendmywayCom.py
new file mode 100644
index 000000000..6749e6215
--- /dev/null
+++ b/pyload/plugins/account/SendmywayCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/ShareonlineBiz.py b/pyload/plugins/account/ShareonlineBiz.py
new file mode 100644
index 000000000..dbbe93869
--- /dev/null
+++ b/pyload/plugins/account/ShareonlineBiz.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+
+
+class ShareonlineBiz(Account):
+ __name__ = "ShareonlineBiz"
+ __type__ = "account"
+ __version__ = "0.24"
+
+ __description__ = """Share-online.biz account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def getUserAPI(self, user, req):
+ return req.load("http://api.share-online.biz/account.php",
+ {"username": user, "password": self.accounts[user]['password'], "act": "userDetails"})
+
+
+ def loadAccountInfo(self, user, req):
+ html = self.getUserAPI(user, req)
+
+ info = {}
+ for line in html.splitlines():
+ if "=" in line:
+ key, value = line.split("=")
+ info[key] = value
+ self.logDebug(info)
+
+ if "dl" in info and info['dl'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", "dl", info['dl'])
+ if "a" in info and info['a'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", "a", info['a'])
+
+ return {"validuntil": int(info['expire_date']) if "expire_date" in info else -1,
+ "trafficleft": -1,
+ "premium": True if ("dl" in info or "a" in info) and (info['group'] != "Sammler") else False}
+
+
+ def login(self, user, data, req):
+ html = self.getUserAPI(user, req)
+ if "EXCEPTION" in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/SimplyPremiumCom.py b/pyload/plugins/account/SimplyPremiumCom.py
new file mode 100644
index 000000000..b3dad84d3
--- /dev/null
+++ b/pyload/plugins/account/SimplyPremiumCom.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Account import Account
+
+
+class SimplyPremiumCom(Account):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Simply-Premium.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ json_data = req.load('http://www.simply-premium.com/api/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}
+
+ #Time package
+ validuntil = float(json_data['result']['timeend'])
+ #Traffic package
+ # {"trafficleft": int(traffic), "validuntil": -1}
+ #trafficleft = int(json_data['result']['traffic'])
+
+ #return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+ return {"premium": True, "validuntil": validuntil}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("simply-premium.com", "lang", "EN")
+
+ if data['password'] == '' or data['password'] == '0':
+ post_data = {"key": user}
+ else:
+ post_data = {"login_name": user, "login_pass": data['password']}
+
+ html = req.load("http://www.simply-premium.com/login.php", post=post_data)
+
+ if 'logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/SimplydebridCom.py b/pyload/plugins/account/SimplydebridCom.py
new file mode 100644
index 000000000..1942e178c
--- /dev/null
+++ b/pyload/plugins/account/SimplydebridCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class SimplydebridCom(Account):
+ __name__ = "SimplydebridCom"
+ __type__ = "account"
+ __version__ = "0.10"
+
+ __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": mktime(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/plugins/account/StahnuTo.py b/pyload/plugins/account/StahnuTo.py
new file mode 100644
index 000000000..70ef292e7
--- /dev/null
+++ b/pyload/plugins/account/StahnuTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class StahnuTo(Account):
+ __name__ = "StahnuTo"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __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)) * 1024 if m else 0
+
+ return {"premium": trafficleft > (512 * 1024), "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"})
+
+ if not '<a href="logout.php">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/StreamcloudEu.py b/pyload/plugins/account/StreamcloudEu.py
new file mode 100644
index 000000000..a2a333dd5
--- /dev/null
+++ b/pyload/plugins/account/StreamcloudEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/TurbobitNet.py b/pyload/plugins/account/TurbobitNet.py
new file mode 100644
index 000000000..e2f56905c
--- /dev/null
+++ b/pyload/plugins/account/TurbobitNet.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class TurbobitNet(Account):
+ __name__ = "TurbobitNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __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 = mktime(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"})
+
+ if not '<div class="menu-item user-name">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/TusfilesNet.py b/pyload/plugins/account/TusfilesNet.py
new file mode 100644
index 000000000..9aa8ed543
--- /dev/null
+++ b/pyload/plugins/account/TusfilesNet.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime, gmtime
+
+from pyload.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/pyload/plugins/account/UlozTo.py b/pyload/plugins/account/UlozTo.py
new file mode 100644
index 000000000..2cdab0167
--- /dev/null
+++ b/pyload/plugins/account/UlozTo.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.Account import Account
+
+
+class UlozTo(Account):
+ __name__ = "UlozTo"
+ __type__ = "account"
+ __version__ = "0.07"
+
+ __description__ = """Uloz.to account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("pulpe", None)]
+
+
+ TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a href="/kredit" title="[^"]*?GB = ([\d.]+) MB"'
+
+
+ def loadAccountInfo(self, user, req):
+ self.phpsessid = req.cj.getCookie("ULOSESSID") #@NOTE: this cookie gets lost somehow after each request
+
+ html = req.load("http://www.ulozto.net/", decode=True)
+
+ req.cj.setCookie("ulozto.net", "ULOSESSID", self.phpsessid)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ trafficleft = int(float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048) if m else 0
+ self.premium = True if trafficleft else False
+
+ return {"validuntil": -1, "trafficleft": trafficleft}
+
+
+ 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(urljoin("http://www.ulozto.net/", action),
+ post={'_token_' : token,
+ 'do' : "loginForm-submit",
+ 'login' : u"Přihlásit",
+ 'password': data['password'],
+ 'username': user},
+ decode=True)
+
+ if '<div class="flash error">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/UnrestrictLi.py b/pyload/plugins/account/UnrestrictLi.py
new file mode 100644
index 000000000..30144ca65
--- /dev/null
+++ b/pyload/plugins/account/UnrestrictLi.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Account import Account
+from pyload.utils import json_loads
+
+
+class UnrestrictLi(Account):
+ __name__ = "UnrestrictLi"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __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 = int(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")
+
+ 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)
+
+ if 'sign_out' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/UploadcCom.py b/pyload/plugins/account/UploadcCom.py
new file mode 100644
index 000000000..8f437eb01
--- /dev/null
+++ b/pyload/plugins/account/UploadcCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/UploadedTo.py b/pyload/plugins/account/UploadedTo.py
new file mode 100644
index 000000000..f5c5d22b2
--- /dev/null
+++ b/pyload/plugins/account/UploadedTo.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+
+from pyload.plugins.internal.Account import Account
+
+
+class UploadedTo(Account):
+ __name__ = "UploadedTo"
+ __type__ = "account"
+ __version__ = "0.27"
+
+ __description__ = """Uploaded.to account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("mkaay", "mkaay@mkaay.de")]
+
+
+ PREMIUM_PATTERN = r'<em>Premium</em>'
+ VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>([^<]+)'
+ TRAFFIC_LEFT_PATTERN = r'<th colspan="2"><b class="cB">([^<]+)'
+
+
+ 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).strip()
+
+ if expiredate == "unlimited":
+ validuntil = -1
+ else:
+ m = re.findall(r'(\d+) (Week|weeks|day|hour)', expiredate)
+ if m:
+ validuntil = time()
+ for n, u in m:
+ validuntil += int(n) * 60 * 60 * {'Week': 168, 'weeks': 168, 'day': 24, 'hour': 1}[u]
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1).replace('.', ''))
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("uploaded.net", "lang", "en")
+
+ page = req.load("http://uploaded.net/io/login",
+ post={'id': user, 'pw': data['password'], '_': ""})
+
+ if "User and password do not match" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/UploadheroCom.py b/pyload/plugins/account/UploadheroCom.py
new file mode 100644
index 000000000..9406de38f
--- /dev/null
+++ b/pyload/plugins/account/UploadheroCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+import datetime
+import time
+
+from pyload.plugins.internal.Account import Account
+
+
+class UploadheroCom(Account):
+ __name__ = "UploadheroCom"
+ __type__ = "account"
+ __version__ = "0.20"
+
+ __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)
+ page = req.load("http://uploadhero.co/my-account")
+
+ if premium_pattern.search(page):
+ end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(page).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):
+ page = req.load("http://uploadhero.co/lib/connexion.php",
+ post={"pseudo_login": user, "password_login": data['password']})
+
+ if "mot de passe invalide" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/account/UploadingCom.py b/pyload/plugins/account/UploadingCom.py
new file mode 100644
index 000000000..8f308b0b6
--- /dev/null
+++ b/pyload/plugins/account/UploadingCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time, strptime, mktime
+
+from pyload.plugins.internal.Account import Account
+from pyload.plugins.internal.SimpleHoster import set_cookies
+
+
+class UploadingCom(Account):
+ __name__ = "UploadingCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __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 = mktime(strptime(expiredate, "%b %d, %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > mktime(gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = None
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ set_cookies([("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() * 1000),
+ post={'email': user, 'password': data['password'], 'remember': "on"})
diff --git a/pyload/plugins/account/UptoboxCom.py b/pyload/plugins/account/UptoboxCom.py
new file mode 100644
index 000000000..9438888e3
--- /dev/null
+++ b/pyload/plugins/account/UptoboxCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class UptoboxCom(XFSAccount):
+ __name__ = "UptoboxCom"
+ __type__ = "account"
+ __version__ = "0.07"
+
+ __description__ = """DDLStorage.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "uptobox.com"
+ HOSTER_URL = "https://uptobox.com/"
diff --git a/pyload/plugins/account/VidPlayNet.py b/pyload/plugins/account/VidPlayNet.py
new file mode 100644
index 000000000..4393b3a82
--- /dev/null
+++ b/pyload/plugins/account/VidPlayNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/account/XFileSharingPro.py b/pyload/plugins/account/XFileSharingPro.py
new file mode 100644
index 000000000..aa16d5045
--- /dev/null
+++ b/pyload/plugins/account/XFileSharingPro.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSAccount import XFSAccount
+
+
+class XFileSharingPro(XFSAccount):
+ __name__ = "XFileSharingPro"
+ __type__ = "account"
+ __version__ = "0.05"
+
+ __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:
+ return super(XFileSharingPro, self).login(user, data, req)
diff --git a/pyload/plugins/account/YibaishiwuCom.py b/pyload/plugins/account/YibaishiwuCom.py
new file mode 100644
index 000000000..06a606bc4
--- /dev/null
+++ b/pyload/plugins/account/YibaishiwuCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Account import Account
+
+
+class YibaishiwuCom(Account):
+ __name__ = "YibaishiwuCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __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/pyload/plugins/account/ZeveraCom.py b/pyload/plugins/account/ZeveraCom.py
new file mode 100644
index 000000000..dfd4f29f5
--- /dev/null
+++ b/pyload/plugins/account/ZeveraCom.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.internal.Account import Account
+
+
+class ZeveraCom(Account):
+ __name__ = "ZeveraCom"
+ __type__ = "account"
+ __version__ = "0.21"
+
+ __description__ = """Zevera.com account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAPIData(req)
+ if data == "No traffic":
+ account_info = {"trafficleft": 0, "validuntil": 0, "premium": False}
+ else:
+ account_info = {
+ "trafficleft": int(data['availabletodaytraffic']) * 1024,
+ "validuntil": mktime(strptime(data['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S")),
+ "premium": True
+ }
+ return account_info
+
+
+ def login(self, user, data, req):
+ self.loginname = user
+ self.password = data['password']
+ if self.getAPIData(req) == "No traffic":
+ self.wrongPassword()
+
+
+ def getAPIData(self, req, just_header=False, **kwargs):
+ get_data = {
+ 'cmd': 'accountinfo',
+ 'login': self.loginname,
+ 'pass': self.password
+ }
+ get_data.update(kwargs)
+
+ res = req.load("http://www.zevera.com/jDownloader.ashx", get=get_data,
+ decode=True, just_header=just_header)
+ 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/plugins/account/__init__.py b/pyload/plugins/account/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/account/__init__.py
diff --git a/pyload/plugins/addon/Checksum.py b/pyload/plugins/addon/Checksum.py
new file mode 100644
index 000000000..11154ac0f
--- /dev/null
+++ b/pyload/plugins/addon/Checksum.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import hashlib
+import re
+import zlib
+
+from os import remove
+from os.path import getsize, isfile, splitext
+
+from pyload.plugins.internal.Addon import Addon
+from pyload.utils import safe_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.15"
+
+ __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>.+)$'}
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def coreReady(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()
+
+ 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(safe_join(download_folder, pyfile.package().folder, pyfile.name))
+
+ if not 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 = 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")
+ del data['size']
+
+ # validate checksum
+ if data and self.getConfig("check_checksum"):
+ if "checksum" in data:
+ data['md5'] = data['checksum']
+
+ 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:
+ 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 = safe_join(self.config['general']['download_folder'], pypack.folder, "")
+
+ for link in pypack.getChildren().itervalues():
+ file_type = splitext(link['name'])[1][1:].lower()
+
+ if file_type not in self.formats:
+ continue
+
+ hash_file = fs_encode(safe_join(download_folder, link['name']))
+ if not 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(safe_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/plugins/addon/ClickAndLoad.py b/pyload/plugins/addon/ClickAndLoad.py
new file mode 100644
index 000000000..cad6e5c13
--- /dev/null
+++ b/pyload/plugins/addon/ClickAndLoad.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+from socket import socket, error
+from threading import Thread
+
+from pyload.plugins.internal.Addon import Addon
+
+
+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)
+
+
+class ClickAndLoad(Addon):
+ __name__ = "ClickAndLoad"
+ __type__ = "addon"
+ __version__ = "0.23"
+
+ __config__ = [("activated", "bool", "Activated" , True ),
+ ("port" , "int" , "Port" , 9666 ),
+ ("extern" , "bool", "Allow external link adding", False)]
+
+ __description__ = """Click'N'Load hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.de"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def coreReady(self):
+ self.interval = 300
+
+
+ def periodical(self):
+ webip = "0.0.0.0" if self.getConfig("extern") else "127.0.0.1"
+ webport = self.config['webinterface']['port']
+ cnlport = self.getConfig("port"))
+
+ try:
+ s = socket()
+ s.bind((webip, cnlport))
+ s.listen(5)
+
+ client = s.accept()[0]
+ server = socket()
+
+ server.connect(("127.0.0.1", webport))
+
+ except error, e:
+ if hasattr(e, "errno"):
+ errno = e.errno
+ else:
+ errno = e.args[0]
+
+ if errno == 98:
+ self.logWarning(_("Port %d already in use") % cnlport)
+ else:
+ self.logDebug(e)
+
+ else:
+ t = Thread(target=forward, args=[client, server])
+ t.setDaemon(True)
+ t.start()
+ self.interval = -1
+
diff --git a/pyload/plugins/addon/DeleteFinished.py b/pyload/plugins/addon/DeleteFinished.py
new file mode 100644
index 000000000..6c2ba38d8
--- /dev/null
+++ b/pyload/plugins/addon/DeleteFinished.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+from pyload.database import style
+from pyload.plugins.internal.Addon import Addon
+
+
+class DeleteFinished(Addon):
+ __name__ = "DeleteFinished"
+ __type__ = "addon"
+ __version__ = "1.11"
+
+ __config__ = [("activated" , "bool", "Activated" , False ),
+ ('interval' , 'int' , 'Delete every (hours)' , '72' ),
+ ('deloffline', 'bool', 'Delete packages with offline links', 'False')]
+
+ __description__ = """Automatically delete all finished packages from queue"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+
+ ## overwritten methods ##
+ 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.removeEvent('packageFinished', self.wakeup)
+
+
+ def coreReady(self):
+ self.info = {'sleep': True}
+ interval = self.getConfig('interval')
+ self.pluginConfigChanged(self.__name__, 'interval', interval)
+ 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.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.m.events:
+ if func in self.m.events[event]:
+ self.logDebug("Function already registered", func)
+ else:
+ self.m.events[event].append(func)
+ else:
+ self.m.events[event] = [func]
+
+
+ def setup(self):
+ self.m = self.manager
+ self.removeEvent = self.m.removeEvent
diff --git a/pyload/plugins/addon/DownloadScheduler.py b/pyload/plugins/addon/DownloadScheduler.py
new file mode 100644
index 000000000..ba7b14467
--- /dev/null
+++ b/pyload/plugins/addon/DownloadScheduler.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import localtime
+
+from pyload.plugins.internal.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")]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def setup(self):
+ self.cb = None #: callback to scheduler job; will be by removed AddonManager when addon 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 = 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/plugins/addon/ExternalScripts.py b/pyload/plugins/addon/ExternalScripts.py
new file mode 100644
index 000000000..1360e52de
--- /dev/null
+++ b/pyload/plugins/addon/ExternalScripts.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+
+import subprocess
+
+from itertools import chain
+from os import listdir, access, X_OK, makedirs
+from os.path import join, exists, basename, abspath
+
+from pyload.plugins.internal.Addon import Addon
+from pyload.utils import safe_join
+
+
+class ExternalScripts(Addon):
+ __name__ = "ExternalScripts"
+ __type__ = "addon"
+ __version__ = "0.25"
+
+ __config__ = [("activated", "bool", "Activated", True)]
+
+ __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_extracted", "package_extracted", "all_archives_extracted", "all_archives_processed",
+ "allDownloadsFinished", "allDownloadsProcessed"]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def setup(self):
+ self.scripts = {}
+
+ folders = ["download_preparing", "download_finished", "all_downloads_finished", "all_downloads_processed",
+ "before_reconnect", "after_reconnect",
+ "package_finished", "package_extracted",
+ "archive_extracted", "all_archives_extracted", "all_archives_processed",
+ # deprecated folders
+ "unrar_finished", "all_dls_finished", "all_dls_processed"]
+
+ for folder in folders:
+ self.scripts[folder] = []
+
+ self.initPluginType(folder, join(pypath, 'scripts', folder))
+ self.initPluginType(folder, join('scripts', folder))
+
+ for script_type, names in self.scripts.iteritems():
+ if names:
+ self.logInfo(_("Installed scripts for"), script_type, ", ".join([basename(x) for x in names]))
+
+
+ def initPluginType(self, folder, path):
+ if not exists(path):
+ try:
+ makedirs(path)
+ except Exception:
+ self.logDebug("Script folder %s not created" % folder)
+ return
+
+ for f in listdir(path):
+ if f.startswith("#") or f.startswith(".") or f.startswith("_") or f.endswith("~") or f.endswith(".swp"):
+ continue
+
+ if not access(join(path, f), X_OK):
+ self.logWarning(_("Script not executable:") + " %s/%s" % (folder, f))
+
+ self.scripts[folder].append(join(path, f))
+
+
+ def callScript(self, script, *args):
+ try:
+ cmd = [script] + [str(x) if not isinstance(x, basestring) else x for x in args]
+ self.logDebug("Executing", abspath(script), " ".join(cmd))
+ #output goes to pyload
+ subprocess.Popen(cmd, bufsize=-1)
+ except Exception, e:
+ self.logError(_("Error in %(script)s: %(error)s") % {"script": basename(script), "error": e})
+
+
+ def downloadPreparing(self, pyfile):
+ for script in self.scripts['download_preparing']:
+ self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.id)
+
+
+ def downloadFinished(self, pyfile):
+ download_folder = self.config['general']['download_folder']
+ for script in self.scripts['download_finished']:
+ filename = safe_join(download_folder, pyfile.package().folder, pyfile.name)
+ self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.name, filename, pyfile.id)
+
+
+ def packageFinished(self, pypack):
+ download_folder = self.config['general']['download_folder']
+ for script in self.scripts['package_finished']:
+ folder = safe_join(download_folder, pypack.folder)
+ self.callScript(script, pypack.name, folder, pypack.password, pypack.id)
+
+
+ def beforeReconnecting(self, ip):
+ for script in self.scripts['before_reconnect']:
+ self.callScript(script, ip)
+
+
+ def afterReconnecting(self, ip):
+ for script in self.scripts['after_reconnect']:
+ self.callScript(script, ip)
+
+
+ def archive_extracted(self, pyfile, folder, filename, files):
+ for script in self.scripts['archive_extracted']:
+ self.callScript(script, folder, filename, files)
+ for script in self.scripts['unrar_finished']: #: deprecated
+ self.callScript(script, folder, filename)
+
+
+ def package_extracted(self, pypack):
+ download_folder = self.config['general']['download_folder']
+ for script in self.scripts['package_extracted']:
+ folder = safe_join(download_folder, pypack.folder)
+ self.callScript(script, pypack.name, folder, pypack.password, pypack.id)
+
+
+ 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)
+
+
+ def allDownloadsFinished(self):
+ for script in chain(self.scripts['all_downloads_finished'], self.scripts['all_dls_finished']):
+ self.callScript(script)
+
+
+ def allDownloadsProcessed(self):
+ for script in chain(self.scripts['all_downloads_processed'], self.scripts['all_dls_processed']):
+ self.callScript(script)
diff --git a/pyload/plugins/addon/ExtractArchive.py b/pyload/plugins/addon/ExtractArchive.py
new file mode 100644
index 000000000..938de0447
--- /dev/null
+++ b/pyload/plugins/addon/ExtractArchive.py
@@ -0,0 +1,368 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import sys
+
+from copy import copy
+from os import remove, chmod, makedirs
+from os.path import exists, basename, isfile, isdir
+from traceback import print_exc
+
+# 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
+ from subprocess import Popen
+
+
+ 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
+
+ Popen.wait = wait
+
+if os.name != "nt":
+ from grp import getgrnam
+ from os import chown
+ from pwd import getpwnam
+
+from pyload.plugins.internal.Addon import Addon, threaded, Expose
+from pyload.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword
+from pyload.utils import safe_join, fs_encode
+
+
+class ExtractArchive(Addon):
+ __name__ = "ExtractArchive"
+ __type__ = "addon"
+ __version__ = "0.19"
+
+ __config__ = [("activated" , "bool" , "Activated" , True ),
+ ("fullpath" , "bool" , "Extract full path" , True ),
+ ("overwrite" , "bool" , "Overwrite files" , True ),
+ ("passwordfile" , "file" , "password file" , "archive_password.txt"),
+ ("deletearchive", "bool" , "Delete archives when done" , False ),
+ ("subfolder" , "bool" , "Create subfolder for each package" , False ),
+ ("destination" , "folder", "Extract files to" , "" ),
+ ("excludefiles" , "str" , "Exclude files from unpacking (seperated by ;)", "" ),
+ ("recursive" , "bool" , "Extract archives in archvies" , True ),
+ ("queue" , "bool" , "Wait for all downloads to be finished" , True ),
+ ("renice" , "int" , "CPU Priority" , 0 )]
+
+ __description__ = """Extract different kind of archives"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "ranan@pyload.org"),
+ ("AndroKev", None),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ event_list = ["allDownloadsProcessed"]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def setup(self):
+ self.plugins = []
+ self.passwords = []
+ names = []
+
+ for p in ("UnRar", "UnZip"):
+ try:
+ module = self.core.pluginManager.loadModule("internal", p)
+ klass = getattr(module, p)
+ if klass.checkDeps():
+ names.append(p)
+ self.plugins.append(klass)
+
+ except OSError, e:
+ if e.errno == 2:
+ self.logInfo(_("No %s installed") % p)
+ else:
+ self.logWarning(_("Could not activate %s") % p, e)
+ if self.core.debug:
+ print_exc()
+
+ except Exception, e:
+ self.logWarning(_("Could not activate %s") % p, e)
+ if self.core.debug:
+ print_exc()
+
+ if names:
+ self.logInfo(_("Activated") + " " + " ".join(names))
+ else:
+ self.logInfo(_("No Extract plugins activated"))
+
+ # queue with package ids
+ self.queue = []
+
+
+ @Expose
+ def extractPackage(self, id):
+ """ Extract package with given id"""
+ self.manager.startThread(self.extract, [id])
+
+
+ def packageFinished(self, pypack):
+ pid = pypack.id
+ if self.getConfig("queue"):
+ self.logInfo(_("Package %s queued for later extracting") % pypack.name)
+ self.queue.append(pid)
+ else:
+ self.manager.startThread(self.extract, [pid])
+
+
+ @threaded
+ def allDownloadsProcessed(self, thread):
+ local = copy(self.queue)
+ del self.queue[:]
+ if self.extract(local, thread): #: check only if all gone fine, no failed reporting for now
+ self.manager.dispatchEvent("all_archives_extracted")
+ self.manager.dispatchEvent("all_archives_processed")
+
+
+ def extract(self, ids, thread=None):
+ processed = []
+ extracted = []
+ failed = []
+
+ destination = self.getConfig("destination")
+ subfolder = self.getConfig("subfolder")
+ fullpath = self.getConfig("fullpath")
+ overwrite = self.getConfig("overwrite")
+ excludefiles = self.getConfig("excludefiles")
+ renice = self.getConfig("renice")
+ recursive = self.getConfig("recursive")
+
+ # reload from txt file
+ self.reloadPasswords()
+
+ # dl folder
+ dl = self.config['general']['download_folder']
+
+ #iterate packages -> plugins -> targets
+ for pid in ids:
+ p = self.core.files.getPackage(pid)
+ self.logInfo(_("Check package %s") % p.name)
+ if not p:
+ continue
+
+ # determine output folder
+ out = safe_join(dl, p.folder, "")
+
+ out = safe_join(dl, p.folder, self.getConfig("destination"), "")
+ if subfolder:
+ out = safe_join(out, fs_encode(p.folder))
+
+ if not exists(out):
+ makedirs(out)
+
+ files_ids = [(safe_join(dl, p.folder, x['name']), x['id']) for x in p.getChildren().itervalues()]
+ matched = False
+ success = True
+
+ # check as long there are unseen files
+ while files_ids:
+ new_files_ids = []
+
+ for plugin in self.plugins:
+ targets = plugin.getTargets(files_ids)
+ if targets:
+ self.logDebug("Targets for %s: %s" % (plugin.__name__, targets))
+ matched = True
+ for target, fid in targets:
+ if target in processed:
+ self.logDebug(basename(target), "skipped")
+ continue
+
+ processed.append(target) # prevent extracting same file twice
+
+ self.logInfo(basename(target), _("Extract to %s") % out)
+ try:
+ klass = plugin(self, target, out, fullpath, overwrite, excludefiles, renice)
+ klass.init()
+ password = p.password.strip().splitlines()
+ new_files = self._extract(klass, fid, password, thread)
+ except Exception, e:
+ self.logError(basename(target), e)
+ success = False
+ continue
+
+ self.logDebug("Extracted", new_files)
+ self.setPermissions(new_files)
+
+ for file in new_files:
+ if not exists(file):
+ self.logDebug("New file %s does not exists" % file)
+ continue
+ if recursive and isfile(file):
+ new_files_ids.append((file, fid)) # append as new target
+
+ files_ids = new_files_ids # also check extracted files
+
+ if matched:
+ if success:
+ extracted.append(pid)
+ self.manager.dispatchEvent("package_extracted", p)
+ else:
+ failed.append(pid)
+ self.manager.dispatchEvent("package_extract_failed", p)
+ else:
+ self.logInfo(_("No files found to extract"))
+
+ return True if not failed else False
+
+
+ def _extract(self, plugin, fid, passwords, thread):
+ pyfile = self.core.files.getFile(fid)
+ deletearchive = self.getConfig("deletearchive")
+
+ pyfile.setCustomStatus(_("extracting"))
+ thread.addActive(pyfile) # keep this file until everything is done
+
+ try:
+ progress = lambda x: pyfile.setProgress(x)
+ success = False
+
+ if not plugin.checkArchive():
+ plugin.extract(progress)
+ success = True
+ else:
+ self.logInfo(basename(plugin.file), _("Password protected"))
+ self.logDebug("Passwords", passwords)
+
+ pwlist = copy(self.getPasswords())
+ # remove already supplied pws from list (only local)
+ for pw in passwords:
+ if pw in pwlist:
+ pwlist.remove(pw)
+
+ for pw in passwords + pwlist:
+ try:
+ self.logDebug("Try password", pw)
+ if plugin.checkPassword(pw):
+ plugin.extract(progress, pw)
+ self.addPassword(pw)
+ success = True
+ break
+ except WrongPassword:
+ self.logDebug("Password was wrong")
+
+ if not success:
+ raise Exception(_("Wrong password"))
+
+ if self.core.debug:
+ self.logDebug("Would delete", ", ".join(plugin.getDeleteFiles()))
+
+ if deletearchive:
+ files = plugin.getDeleteFiles()
+ self.logInfo(_("Deleting %s files") % len(files))
+ for f in files:
+ if exists(f):
+ remove(f)
+ else:
+ self.logDebug("%s does not exists" % f)
+
+ self.logInfo(basename(plugin.file), _("Extracting finished"))
+
+ extracted_files = plugin.getExtractedFiles()
+ self.manager.dispatchEvent("archive_extracted", pyfile, plugin.out, plugin.file, extracted_files)
+
+ return extracted_files
+
+ except ArchiveError, e:
+ self.logError(basename(plugin.file), _("Archive Error"), e)
+ except CRCError:
+ self.logError(basename(plugin.file), _("CRC Mismatch"))
+ except Exception, e:
+ if self.core.debug:
+ print_exc()
+ self.logError(basename(plugin.file), _("Unknown Error"), e)
+
+ self.manager.dispatchEvent("archive_extract_failed", pyfile)
+ raise Exception(_("Extract failed"))
+
+
+ @Expose
+ def getPasswords(self):
+ """ List of saved passwords """
+ return self.passwords
+
+
+ def reloadPasswords(self):
+ passwordfile = self.getConfig("passwordfile")
+
+ try:
+ passwords = []
+ with open(passwordfile, "a+") 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, pw):
+ """ Adds a password to saved list"""
+ passwordfile = self.getConfig("passwordfile")
+
+ if pw in self.passwords:
+ self.passwords.remove(pw)
+
+ self.passwords.insert(0, pw)
+
+ try:
+ with open(passwordfile, "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 exists(f):
+ continue
+ try:
+ if self.config['permission']['change_file']:
+ if isfile(f):
+ chmod(f, int(self.config['permission']['file'], 8))
+ elif isdir(f):
+ 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]
+ chown(f, uid, gid)
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), e)
diff --git a/pyload/plugins/addon/HotFolder.py b/pyload/plugins/addon/HotFolder.py
new file mode 100644
index 000000000..b16c02cf8
--- /dev/null
+++ b/pyload/plugins/addon/HotFolder.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import time
+
+from os import listdir, makedirs
+from os.path import exists, isfile, join
+from shutil import move
+
+from pyload.plugins.internal.Addon import Addon
+from pyload.utils import fs_encode, safe_join
+
+
+class HotFolder(Addon):
+ __name__ = "HotFolder"
+ __type__ = "addon"
+ __version__ = "0.12"
+
+ __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 = 10
+
+
+ def periodical(self):
+ folder = fs_encode(self.getConfig("folder"))
+
+ try:
+ if not exists(join(folder, "finished")):
+ makedirs(join(folder, "finished"))
+
+ if self.getConfig("watch_file"):
+ with open(fs_encode(self.getConfig("file")), "a+") as f:
+ content = f.read().strip()
+
+ if content:
+ name = "%s_%s.txt" % (self.getConfig("file"), time.strftime("%H-%M-%S_%d%b%Y"))
+
+ with open(safe_join(folder, "finished", name), "wb") as f:
+ f.write(content)
+
+ self.core.api.addPackage(f.name, [f.name], 1)
+
+ for f in listdir(folder):
+ path = join(folder, f)
+
+ if not isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
+ continue
+
+ newpath = 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, e:
+ self.logError(e)
diff --git a/pyload/plugins/addon/IRCInterface.py b/pyload/plugins/addon/IRCInterface.py
new file mode 100644
index 000000000..5392b01a8
--- /dev/null
+++ b/pyload/plugins/addon/IRCInterface.py
@@ -0,0 +1,436 @@
+# -*- coding: utf-8 -*-
+
+import re
+import socket
+import ssl
+import time
+
+from pycurl import FORM_FILE
+from select import select
+from threading import Thread
+from time import sleep
+from traceback import print_exc
+
+from pyload.api import PackageDoesNotExists, FileDoesNotExists
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.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)
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ 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)
+
+ page = getURL("http://www.freeimagehosting.net/upload.php",
+ post={"attached": (FORM_FILE, task.captchaFile)}, multipart=True)
+
+ url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).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 custom 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")
+ print_exc()
+ self.sock.close()
+
+
+ def main_loop(self):
+ readbuffer = ""
+ while True:
+ 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/plugins/addon/MergeFiles.py b/pyload/plugins/addon/MergeFiles.py
new file mode 100644
index 000000000..2efc221dc
--- /dev/null
+++ b/pyload/plugins/addon/MergeFiles.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+
+from traceback import print_exc
+
+from pyload.plugins.internal.Addon import Addon, threaded
+from pyload.utils import safe_join, fs_encode
+
+
+class MergeFiles(Addon):
+ __name__ = "MergeFiles"
+ __type__ = "addon"
+ __version__ = "0.13"
+
+ __config__ = [("activated", "bool", "Activated", True)]
+
+ __description__ = """Merges parts splitted with hjsplit"""
+ __license__ = "GPLv3"
+ __authors__ = [("and9000", "me@has-no-mail.com")]
+
+
+ BUFFER_SIZE = 4096
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def setup(self):
+ # nothing to do
+ pass
+
+
+ @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 = safe_join(download_folder, pack.folder)
+
+ for name, file_list in files.iteritems():
+ self.logInfo(_("Starting merging of"), name)
+
+ final_file = open(save_join(download_folder, name), "wb")
+ 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(os.path.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:
+ print_exc()
+
+ finally:
+ pyfile.setProgress(100)
+ pyfile.setStatus("finished")
+ pyfile.release()
+
+ self.logInfo(_("Finished merging of"), name)
diff --git a/pyload/plugins/addon/MultiHome.py b/pyload/plugins/addon/MultiHome.py
new file mode 100644
index 000000000..bcb51254d
--- /dev/null
+++ b/pyload/plugins/addon/MultiHome.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.internal.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")]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def setup(self):
+ 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()
+
+
+ def __repr__(self):
+ return "<Interface - %s>" % self.adress
diff --git a/pyload/plugins/addon/RestartFailed.py b/pyload/plugins/addon/RestartFailed.py
new file mode 100644
index 000000000..861223f3d
--- /dev/null
+++ b/pyload/plugins/addon/RestartFailed.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Addon import Addon
+
+
+class RestartFailed(Addon):
+ __name__ = "RestartFailed"
+ __type__ = "addon"
+ __version__ = "1.57"
+
+ __config__ = [("activated", "bool", "Activated" , True),
+ ("interval" , "int" , "Check interval in minutes", 90 )]
+
+ __description__ = """Periodically restart all failed downloads in queue"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ MIN_INTERVAL = 15 * 60 #: 15m minimum check interval (value is in seconds)
+
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == "interval":
+ interval = value * 60
+ if self.MIN_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.interval = self.MIN_INTERVAL
+
+
+ def coreReady(self):
+ self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
diff --git a/pyload/plugins/addon/SkipRev.py b/pyload/plugins/addon/SkipRev.py
new file mode 100644
index 000000000..66ddc89d2
--- /dev/null
+++ b/pyload/plugins/addon/SkipRev.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.plugins.internal.Addon import Addon
+from pyload.plugins.Plugin import SkipDownload
+
+
+class SkipRev(Adoon):
+ __name__ = "SkipRev"
+ __type__ = "addon"
+ __version__ = "0.14"
+
+ __config__ = [("tokeep", "int", "Number of rev files to keep for package (-1 to auto)", -1)]
+
+ __description__ = """Skip files ending with extension rev"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def _setup(self):
+ super(self.pyfile.plugin, self).setup()
+ if self.pyfile.hasStatus("skipped"):
+ raise SkipDownload(self.pyfile.getStatusName() or self.pyfile.pluginname)
+
+
+ def pyname(self, pyfile):
+ url = pyfile.url
+ plugin = pyfile.plugin
+
+ if hasattr(plugin, "info") and 'name' in plugin.info and plugin.info['name']:
+ name = plugin.info['name']
+
+ elif hasattr(plugin, "parseInfo"):
+ name = next(plugin.parseInfo([url]))['name']
+
+ elif hasattr(plugin, "getInfo"): #@NOTE: if parseInfo was not found, getInfo should be missing too
+ name = plugin.getInfo(url)['name']
+
+ else:
+ self.logWarning("Unable to grab file name")
+ name = urlparse(unquote(url)).path.split('/')[-1])
+
+ return name
+
+
+ def downloadPreparing(self, pyfile):
+ if pyfile.getStatusName() is "unskipped" or not pyname(pyfile).endswith(".rev"):
+ return
+
+ tokeep = self.getConfig("tokeep")
+
+ if tokeep:
+ saved = [True for link in pyfile.package().getChildren() \
+ if link.name.endswith(".rev") and (link.hasStatus("finished") or link.hasStatus("downloading"))].count(True)
+
+ if not saved or saved < tokeep: #: keep one rev at least in auto mode
+ return
+
+ pyfile.setCustomStatus("SkipRev", "skipped")
+ pyfile.plugin.setup = _setup #: work-around: inject status checker inside the preprocessing routine of the plugin
+
+
+ def downloadFailed(self, pyfile):
+ tokeep = self.getConfig("tokeep")
+
+ if not tokeep:
+ return
+
+ for link in pyfile.package().getChildren():
+ if link.hasStatus("skipped") and link.name.endswith(".rev"):
+ if tokeep > -1 or pyfile.name.endswith(".rev"):
+ link.setStatus("queued")
+ else:
+ link.setCustomStatus("unskipped", "queued")
+ return
diff --git a/pyload/plugins/addon/UnSkipOnFail.py b/pyload/plugins/addon/UnSkipOnFail.py
new file mode 100644
index 000000000..a0b3ec540
--- /dev/null
+++ b/pyload/plugins/addon/UnSkipOnFail.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+from os.path import basename
+
+from pyload.datatype.PyFile import PyFile
+from pyload.plugins.internal.Addon import Addon
+from pyload.utils import fs_encode
+
+
+class UnSkipOnFail(Addon):
+ __name__ = "UnSkipOnFail"
+ __type__ = "addon"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", True)]
+
+ __description__ = """When a download fails, restart skipped duplicates"""
+ __license__ = "GPLv3"
+ __authors__ = [("hagg", None)]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def downloadFailed(self, pyfile):
+ pyfile_name = basename(pyfile.name)
+ pid = pyfile.package().id
+ msg = _('look for skipped duplicates for %s (pid:%s)')
+ self.logInfo(msg % (pyfile_name, pid))
+ dups = self.findDuplicates(pyfile)
+ for link in dups:
+ # check if link is "skipped"(=4)
+ if link.status == 4:
+ lpid = link.packageID
+ self.logInfo(_('restart "%s" (pid:%s)') % (pyfile_name, lpid))
+ self.setLinkStatus(link, "queued")
+
+
+ def findDuplicates(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 (basename(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.
+ """
+ dups = []
+ pyfile_name = fs_encode(basename(pyfile.name))
+ # get packages (w/o files, as most file data is useless here)
+ queue = self.core.api.getQueue()
+ for package in queue:
+ # check if package-folder equals pyfile's package folder
+ if fs_encode(package.folder) == fs_encode(pyfile.package().folder):
+ # now get packaged data w/ files/links
+ pdata = self.core.api.getPackageData(package.pid)
+ if pdata.links:
+ for link in pdata.links:
+ link_name = fs_encode(basename(link.name))
+ # check if link name collides with pdata's name
+ if link_name == pyfile_name:
+ # at last check if it is not pyfile itself
+ if link.fid != pyfile.id:
+ dups.append(link)
+ return dups
+
+
+ def setLinkStatus(self, link, new_status):
+ """ 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.
+ """
+ pyfile = PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
+ pyfile.setStatus(new_status)
+ self.core.files.save()
+ pyfile.release()
diff --git a/pyload/plugins/addon/UpdateManager.py b/pyload/plugins/addon/UpdateManager.py
new file mode 100644
index 000000000..97fa4a399
--- /dev/null
+++ b/pyload/plugins/addon/UpdateManager.py
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+import sys
+
+from operator import itemgetter
+from os import path, remove, stat
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Addon import Expose, Addon, threaded
+from pyload.utils import safe_join
+
+
+class UpdateManager(Addon):
+ __name__ = "UpdateManager"
+ __type__ = "addon"
+ __version__ = "0.42"
+
+ __config__ = [("activated" , "bool" , "Activated" , True ),
+ ("mode" , "pyLoad + plugins;plugins only", "Check updates for" , "pyLoad + plugins"),
+ ("interval" , "int" , "Check interval in hours" , 8 ),
+ ("autorestart" , "bool" , "Automatically restart pyLoad when required" , True ),
+ ("reloadplugins", "bool" , "Monitor plugins for code changes in debug mode", True ),
+ ("nodebugupdate", "bool" , "Don't check for updates in debug mode" , True )]
+
+ __description__ = """Check for updates"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ SERVER_URL = "http://updatemanager.pyload.org"
+ VERSION = re.compile(r'__version__.*=.*("|\')([\d.]+)')
+ MIN_INTERVAL = 3 * 60 * 60 #: 3h minimum check interval (value is in seconds)
+
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == "interval":
+ interval = value * 60 * 60
+ if self.MIN_INTERVAL <= interval != self.interval:
+ self.core.scheduler.removeJob(self.cb)
+ self.interval = interval
+ self.initPeriodical()
+ else:
+ self.logDebug("Invalid interval value, kept current")
+
+ elif name == "reloadplugins":
+ if self.cb2:
+ self.core.scheduler.removeJob(self.cb2)
+ if value is True and self.core.debug:
+ self.periodical2()
+
+
+ def coreReady(self):
+ self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
+ x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins"))
+ self.core.scheduler.addJob(10, x, threaded=False)
+
+
+ def unload(self):
+ self.pluginConfigChanged(self.__name__, "reloadplugins", False)
+
+
+ def setup(self):
+ self.cb2 = None
+ self.interval = self.MIN_INTERVAL
+ self.updating = False
+ self.info = {'pyload': False, 'version': None, 'plugins': False}
+ self.mtimes = {} #: store modification time for each plugin
+
+
+ def periodical2(self):
+ if not self.updating:
+ self.autoreloadPlugins()
+
+ self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False)
+
+
+ @Expose
+ def autoreloadPlugins(self):
+ """ reload and reindex all modified plugins """
+ modules = filter(
+ lambda m: m and (m.__name__.startswith("pyload.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 path.isfile(f):
+ continue
+
+ mtime = 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 periodical(self):
+ if self.info['pyload'] or self.getConfig("nodebugupdate") and self.core.debug:
+ return
+
+ self.updateThread()
+
+
+ def server_request(self):
+ try:
+ return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
+ except Exception:
+ self.logWarning(_("Unable to contact server to get updates"))
+
+
+ @threaded
+ def updateThread(self):
+ self.updating = True
+
+ status = self.update(onlyplugin=self.getConfig("mode") == "plugins only")
+
+ if status is 2 and self.getConfig("autorestart"):
+ self.core.api.restart()
+ else:
+ self.updating = False
+
+
+ @Expose
+ def updatePlugins(self):
+ """ simple wrapper for calling plugin update quickly """
+ return self.update(onlyplugin=True)
+
+
+ @Expose
+ def update(self, onlyplugin=False):
+ """ check for updates """
+ data = self.server_request()
+
+ if not data:
+ exitcode = 0
+
+ elif data[0] == "None":
+ self.logInfo(_("No new pyLoad version available"))
+ updates = data[1:]
+ exitcode = self._updatePlugins(updates)
+
+ elif onlyplugin:
+ exitcode = 0
+
+ else:
+ newversion = data[0]
+ self.logInfo(_("*** New pyLoad Version %s available ***") % newversion)
+ self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
+ exitcode = 3
+ self.info['pyload'] = True
+ self.info['version'] = newversion
+
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available
+
+
+ def _updatePlugins(self, updates):
+ """ check for plugin updates """
+
+ if self.info['plugins']:
+ return False #: plugins were already updated
+
+ exitcode = 0
+ updated = []
+
+ url = updates[0]
+ schema = updates[1].split('|')
+
+ if "BLACKLIST" in updates:
+ blacklist = updates[updates.index('BLACKLIST') + 1:]
+ updates = updates[2:updates.index('BLACKLIST')]
+ else:
+ blacklist = None
+ updates = updates[2:]
+
+ upgradable = [dict(zip(schema, x.split('|'))) for x in updates]
+ blacklisted = [(x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]) for x in blacklist] if blacklist else []
+
+ if blacklist:
+ # Protect internal plugins against removing
+ for i, t, n in enumerate(blacklisted):
+ if t == "internal":
+ blacklisted.pop(i)
+ continue
+
+ for idx, plugin in enumerate(upgradable):
+ if n == plugin['name'] and t == plugin['type']:
+ upgradable.pop(idx)
+ break
+
+ for t, n in self.removePlugins(sorted(blacklisted)):
+ self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % {
+ 'type': t,
+ 'name': n,
+ })
+
+ for plugin in sorted(upgradable, 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 = self.VERSION.search(content)
+
+ if m and m.group(2) == version:
+ with open(safe_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, str(e))
+
+ if updated:
+ reloaded = self.core.pluginManager.reloadPlugins(updated)
+ if reloaded:
+ self.logInfo(_("Plugins updated and reloaded"))
+ exitcode = 1
+ else:
+ self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***"))
+ self.info['plugins'] = True
+ exitcode = 2
+ else:
+ self.logInfo(_("No plugin updates available"))
+
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required
+
+
+ @Expose
+ def removePlugins(self, type_plugins):
+ """ delete plugins from disk """
+
+ if not type_plugins:
+ return
+
+ self.logDebug("Requested deletion of plugins: %s" % type_plugins)
+
+ removed = []
+
+ for type, name in type_plugins:
+ err = False
+ file = name + ".py"
+
+ for root in ("userplugins", path.join(pypath, "pyload", "plugins")):
+
+ filename = safe_join(root, type, file)
+ try:
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting: %s" % path.basename(filename), e)
+ err = True
+
+ filename += "c"
+ if path.isfile(filename):
+ try:
+ if type == "addon":
+ self.manager.deactivateAddon(name)
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting: %s" % path.basename(filename), e)
+ err = True
+
+ if not err:
+ id = (type, name)
+ removed.append(id)
+
+ return removed #: return a list of the plugins successfully removed
diff --git a/pyload/plugins/addon/WindowsPhoneToastNotify.py b/pyload/plugins/addon/WindowsPhoneToastNotify.py
new file mode 100644
index 000000000..fbaa89ea4
--- /dev/null
+++ b/pyload/plugins/addon/WindowsPhoneToastNotify.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import httplib
+import time
+
+from pyload.plugins.internal.Addon import Addon
+
+
+class WindowsPhoneToastNotify(Addon):
+ __name__ = "WindowsPhoneToastNotify"
+ __type__ = "addon"
+ __version__ = "0.03"
+
+ __config__ = [("force" , "bool", "Force even if client is connected" , False),
+ ("pushId" , "str" , "pushId" , "" ),
+ ("pushUrl" , "str" , "pushUrl" , "" ),
+ ("pushTimeout", "int" , "Timeout between notifications in seconds", 0 )]
+
+ __description__ = """Send push notifications to Windows Phone"""
+ __license__ = "GPLv3"
+ __authors__ = [("Andy Voigt", "phone-support@hotmail.de")]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def getXmlData(self):
+ myxml = ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
+ "<wp:Toast> <wp:Text1>Pyload Mobile</wp:Text1> <wp:Text2>Captcha waiting!</wp:Text2> "
+ "</wp:Toast> </wp:Notification>")
+ return myxml
+
+
+ def doRequest(self):
+ URL = self.getConfig("pushUrl")
+ request = self.getXmlData()
+ webservice = httplib.HTTP(URL)
+ webservice.putrequest("POST", self.getConfig("pushId"))
+ 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.setStorage("LAST_NOTIFY", time.time())
+
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig("pushId") or not self.getConfig("pushUrl"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if (time.time() - float(self.getStorage("LAST_NOTIFY", 0))) < self.getConf("pushTimeout"):
+ return False
+
+ self.doRequest()
diff --git a/pyload/plugins/addon/XMPPInterface.py b/pyload/plugins/addon/XMPPInterface.py
new file mode 100644
index 000000000..5bbff8915
--- /dev/null
+++ b/pyload/plugins/addon/XMPPInterface.py
@@ -0,0 +1,253 @@
+# -*- 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.plugins.addon.IRCInterface import IRCInterface
+
+
+class XMPPInterface(IRCInterface, JabberClient):
+ __name__ = "XMPPInterface"
+ __type__ = "addon"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated" , False ),
+ ("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/pyload/plugins/addon/__init__.py b/pyload/plugins/addon/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/addon/__init__.py
diff --git a/pyload/plugins/captcha/AdsCaptcha.py b/pyload/plugins/captcha/AdsCaptcha.py
new file mode 100644
index 000000000..fe8fec29b
--- /dev/null
+++ b/pyload/plugins/captcha/AdsCaptcha.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugins.internal.Captcha import Captcha
+
+
+class AdsCaptcha(Captcha):
+ __name__ = "AdsCaptcha"
+ __type__ = "captcha"
+ __version__ = "0.05"
+
+ __description__ = """AdsCaptcha captcha service plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ ID_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?[^"\']*CaptchaId=(?P<ID>\d+)'
+ KEY_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?[^"\']*PublicKey=(?P<KEY>[\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.error(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.ID_PATTERN, html)
+ n = re.search(self.KEY_PATTERN, html)
+ if m and n:
+ self.key = (m.group("ID"), m.group("KEY"))
+ self.plugin.logDebug("AdsCaptcha id|key: %s | %s" % self.key)
+ return self.key
+ else:
+ self.plugin.logDebug("AdsCaptcha id or key not found")
+ return None
+
+
+ def challenge(self, key=None): #: key is a tuple(CaptchaId, PublicKey)
+ if not key:
+ if self.detect_key():
+ key = self.key
+ else:
+ errmsg = _("AdsCaptcha key not found")
+ self.plugin.error(errmsg)
+ raise TypeError(errmsg)
+
+ CaptchaId, PublicKey = key
+
+ js = self.plugin.req.load("http://api.adscaptcha.com/Get.aspx", get={'CaptchaId': CaptchaId, 'PublicKey': PublicKey})
+ try:
+ challenge = re.search("challenge: '(.+?)',", js).group(1)
+ server = re.search("server: '(.+?)',", js).group(1)
+ except Exception:
+ self.plugin.error(_("AdsCaptcha challenge pattern not found"))
+
+ result = self.result(server, challenge)
+
+ self.plugin.logDebug("AdsCaptcha result: %s" % result, "challenge: %s" % challenge)
+
+ return challenge, result
+
+
+ def result(self, server, challenge):
+ return self.plugin.decryptCaptcha("%sChallenge.aspx" % server, get={'cid': challenge, 'dummy': random()},
+ cookies=True, imgtype="jpg")
diff --git a/pyload/plugins/captcha/ReCaptcha.py b/pyload/plugins/captcha/ReCaptcha.py
new file mode 100644
index 000000000..8e4d8c27c
--- /dev/null
+++ b/pyload/plugins/captcha/ReCaptcha.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Captcha import Captcha
+
+
+class ReCaptcha(Captcha):
+ __name__ = "ReCaptcha"
+ __type__ = "captcha"
+ __version__ = "0.08"
+
+ __description__ = """ReCaptcha captcha service plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ KEY_PATTERN = r'recaptcha(/api|\.net)/(challenge|noscript)\?k=(?P<KEY>[\w-]+)'
+ KEY_AJAX_PATTERN = r'Recaptcha\.create\s*\(\s*["\'](?P<KEY>[\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.error(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_PATTERN, html) or re.search(self.KEY_AJAX_PATTERN, html)
+ if m:
+ self.key = m.group("KEY")
+ self.plugin.logDebug("ReCaptcha key: %s" % self.key)
+ return self.key
+ else:
+ self.plugin.logDebug("ReCaptcha key not found")
+ return None
+
+
+ def challenge(self, key=None):
+ if not key:
+ if self.detect_key():
+ key = self.key
+ else:
+ errmsg = _("ReCaptcha key not found")
+ self.plugin.error(errmsg)
+ raise TypeError(errmsg)
+
+ js = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", get={'k': key})
+ try:
+ challenge = re.search("challenge : '(.+?)',", js).group(1)
+ server = re.search("server : '(.+?)',", js).group(1)
+ except Exception:
+ self.plugin.error(_("ReCaptcha challenge pattern not found"))
+
+ result = self.result(server, challenge)
+
+ self.plugin.logDebug("ReCaptcha result: %s" % result, "challenge: %s" % challenge)
+
+ return challenge, result
+
+
+ def result(self, server, challenge):
+ return self.plugin.decryptCaptcha("%simage" % server, get={'c': challenge},
+ cookies=True, forceUser=True, imgtype="jpg")
diff --git a/pyload/plugins/captcha/SolveMedia.py b/pyload/plugins/captcha/SolveMedia.py
new file mode 100644
index 000000000..5d5f6ea9a
--- /dev/null
+++ b/pyload/plugins/captcha/SolveMedia.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Captcha import Captcha
+
+
+class SolveMedia(Captcha):
+ __name__ = "SolveMedia"
+ __type__ = "captcha"
+ __version__ = "0.06"
+
+ __description__ = """SolveMedia captcha service plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ KEY_PATTERN = r'api\.solvemedia\.com/papi/challenge\.(no)?script\?k=(?P<KEY>.+?)["\']'
+
+
+ def challenge(self, key=None):
+ if not key:
+ if self.detect_key():
+ key = self.key
+ else:
+ errmsg = _("SolveMedia key not found")
+ self.plugin.error(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 Exception:
+ self.plugin.error(_("SolveMedia challenge pattern not found"))
+
+ result = self.result(server, challenge)
+
+ self.plugin.logDebug("SolveMedia result: %s" % result, "challenge: %s" % challenge)
+
+ return challenge, result
+
+
+ def result(self, server, challenge):
+ return self.plugin.decryptCaptcha(server, get={'c': challenge}, imgtype="gif")
diff --git a/pyload/plugins/captcha/__init__.py b/pyload/plugins/captcha/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/captcha/__init__.py
diff --git a/pyload/plugins/container/CCF.py b/pyload/plugins/container/CCF.py
new file mode 100644
index 000000000..a558baa03
--- /dev/null
+++ b/pyload/plugins/container/CCF.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from os import makedirs
+from os.path import exists
+from urllib2 import build_opener
+
+from MultipartPostHandler import MultipartPostHandler
+
+from pyload.plugins.internal.Container import Container
+from pyload.utils import safe_join
+
+
+class CCF(Container):
+ __name__ = "CCF"
+ __version__ = "0.20"
+
+ __pattern__ = r'.+\.ccf'
+
+ __description__ = """CCF container decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Willnix", "Willnix@pyload.org")]
+
+
+ def decrypt(self, pyfile):
+ infile = pyfile.url.replace("\n", "")
+
+ opener = build_opener(MultipartPostHandler)
+ params = {"src": "ccf",
+ "filename": "test.ccf",
+ "upload": open(infile, "rb")}
+ tempdlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', params).read()
+
+ download_folder = self.config['general']['download_folder']
+
+ tempdlc_name = safe_join(download_folder, "tmp_%s.dlc" % pyfile.name)
+ with open(tempdlc_name, "w") as tempdlc:
+ tempdlc.write(re.search(r'<dlc>(.*)</dlc>', tempdlc_content, re.S).group(1))
+
+ self.urls = [tempdlc_name]
diff --git a/pyload/plugins/container/LinkList.py b/pyload/plugins/container/LinkList.py
new file mode 100644
index 000000000..9e6396cab
--- /dev/null
+++ b/pyload/plugins/container/LinkList.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+
+from pyload.plugins.internal.Container import Container
+from pyload.utils import fs_encode
+
+
+class LinkList(Container):
+ __name__ = "LinkList"
+ __version__ = "0.12"
+
+ __pattern__ = r'.+\.txt'
+ __config__ = [("clear", "bool", "Clear Linklist after adding", False),
+ ("encoding", "string", "File encoding (default utf-8)", "")]
+
+ __description__ = """Read link lists in txt format"""
+ __license__ = "GPLv3"
+ __authors__ = [("spoob", "spoob@pyload.org"),
+ ("jeix", "jeix@hasnomail.com")]
+
+
+ def decrypt(self, pyfile):
+ try:
+ file_enc = codecs.lookup(self.getConfig("encoding")).name
+ except Exception:
+ file_enc = "utf-8"
+
+ file_name = fs_encode(pyfile.url)
+
+ txt = codecs.open(file_name, 'r', file_enc)
+ links = txt.readlines()
+ curPack = "Parsed links from %s" % pyfile.name
+
+ packages = {curPack:[],}
+
+ for link in links:
+ 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
+
+ delete = []
+
+ for key,value in packages.iteritems():
+ if not value:
+ delete.append(key)
+
+ for key in delete:
+ del packages[key]
+
+ if self.getConfig("clear"):
+ try:
+ txt = open(file_name, 'wb')
+ txt.close()
+ except Exception:
+ self.logWarning(_("LinkList could not be cleared"))
+
+ for name, links in packages.iteritems():
+ self.packages.append((name, links, name))
diff --git a/pyload/plugins/container/RSDF.py b/pyload/plugins/container/RSDF.py
new file mode 100644
index 000000000..41811469f
--- /dev/null
+++ b/pyload/plugins/container/RSDF.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import binascii
+import re
+
+from pyload.plugins.internal.Container import Container
+from pyload.utils import fs_encode
+
+
+class RSDF(Container):
+ __name__ = "RSDF"
+ __version__ = "0.24"
+
+ __pattern__ = r'.+\.rsdf'
+
+ __description__ = """RSDF container decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org")]
+
+
+ def decrypt(self, pyfile):
+
+ from Crypto.Cipher import AES
+
+ infile = fs_encode(pyfile.url.replace("\n", ""))
+ Key = binascii.unhexlify('8C35192D964DC3182C6F84F3252239EB4A320D2500000000')
+
+ IV = binascii.unhexlify('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
+ IV_Cipher = AES.new(Key, AES.MODE_ECB)
+ IV = IV_Cipher.encrypt(IV)
+
+ obj = AES.new(Key, AES.MODE_CFB, IV)
+
+ try:
+ with open(infile, 'r') as rsdf:
+ data = rsdf.read()
+ except IOError, e:
+ self.fail(str(e))
+
+ if re.search(r"<title>404 - Not Found</title>", data) is None:
+ data = binascii.unhexlify(''.join(data.split()))
+ data = data.splitlines()
+
+ for link in data:
+ if not link:
+ continue
+ link = base64.b64decode(link)
+ link = obj.decrypt(link)
+ decryptedUrl = link.replace('CCF: ', '')
+ self.urls.append(decryptedUrl)
+
+ self.logDebug("Adding package %s with %d links" % (pyfile.package().name, len(self.urls)))
diff --git a/pyload/plugins/container/__init__.py b/pyload/plugins/container/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/container/__init__.py
diff --git a/pyload/plugins/crypter/BitshareCom.py b/pyload/plugins/crypter/BitshareCom.py
new file mode 100644
index 000000000..723faf594
--- /dev/null
+++ b/pyload/plugins/crypter/BitshareCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class BitshareCom(SimpleCrypter):
+ __name__ = "BitshareCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/C1neonCom.py b/pyload/plugins/crypter/C1neonCom.py
new file mode 100644
index 000000000..c650d444b
--- /dev/null
+++ b/pyload/plugins/crypter/C1neonCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/ChipDe.py b/pyload/plugins/crypter/ChipDe.py
new file mode 100644
index 000000000..842fff292
--- /dev/null
+++ b/pyload/plugins/crypter/ChipDe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/CrockoCom.py b/pyload/plugins/crypter/CrockoCom.py
new file mode 100644
index 000000000..c959fa70a
--- /dev/null
+++ b/pyload/plugins/crypter/CrockoCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class CrockoCom(SimpleCrypter):
+ __name__ = "CrockoCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?crocko\.com/f/.*'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/CryptItCom.py b/pyload/plugins/crypter/CryptItCom.py
new file mode 100644
index 000000000..c420a25bc
--- /dev/null
+++ b/pyload/plugins/crypter/CryptItCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/CzshareCom.py b/pyload/plugins/crypter/CzshareCom.py
new file mode 100644
index 000000000..61d943222
--- /dev/null
+++ b/pyload/plugins/crypter/CzshareCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/DDLMusicOrg.py b/pyload/plugins/crypter/DDLMusicOrg.py
new file mode 100644
index 000000000..ec50a3265
--- /dev/null
+++ b/pyload/plugins/crypter/DDLMusicOrg.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import sleep
+
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class DDLMusicOrg(Crypter):
+ __name__ = "DDLMusicOrg"
+ __type__ = "crypter"
+ __version__ = "0.30"
+
+ __pattern__ = r'http://(?:www\.)?ddl-music\.org/captcha/ddlm_cr\d\.php\?\d+\?\d+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Ddl-music.org decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def setup(self):
+ self.multiDL = False
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url, cookies=True)
+
+ if re.search(r"Wer dies nicht rechnen kann", html) is not None:
+ self.offline()
+
+ math = re.search(r"(\d+) ([+-]) (\d+) =\s+<inp", self.html)
+ id = re.search(r"name=\"id\" value=\"(\d+)\"", self.html).group(1)
+ linknr = re.search(r"name=\"linknr\" value=\"(\d+)\"", self.html).group(1)
+
+ solve = ""
+ if math.group(2) == "+":
+ solve = int(math.group(1)) + int(math.group(3))
+ else:
+ solve = int(math.group(1)) - int(math.group(3))
+ sleep(3)
+ htmlwithlink = self.load(pyfile.url, cookies=True,
+ post={"calc%s" % linknr: solve, "send%s" % linknr: "Send", "id": id,
+ "linknr": linknr})
+ m = re.search(r"<form id=\"ff\" action=\"(.*?)\" method=\"post\">", htmlwithlink)
+ if m:
+ self.urls = [m.group(1)]
+ else:
+ self.retry()
diff --git a/pyload/plugins/crypter/DailymotionBatch.py b/pyload/plugins/crypter/DailymotionBatch.py
new file mode 100644
index 000000000..4ea26a830
--- /dev/null
+++ b/pyload/plugins/crypter/DailymotionBatch.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.utils import safe_join
+
+
+class DailymotionBatch(Crypter):
+ __name__ = "DailymotionBatch"
+ __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_package", "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 = urljoin("https://api.dailymotion.com/", ref)
+ page = self.load(url, get=req)
+ return json_loads(page)
+
+
+ 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 = safe_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/pyload/plugins/crypter/DataHu.py b/pyload/plugins/crypter/DataHu.py
new file mode 100644
index 000000000..2a02e03c8
--- /dev/null
+++ b/pyload/plugins/crypter/DataHu.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DataHu(SimpleCrypter):
+ __name__ = "DataHu"
+ __type__ = "crypter"
+ __version__ = "0.06"
+
+ __pattern__ = r'http://(?:www\.)?data\.hu/dir/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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(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/plugins/crypter/DdlstorageCom.py b/pyload/plugins/crypter/DdlstorageCom.py
new file mode 100644
index 000000000..95f771c9e
--- /dev/null
+++ b/pyload/plugins/crypter/DdlstorageCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(DdlstorageCom)
diff --git a/pyload/plugins/crypter/DepositfilesCom.py b/pyload/plugins/crypter/DepositfilesCom.py
new file mode 100644
index 000000000..9ba211607
--- /dev/null
+++ b/pyload/plugins/crypter/DepositfilesCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DepositfilesCom(SimpleCrypter):
+ __name__ = "DepositfilesCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?depositfiles\.com/folders/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/Dereferer.py b/pyload/plugins/crypter/Dereferer.py
new file mode 100644
index 000000000..94a3aa1e4
--- /dev/null
+++ b/pyload/plugins/crypter/Dereferer.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class Dereferer(Crypter):
+ __name__ = "Dereferer"
+ __type__ = "crypter"
+ __version__ = "0.10"
+
+ __pattern__ = r'https?://([^/]+)/.*?(?P<url>(ht|f)tps?(://|%3A%2F%2F).*)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Crypter for dereferers"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def decrypt(self, pyfile):
+ link = re.match(self.__pattern__, pyfile.url).group('url')
+ self.urls = [unquote(link).rstrip('+')]
diff --git a/pyload/plugins/crypter/DlProtectCom.py b/pyload/plugins/crypter/DlProtectCom.py
new file mode 100644
index 000000000..4f96a1d13
--- /dev/null
+++ b/pyload/plugins/crypter/DlProtectCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from base64 import urlsafe_b64encode
+from time import time
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DlProtectCom(SimpleCrypter):
+ __name__ = "DlProtectCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?dl-protect\.com/((en|fr)/)?(?P<ID>\w+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Dl-protect.com decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ 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"http://(?:www\.)?dl-protect\.com", self.req.http.lastEffectiveURL):
+ return [self.req.http.lastEffectiveURL]
+
+ #id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ key = re.search(r'name="id_key" value="(.+?)"', self.html).group(1)
+
+ post_req = {"id_key": key, "submitform": ""}
+
+ if self.OFFLINE_PATTERN in self.html:
+ self.offline()
+ elif ">Please click on continue to see the content" in self.html:
+ post_req.update({"submitform": "Continue"})
+ else:
+ mstime = int(round(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:]))
+
+ pattern = r'<a href="([^/].+?)" target="_blank">'
+ return re.findall(pattern, self.html)
diff --git a/pyload/plugins/crypter/DontKnowMe.py b/pyload/plugins/crypter/DontKnowMe.py
new file mode 100644
index 000000000..e48f56561
--- /dev/null
+++ b/pyload/plugins/crypter/DontKnowMe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class DontKnowMe(Crypter):
+ __name__ = "DontKnowMe"
+ __type__ = "crypter"
+ __version__ = "0.10"
+
+ __pattern__ = r'http://(?:www\.)?dontknow\.me/at/\?.+$'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """DontKnow.me decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("selaux", None)]
+
+
+ LINK_PATTERN = r'http://dontknow\.me/at/\?(.+)$'
+
+
+ def decrypt(self, pyfile):
+ link = re.findall(self.LINK_PATTERN, pyfile.url)[0]
+ self.urls = [unquote(link)]
diff --git a/pyload/plugins/crypter/DuckCryptInfo.py b/pyload/plugins/crypter/DuckCryptInfo.py
new file mode 100644
index 000000000..054783f3c
--- /dev/null
+++ b/pyload/plugins/crypter/DuckCryptInfo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugins.internal.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_package", "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/plugins/crypter/DuploadOrg.py b/pyload/plugins/crypter/DuploadOrg.py
new file mode 100644
index 000000000..209c98dc9
--- /dev/null
+++ b/pyload/plugins/crypter/DuploadOrg.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(DuploadOrg)
diff --git a/pyload/plugins/crypter/EasybytezCom.py b/pyload/plugins/crypter/EasybytezCom.py
new file mode 100644
index 000000000..4cd86e9b6
--- /dev/null
+++ b/pyload/plugins/crypter/EasybytezCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.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_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Easybytez.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ HOSTER_DOMAIN = "easybytez.com"
+
+ LOGIN_ACCOUNT = True
diff --git a/pyload/plugins/crypter/EmbeduploadCom.py b/pyload/plugins/crypter/EmbeduploadCom.py
new file mode 100644
index 000000000..c6649890f
--- /dev/null
+++ b/pyload/plugins/crypter/EmbeduploadCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/FilebeerInfo.py b/pyload/plugins/crypter/FilebeerInfo.py
new file mode 100644
index 000000000..514559f86
--- /dev/null
+++ b/pyload/plugins/crypter/FilebeerInfo.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class FilebeerInfo(DeadCrypter):
+ __name__ = "FilebeerInfo"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?filebeer\.info/(\d+~f).*'
+ __config__ = []
+
+ __description__ = """Filebeer.info folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(FilebeerInfo)
diff --git a/pyload/plugins/crypter/FilecloudIo.py b/pyload/plugins/crypter/FilecloudIo.py
new file mode 100644
index 000000000..6c8369925
--- /dev/null
+++ b/pyload/plugins/crypter/FilecloudIo.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FilecryptCc.py b/pyload/plugins/crypter/FilecryptCc.py
new file mode 100644
index 000000000..ee8b3a99f
--- /dev/null
+++ b/pyload/plugins/crypter/FilecryptCc.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class FilecryptCc(Crypter):
+ __name__ = "FilecryptCc"
+ __type__ = "crypter"
+ __version__ = "0.05"
+
+ __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="(.+?)"'
+
+ MIRROR_PAGE_PATTERN = r'"[\w]*" href="(http://filecrypt.cc/Container/\w+\.html\?mirror=\d+)">'
+
+
+ def setup(self):
+ self.links = []
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, cookies=True)
+
+ if "content not found" in self.html:
+ 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, cookies=True).decode("utf-8", "replace")
+
+
+ def handlePasswordProtection(self):
+ if '<input type="text" name="password"' not in self.html:
+ return
+
+ self.logInfo(_("Folder is password protected"))
+
+ if not self.pyfile.package().password:
+ self.fail(_("Please enter the password in package section and try again"))
+
+ self.html = self.load(self.pyfile.url, post={"password": self.password}, cookies=True)
+
+
+ def handleCaptcha(self):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+
+ if m:
+ self.logDebug("Captcha-URL: %s" % m.group(1))
+ captcha_code = self.decryptCaptcha("http://filecrypt.cc" + m.group(1), forceUser=True, imgtype="gif")
+ self.siteWithLinks = self.load(self.pyfile.url, post={"recaptcha_response_field":captcha_code}, decode=True, cookies=True)
+ else:
+ self.logDebug("No captcha found")
+ self.siteWithLinks = self.html
+
+ if "recaptcha_response_field" 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("http://filecrypt.cc/DLC/%s.dlc" % i)
+
+
+ def handleWeblinks(self):
+ try:
+ weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks)
+
+ for link in weblinks:
+ res = self.load("http://filecrypt.cc/Link/%s.html" % link, cookies=True)
+ link2 = re.search('<iframe noresize src="(.*)"></iframe>', res)
+ res2 = self.load(link2.group(1), just_header=True, cookies=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))
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ links = filter(lambda x: x != "",
+ text.replace("\x00", "").replace("\r", "").split("\n"))
+
+ return links
diff --git a/pyload/plugins/crypter/FilefactoryCom.py b/pyload/plugins/crypter/FilefactoryCom.py
new file mode 100644
index 000000000..cc9563ed9
--- /dev/null
+++ b/pyload/plugins/crypter/FilefactoryCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilefactoryCom(SimpleCrypter):
+ __name__ = "FilefactoryCom"
+ __type__ = "crypter"
+ __version__ = "0.31"
+
+ __pattern__ = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Filefactory.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<td><a href="([^"]+)">'
+ NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+)</span></h1>'
+ PAGES_PATTERN = r'data-paginator-totalPages="(\d+)"'
+
+ COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
+
+
+ def loadPage(self, page_n):
+ return self.load(self.pyfile.url, get={'page': page_n})
diff --git a/pyload/plugins/crypter/FilerNet.py b/pyload/plugins/crypter/FilerNet.py
new file mode 100644
index 000000000..4d28dc0da
--- /dev/null
+++ b/pyload/plugins/crypter/FilerNet.py
@@ -0,0 +1,26 @@
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilerNet(SimpleCrypter):
+ __name__ = "FilerNet"
+ __type__ = "crypter"
+ __version__ = "0.41"
+
+ __pattern__ = r'https?://filer\.net/folder/\w{16}'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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'
+
+
+ def getLinks(self):
+ return ['http://filer.net%s' % link for link in re.findall(self.LINK_PATTERN, self.html)]
diff --git a/pyload/plugins/crypter/FileserveCom.py b/pyload/plugins/crypter/FileserveCom.py
new file mode 100644
index 000000000..5ccccfc16
--- /dev/null
+++ b/pyload/plugins/crypter/FileserveCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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_package", "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/plugins/crypter/FilesonicCom.py b/pyload/plugins/crypter/FilesonicCom.py
new file mode 100644
index 000000000..0f07147bb
--- /dev/null
+++ b/pyload/plugins/crypter/FilesonicCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class FilesonicCom(DeadCrypter):
+ __name__ = "FilesonicCom"
+ __type__ = "crypter"
+ __version__ = "0.12"
+
+ __pattern__ = r'http://(?:www\.)?filesonic\.com/folder/\w+'
+
+ __description__ = """Filesonic.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(FilesonicCom)
diff --git a/pyload/plugins/crypter/FilestubeCom.py b/pyload/plugins/crypter/FilestubeCom.py
new file mode 100644
index 000000000..5c97bca9e
--- /dev/null
+++ b/pyload/plugins/crypter/FilestubeCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilestubeCom(SimpleCrypter):
+ __name__ = "FilestubeCom"
+ __type__ = "crypter"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FiletramCom.py b/pyload/plugins/crypter/FiletramCom.py
new file mode 100644
index 000000000..289642494
--- /dev/null
+++ b/pyload/plugins/crypter/FiletramCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FiletramCom(SimpleCrypter):
+ __name__ = "FiletramCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?filetram\.com/[^/]+/.+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FiredriveCom.py b/pyload/plugins/crypter/FiredriveCom.py
new file mode 100644
index 000000000..dfc85c957
--- /dev/null
+++ b/pyload/plugins/crypter/FiredriveCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(FiredriveCom)
diff --git a/pyload/plugins/crypter/FourChanOrg.py b/pyload/plugins/crypter/FourChanOrg.py
new file mode 100644
index 000000000..6ce037f22
--- /dev/null
+++ b/pyload/plugins/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.plugins.internal.Crypter import Crypter
+
+
+class FourChanOrg(Crypter):
+ __name__ = "FourChanOrg"
+ __type__ = "crypter"
+ __version__ = "0.30"
+
+ __pattern__ = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FreakhareCom.py b/pyload/plugins/crypter/FreakhareCom.py
new file mode 100644
index 000000000..792badeb4
--- /dev/null
+++ b/pyload/plugins/crypter/FreakhareCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreakhareCom(SimpleCrypter):
+ __name__ = "FreakhareCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?freakshare\.com/folder/.+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FreetexthostCom.py b/pyload/plugins/crypter/FreetexthostCom.py
new file mode 100644
index 000000000..13cb33f84
--- /dev/null
+++ b/pyload/plugins/crypter/FreetexthostCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreetexthostCom(SimpleCrypter):
+ __name__ = "FreetexthostCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?freetexthost\.com/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/FshareVn.py b/pyload/plugins/crypter/FshareVn.py
new file mode 100644
index 000000000..8b22b8bf2
--- /dev/null
+++ b/pyload/plugins/crypter/FshareVn.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FshareVn(SimpleCrypter):
+ __name__ = "FshareVn"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?fshare\.vn/folder/.*'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/Go4UpCom.py b/pyload/plugins/crypter/Go4UpCom.py
new file mode 100644
index 000000000..f63695912
--- /dev/null
+++ b/pyload/plugins/crypter/Go4UpCom.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
+
+
+class Go4UpCom(SimpleCrypter):
+ __name__ = "Go4UpCom"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)'
+
+ __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/.+?)"')
+ if m:
+ self.html = self.load(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:
+ continue
+
+ return links
+
+
+getInfo = create_getInfo(Go4UpCom)
diff --git a/pyload/plugins/crypter/GooGl.py b/pyload/plugins/crypter/GooGl.py
new file mode 100644
index 000000000..b5fb40f34
--- /dev/null
+++ b/pyload/plugins/crypter/GooGl.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.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_package", "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/plugins/crypter/HoerbuchIn.py b/pyload/plugins/crypter/HoerbuchIn.py
new file mode 100644
index 000000000..0bc374e7a
--- /dev/null
+++ b/pyload/plugins/crypter/HoerbuchIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
+
+from pyload.plugins.internal.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_package", "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/plugins/crypter/HotfileCom.py b/pyload/plugins/crypter/HotfileCom.py
new file mode 100644
index 000000000..f64968e15
--- /dev/null
+++ b/pyload/plugins/crypter/HotfileCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(HotfileCom)
diff --git a/pyload/plugins/crypter/ILoadTo.py b/pyload/plugins/crypter/ILoadTo.py
new file mode 100644
index 000000000..2a58a57d0
--- /dev/null
+++ b/pyload/plugins/crypter/ILoadTo.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/ImgurComAlbum.py b/pyload/plugins/crypter/ImgurComAlbum.py
new file mode 100644
index 000000000..1dc717ca9
--- /dev/null
+++ b/pyload/plugins/crypter/ImgurComAlbum.py
@@ -0,0 +1,27 @@
+import re
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/JunocloudMe.py b/pyload/plugins/crypter/JunocloudMe.py
new file mode 100644
index 000000000..3036be616
--- /dev/null
+++ b/pyload/plugins/crypter/JunocloudMe.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSCrypter import XFSCrypter
+
+
+class JunocloudMe(XFSCrypter):
+ __name__ = "JunocloudMe"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?junocloud\.me/folders/(?P<ID>\d+/\w+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Junocloud.me folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "junocloud.me"
diff --git a/pyload/plugins/crypter/LetitbitNet.py b/pyload/plugins/crypter/LetitbitNet.py
new file mode 100644
index 000000000..5706465bf
--- /dev/null
+++ b/pyload/plugins/crypter/LetitbitNet.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/LinkCryptWs.py b/pyload/plugins/crypter/LinkCryptWs.py
new file mode 100644
index 000000000..219d836dd
--- /dev/null
+++ b/pyload/plugins/crypter/LinkCryptWs.py
@@ -0,0 +1,327 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+import pycurl
+
+from Crypto.Cipher import AES
+
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.utils import html_unescape
+
+
+class LinkCryptWs(Crypter):
+ __name__ = "LinkCryptWs"
+ __type__ = "crypter"
+ __version__ = "0.07"
+
+ __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(4, 30, _("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'Key[ -]', 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)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+
+ return links
diff --git a/pyload/plugins/crypter/LinkSaveIn.py b/pyload/plugins/crypter/LinkSaveIn.py
new file mode 100644
index 000000000..e39d1a13d
--- /dev/null
+++ b/pyload/plugins/crypter/LinkSaveIn.py
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+#
+# * cnl2 and web links are skipped if JS is not available (instead of failing the package)
+# * only best available link source is used (priority: cnl2>rsdf>ccf>dlc>web
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import html_unescape
+
+
+class LinkSaveIn(SimpleCrypter):
+ __name__ = "LinkSaveIn"
+ __type__ = "crypter"
+ __version__ = "2.02"
+
+ __pattern__ = r'http://(?:www\.)?linksave\.in/(?P<id>\w+)$'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """LinkSave.in decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ COOKIES = [("linksave.in", "Linksave_Language", "english")]
+
+ # Constants
+ _JK_KEY_ = "jk"
+ _CRYPTED_KEY_ = "crypted"
+
+
+ def setup(self):
+ self.fileid = None
+ self.captcha = False
+ self.package = None
+ self.preferred_sources = ["cnl2", "rsdf", "ccf", "dlc", "web"]
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.package = pyfile.package()
+ self.fileid = re.match(self.__pattern__, pyfile.url).group('id')
+
+ # Request package
+ self.html = self.load(pyfile.url)
+ 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 type_ in self.preferred_sources:
+ package_links.extend(self.handleLinkSource(type_))
+ 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 isOnline(self):
+ if "<big>Error 404 - Folder not found!</big>" in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+
+ def isPasswordProtected(self):
+ if re.search(r'''<input.*?type="password"''', self.html):
+ self.logDebug("Links are password protected")
+ return True
+
+
+ def isCaptchaProtected(self):
+ if "<b>Captcha:</b>" 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)
+ post = {"id": self.fileid, "besucherpasswort": password, 'login': 'submit'}
+ self.html = self.load(self.pyfile.url, post=post)
+
+
+ def unlockCaptchaProtection(self):
+ captcha_hash = re.search(r'name="hash" value="([^"]+)', self.html).group(1)
+ captcha_url = re.search(r'src=".(/captcha/cap.php\?hsh=[^"]+)', self.html).group(1)
+ captcha_code = self.decryptCaptcha("http://linksave.in" + captcha_url, forceUser=True)
+ self.html = self.load(self.pyfile.url, post={"id": self.fileid, "hash": captcha_hash, "code": captcha_code})
+
+
+ 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 handleErrors(self):
+ if "The visitorpassword you have entered is 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 "Wrong code. Please retry" in self.html:
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+
+ def handleLinkSource(self, type_):
+ if type_ == "cnl2":
+ return self.handleCNL2()
+ elif type_ in ("rsdf", "ccf", "dlc"):
+ return self.handleContainer(type_)
+ elif type_ == "web":
+ return self.handleWebLinks()
+ else:
+ self.error('Unknown source type "%s" (this is probably a bug)' % type_)
+
+
+ def handleWebLinks(self):
+ package_links = []
+ self.logDebug("Search for Web links")
+ if not self.js:
+ self.logDebug("No JS -> skip Web links")
+ else:
+ #@TODO: Gather paginated web links
+ pattern = r'<a href="http://linksave\.in/(\w{43})"'
+ ids = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Web links" % len(ids))
+ for i, weblink_id in enumerate(ids):
+ try:
+ webLink = "http://linksave.in/%s" % weblink_id
+
+ self.logDebug("Decrypting Web link %d, %s" % (i + 1, webLink))
+
+ fwLink = "http://linksave.in/fw-%s" % weblink_id
+ res = self.load(fwLink)
+
+ jscode = re.findall(r'<script type="text/javascript">(.*)</script>', res)[-1]
+ jseval = self.js.eval("document = { write: function(e) { return e; } }; %s" % jscode)
+ dlLink = re.search(r'http://linksave\.in/dl-\w+', jseval).group(0)
+ self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
+
+ res = self.load(dlLink)
+ link = html_unescape(re.search(r'<iframe src="(.+?)"', res).group(1))
+
+ package_links.append(link)
+
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (webLink, detail))
+
+ return package_links
+
+
+ def handleContainer(self, type_):
+ package_links = []
+ type_ = type_.lower()
+ self.logDebug("Seach 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.error('Unknown container type "%s" (this is probably a bug)' % type_)
+ pattern = r'\(\'%s_link\'\).href=unescape\(\'(.*?\.%s)\'\)' % (type_, type_)
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Found %d %s Container links" % (len(containersLinks), type_.upper()))
+ for containerLink in containersLinks:
+ link = "http://linksave.in/%s" % html_unescape(containerLink)
+ package_links.append(link)
+ return package_links
+
+
+ def handleCNL2(self):
+ package_links = []
+ self.logDebug("Search for CNL2 links")
+ if not self.js:
+ self.logDebug("No JS -> skip CNL2 links")
+ elif 'cnl2_load' in self.html:
+ 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 _getCipherParams(self):
+ # Get jk
+ jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._JK_KEY_
+ vjk = re.findall(jk_re, self.html)
+
+ # Get crypted
+ crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._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)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/LinkdecrypterCom.py b/pyload/plugins/crypter/LinkdecrypterCom.py
new file mode 100644
index 000000000..57045c9d0
--- /dev/null
+++ b/pyload/plugins/crypter/LinkdecrypterCom.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class LinkdecrypterCom(Crypter):
+ __name__ = "LinkdecrypterCom"
+ __type__ = "crypter"
+ __version__ = "0.27"
+
+ __pattern__ = r'^unmatchable$'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Linkdecrypter.com"""
+ __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 decrypt(self, pyfile):
+ self.passwords = self.getPassword().splitlines()
+
+ # API not working anymore
+ self.urls = self.decryptHTML()
+
+
+ def decryptAPI(self):
+ get_dict = {"t": "link", "url": self.pyfile.url, "lcache": "1"}
+ self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
+ if self.html.startswith('http://'):
+ return self.html.splitlines()
+
+ if self.html == 'INTERRUPTION(PASSWORD)':
+ for get_dict['pass'] in self.passwords:
+ self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
+ if self.html.startswith('http://'):
+ return self.html.splitlines()
+
+ self.logError("API", self.html)
+ if self.html == 'INTERRUPTION(PASSWORD)':
+ self.fail(_("No or incorrect password"))
+
+ return None
+
+
+ def decryptHTML(self):
+ retries = 5
+
+ post_dict = {"link_cache": "on", "pro_links": self.pyfile.url, "modo_links": "text"}
+ self.html = self.load('http://linkdecrypter.com/', post=post_dict, cookies=True, decode=True)
+
+ while self.passwords or retries:
+ m = re.search(self.TEXTAREA_PATTERN, self.html, flags=re.S)
+ if m:
+ return [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.passwords:
+ password = self.passwords.pop(0)
+ self.logInfo(_("Password protected link, trying ") + password)
+ self.html = self.load('http://linkdecrypter.com/', post={'password': password}, decode=True)
+ else:
+ self.fail(_("No or incorrect password"))
+
+ else:
+ retries -= 1
+ self.html = self.load('http://linkdecrypter.com/', cookies=True, decode=True)
+
+ return None
diff --git a/pyload/plugins/crypter/LixIn.py b/pyload/plugins/crypter/LixIn.py
new file mode 100644
index 000000000..2394a17b8
--- /dev/null
+++ b/pyload/plugins/crypter/LixIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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_package", "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="(?P<image>captcha_img\.php\?.*?)"'
+ SUBMIT_PATTERN = r'value=\'continue.*?\''
+ LINK_PATTERN = r'name="ifram" src="(?P<link>.*?)"'
+
+
+ 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("image"))
+ 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("link")]
+ self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/pyload/plugins/crypter/LofCc.py b/pyload/plugins/crypter/LofCc.py
new file mode 100644
index 000000000..89e34d58b
--- /dev/null
+++ b/pyload/plugins/crypter/LofCc.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/MBLinkInfo.py b/pyload/plugins/crypter/MBLinkInfo.py
new file mode 100644
index 000000000..efd9ade8a
--- /dev/null
+++ b/pyload/plugins/crypter/MBLinkInfo.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/MediafireCom.py b/pyload/plugins/crypter/MediafireCom.py
new file mode 100644
index 000000000..28c4fa984
--- /dev/null
+++ b/pyload/plugins/crypter/MediafireCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.plugins.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_package", "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/plugins/crypter/MegaRapidCz.py b/pyload/plugins/crypter/MegaRapidCz.py
new file mode 100644
index 000000000..da797965e
--- /dev/null
+++ b/pyload/plugins/crypter/MegaRapidCz.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/MegauploadCom.py b/pyload/plugins/crypter/MegauploadCom.py
new file mode 100644
index 000000000..af4ebf5c4
--- /dev/null
+++ b/pyload/plugins/crypter/MegauploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class MegauploadCom(DeadCrypter):
+ __name__ = "MegauploadCom"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+'
+
+ __description__ = """Megaupload.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(MegauploadCom)
diff --git a/pyload/plugins/crypter/Movie2kTo.py b/pyload/plugins/crypter/Movie2kTo.py
new file mode 100644
index 000000000..cd2138d9a
--- /dev/null
+++ b/pyload/plugins/crypter/Movie2kTo.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/MultiUpOrg.py b/pyload/plugins/crypter/MultiUpOrg.py
new file mode 100644
index 000000000..ba5549683
--- /dev/null
+++ b/pyload/plugins/crypter/MultiUpOrg.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+from urlparse import urljoin
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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 = 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/plugins/crypter/MultiloadCz.py b/pyload/plugins/crypter/MultiloadCz.py
new file mode 100644
index 000000000..75f232929
--- /dev/null
+++ b/pyload/plugins/crypter/MultiloadCz.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/MultiuploadCom.py b/pyload/plugins/crypter/MultiuploadCom.py
new file mode 100644
index 000000000..55bb39cc9
--- /dev/null
+++ b/pyload/plugins/crypter/MultiuploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """MultiUpload.com decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(MultiuploadCom)
diff --git a/pyload/plugins/crypter/NCryptIn.py b/pyload/plugins/crypter/NCryptIn.py
new file mode 100644
index 000000000..852c75b54
--- /dev/null
+++ b/pyload/plugins/crypter/NCryptIn.py
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.plugins.internal.captcha import ReCaptcha
+
+
+class NCryptIn(Crypter):
+ __name__ = "NCryptIn"
+ __type__ = "crypter"
+ __version__ = "1.33"
+
+ __pattern__ = r'http://(?:www\.)?ncrypt\.in/(?P<type>folder|link|frame)-([^/\?]+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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 is not None:
+ 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)
+ challenge, response = 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" (this is probably a bug)' % 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)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/NetfolderIn.py b/pyload/plugins/crypter/NetfolderIn.py
new file mode 100644
index 000000000..bba72c047
--- /dev/null
+++ b/pyload/plugins/crypter/NetfolderIn.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class NetfolderIn(SimpleCrypter):
+ __name__ = "NetfolderIn"
+ __type__ = "crypter"
+ __version__ = "0.72"
+
+ __pattern__ = r'http://(?:www\.)?netfolder\.in/((?P<id1>\w+)/\w+|folder\.php\?folder_id=(?P<id2>\w+))'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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 = max(m.group('id1'), m.group('id2'))
+ 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/plugins/crypter/NosvideoCom.py b/pyload/plugins/crypter/NosvideoCom.py
new file mode 100644
index 000000000..5bd3b16a0
--- /dev/null
+++ b/pyload/plugins/crypter/NosvideoCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class NosvideoCom(SimpleCrypter):
+ __name__ = "NosvideoCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/OneKhDe.py b/pyload/plugins/crypter/OneKhDe.py
new file mode 100644
index 000000000..14b48549e
--- /dev/null
+++ b/pyload/plugins/crypter/OneKhDe.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import html_unescape
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class OneKhDe(Crypter):
+ __name__ = "OneKhDe"
+ __type__ = "crypter"
+ __version__ = "0.10"
+
+ __pattern__ = r'http://(?:www\.)?1kh\.de/f/'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/OronCom.py b/pyload/plugins/crypter/OronCom.py
new file mode 100644
index 000000000..ae695ef34
--- /dev/null
+++ b/pyload/plugins/crypter/OronCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(OronCom)
diff --git a/pyload/plugins/crypter/PastebinCom.py b/pyload/plugins/crypter/PastebinCom.py
new file mode 100644
index 000000000..e47698752
--- /dev/null
+++ b/pyload/plugins/crypter/PastebinCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class PastebinCom(SimpleCrypter):
+ __name__ = "PastebinCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?pastebin\.com/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/QuickshareCz.py b/pyload/plugins/crypter/QuickshareCz.py
new file mode 100644
index 000000000..c1064bd20
--- /dev/null
+++ b/pyload/plugins/crypter/QuickshareCz.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/RSLayerCom.py b/pyload/plugins/crypter/RSLayerCom.py
new file mode 100644
index 000000000..400c190d5
--- /dev/null
+++ b/pyload/plugins/crypter/RSLayerCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/RapidfileshareNet.py b/pyload/plugins/crypter/RapidfileshareNet.py
new file mode 100644
index 000000000..bc318a01e
--- /dev/null
+++ b/pyload/plugins/crypter/RapidfileshareNet.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSCrypter import XFSCrypter
+
+
+class RapidfileshareNet(XFSCrypter):
+ __name__ = "RapidfileshareNet"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?rapidfileshare\.net/users/\w+/\d+/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Rapidfileshare.net folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "rapidfileshare.net"
diff --git a/pyload/plugins/crypter/RelinkUs.py b/pyload/plugins/crypter/RelinkUs.py
new file mode 100644
index 000000000..2eb72dd17
--- /dev/null
+++ b/pyload/plugins/crypter/RelinkUs.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import binascii
+import re
+import os
+
+from Crypto.Cipher import AES
+from pyload.plugins.internal.Crypter import Crypter
+
+
+class RelinkUs(Crypter):
+ __name__ = "RelinkUs"
+ __type__ = "crypter"
+ __version__ = "3.11"
+
+ __pattern__ = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<id>.+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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\(\'(?P<link>.+)\'\)'
+ WEB_FORWARD_URL = r'http://www.relink.us/frame.php'
+ WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(?P<link>.+)"></iframe>'
+
+
+ def setup(self):
+ self.fileid = None
+ self.package = None
+ self.password = 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()
+ self.password = self.getPassword()
+
+
+ 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):
+ self.logDebug("Submitting password [%s] for protected links" % self.password)
+ passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
+ passwd_data = {'id': self.fileid, 'password': self.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 is not None:
+ 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" (this is probably a bug)' % 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 is not None:
+ 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 is not None:
+ 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 = os.path.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('link')
+
+ 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)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/SafelinkingNet.py b/pyload/plugins/crypter/SafelinkingNet.py
new file mode 100644
index 000000000..06243d069
--- /dev/null
+++ b/pyload/plugins/crypter/SafelinkingNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.plugins.internal.captcha import SolveMedia
+
+
+class SafelinkingNet(Crypter):
+ __name__ = "SafelinkingNet"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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"}
+
+ 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"))
+
+ challenge, response = 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/plugins/crypter/SecuredIn.py b/pyload/plugins/crypter/SecuredIn.py
new file mode 100644
index 000000000..cea23a53c
--- /dev/null
+++ b/pyload/plugins/crypter/SecuredIn.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/SexuriaCom.py b/pyload/plugins/crypter/SexuriaCom.py
new file mode 100644
index 000000000..dd9b01bcd
--- /dev/null
+++ b/pyload/plugins/crypter/SexuriaCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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_package", "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', flags=re.I)
+ PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', flags=re.I)
+ PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', flags=re.I)
+ PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', flags=re.I)
+ PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', flags=re.I | re.S)
+ PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', flags=re.I)
+ PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', flags=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/plugins/crypter/ShareLinksBiz.py b/pyload/plugins/crypter/ShareLinksBiz.py
new file mode 100644
index 000000000..64b198100
--- /dev/null
+++ b/pyload/plugins/crypter/ShareLinksBiz.py
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+from pyload.plugins.internal.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_package", "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 is not None:
+ 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(base64.standard_b64decode(params[1]))
+ strlist.reverse()
+ jk = ''.join(strlist)
+
+ # Get crypted
+ strlist = list(base64.standard_b64decode(params[2]))
+ strlist.reverse()
+ crypted = ''.join(strlist)
+
+ # 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)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/SharingmatrixCom.py b/pyload/plugins/crypter/SharingmatrixCom.py
new file mode 100644
index 000000000..3ada25537
--- /dev/null
+++ b/pyload/plugins/crypter/SharingmatrixCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class SharingmatrixCom(DeadCrypter):
+ __name__ = "SharingmatrixCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+'
+
+ __description__ = """Sharingmatrix.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(SharingmatrixCom)
diff --git a/pyload/plugins/crypter/SpeedLoadOrg.py b/pyload/plugins/crypter/SpeedLoadOrg.py
new file mode 100644
index 000000000..5819c78c9
--- /dev/null
+++ b/pyload/plugins/crypter/SpeedLoadOrg.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+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")]
+
+
+getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/pyload/plugins/crypter/StealthTo.py b/pyload/plugins/crypter/StealthTo.py
new file mode 100644
index 000000000..c5236c3e9
--- /dev/null
+++ b/pyload/plugins/crypter/StealthTo.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/TnyCz.py b/pyload/plugins/crypter/TnyCz.py
new file mode 100644
index 000000000..b2a777da2
--- /dev/null
+++ b/pyload/plugins/crypter/TnyCz.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/TrailerzoneInfo.py b/pyload/plugins/crypter/TrailerzoneInfo.py
new file mode 100644
index 000000000..a1a7e8477
--- /dev/null
+++ b/pyload/plugins/crypter/TrailerzoneInfo.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/TurbobitNet.py b/pyload/plugins/crypter/TurbobitNet.py
new file mode 100644
index 000000000..e038b9a34
--- /dev/null
+++ b/pyload/plugins/crypter/TurbobitNet.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/TusfilesNet.py b/pyload/plugins/crypter/TusfilesNet.py
new file mode 100644
index 000000000..ef00ba440
--- /dev/null
+++ b/pyload/plugins/crypter/TusfilesNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import math
+import re
+from urlparse import urljoin
+
+from pyload.plugins.internal.XFSCrypter import XFSCrypter
+
+
+class TusfilesNet(XFSCrypter):
+ __name__ = "TusfilesNet"
+ __type__ = "crypter"
+ __version__ = "0.07"
+
+ __pattern__ = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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")]
+
+
+ HOSTER_DOMAIN = "tusfiles.net"
+
+ PAGES_PATTERN = r'>\((\d+) \w+\)<'
+
+ URL_REPLACEMENTS = [(__pattern__ + ".*", r'https://www.tusfiles.net/go/\g<ID>/')]
+
+
+ def loadPage(self, page_n):
+ return self.load(urljoin(self.pyfile.url, str(page_n)), decode=True)
+
+
+ def handleMultiPages(self):
+ 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/plugins/crypter/UlozTo.py b/pyload/plugins/crypter/UlozTo.py
new file mode 100644
index 000000000..53380b5da
--- /dev/null
+++ b/pyload/plugins/crypter/UlozTo.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.internal.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_package", "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/plugins/crypter/UploadableCh.py b/pyload/plugins/crypter/UploadableCh.py
new file mode 100644
index 000000000..4edc08846
--- /dev/null
+++ b/pyload/plugins/crypter/UploadableCh.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadableCh(SimpleCrypter):
+ __name__ = "UploadableCh"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?uploadable\.ch/list/\w+'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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/plugins/crypter/UploadedTo.py b/pyload/plugins/crypter/UploadedTo.py
new file mode 100644
index 000000000..1fbed5f86
--- /dev/null
+++ b/pyload/plugins/crypter/UploadedTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.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_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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="(?P<plain>[\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 = urljoin("http://uploaded.net/", m.group('plain'))
+ return self.load(plain_link).split('\n')[:-1]
diff --git a/pyload/plugins/crypter/WiiReloadedOrg.py b/pyload/plugins/crypter/WiiReloadedOrg.py
new file mode 100644
index 000000000..d80a03940
--- /dev/null
+++ b/pyload/plugins/crypter/WiiReloadedOrg.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/pyload/plugins/crypter/WuploadCom.py b/pyload/plugins/crypter/WuploadCom.py
new file mode 100644
index 000000000..f64b9afb6
--- /dev/null
+++ b/pyload/plugins/crypter/WuploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class WuploadCom(DeadCrypter):
+ __name__ = "WuploadCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?wupload\.com/folder/\w+'
+
+ __description__ = """Wupload.com folder decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(WuploadCom)
diff --git a/pyload/plugins/crypter/XFileSharingPro.py b/pyload/plugins/crypter/XFileSharingPro.py
new file mode 100644
index 000000000..27de7f997
--- /dev/null
+++ b/pyload/plugins/crypter/XFileSharingPro.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.XFSCrypter import XFSCrypter
+
+
+class XFileSharingPro(XFSCrypter):
+ __name__ = "XFileSharingPro"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'^unmatchable$'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True),
+ ("subfolder_per_package", "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(XFileSharingPro, self).init()
+
+ self.__pattern__ = self.core.pluginManager.crypterPlugins[self.__name__]['pattern']
+
+ self.HOSTER_DOMAIN = re.match(self.__pattern__, self.pyfile.url).group(1).lower()
+ self.HOSTER_NAME = "".join([str.capitalize() for str in self.HOSTER_DOMAIN.split('.')])
+
+ 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/plugins/crypter/XupPl.py b/pyload/plugins/crypter/XupPl.py
new file mode 100644
index 000000000..bf285f96c
--- /dev/null
+++ b/pyload/plugins/crypter/XupPl.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.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_package", "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/plugins/crypter/YoutubeBatch.py b/pyload/plugins/crypter/YoutubeBatch.py
new file mode 100644
index 000000000..d25f991e8
--- /dev/null
+++ b/pyload/plugins/crypter/YoutubeBatch.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.utils import safe_join
+
+
+class YoutubeBatch(Crypter):
+ __name__ = "YoutubeBatch"
+ __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_package", "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 = urljoin("https://www.googleapis.com/youtube/v3/", ref)
+ page = self.load(url, get=req)
+ return json_loads(page)
+
+
+ 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 = safe_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/pyload/plugins/crypter/__init__.py b/pyload/plugins/crypter/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugins/crypter/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugins/hook/AlldebridCom.py b/pyload/plugins/hook/AlldebridCom.py
new file mode 100644
index 000000000..cf79af917
--- /dev/null
+++ b/pyload/plugins/hook/AlldebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class AlldebridCom(MultiHoster):
+ __name__ = "AlldebridCom"
+ __type__ = "hook"
+ __version__ = "0.13"
+
+ __config__ = [("https", "bool", "Enable HTTPS", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Alldebrid.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip()
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hook/BypassCaptcha.py b/pyload/plugins/hook/BypassCaptcha.py
new file mode 100644
index 000000000..b6e237b37
--- /dev/null
+++ b/pyload/plugins/hook/BypassCaptcha.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+
+from pycurl import FORM_FILE, LOW_SPEED_TIME
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.internal.Addon import Hook
+
+
+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.05"
+
+ __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"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ 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(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': (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)
+
+
+ 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/plugins/hook/Captcha9kw.py b/pyload/plugins/hook/Captcha9kw.py
new file mode 100644
index 000000000..ed1b27646
--- /dev/null
+++ b/pyload/plugins/hook/Captcha9kw.py
@@ -0,0 +1,258 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from base64 import b64encode
+from time import sleep
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+
+from pyload.plugins.internal.Addon import Hook
+
+
+class Captcha9kw(Hook):
+ __name__ = "Captcha9kw"
+ __type__ = "hook"
+ __version__ = "0.26"
+
+ __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"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def coreReady(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
+
+
+ def _processCaptcha(self, task):
+ try:
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+
+ except IOError, e:
+ self.logError(e)
+ return
+
+ data = b64encode(data)
+ mouse = 1 if task.isPositional() else 0
+ 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' : mouse,
+ 'file-upload-01': data,
+ 'action' : "usercaptchaupload"}
+
+ for _i in xrange(5):
+ try:
+ res = getURL(self.API_URL, post=post_data)
+ except BadHeader, e:
+ 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":
+ 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
+
+ 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
+
+ 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/plugins/hook/CaptchaBrotherhood.py b/pyload/plugins/hook/CaptchaBrotherhood.py
new file mode 100644
index 000000000..1f3aebd85
--- /dev/null
+++ b/pyload/plugins/hook/CaptchaBrotherhood.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import StringIO
+import pycurl
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from time import sleep
+from urllib import urlencode
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.internal.Addon import Hook
+
+
+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.06"
+
+ __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/"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ 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,
+ 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):
+ sleep(5)
+ res = self.get_api("askCaptchaResult", ticket)
+ if res.startswith("OK-answered"):
+ return ticket, res[12:]
+
+ raise CaptchaBrotherhoodException("No solution received in time")
+
+
+ def get_api(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.get_api("complainCaptcha", task.data['ticket'])
+
+
+ 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/plugins/hook/DeathByCaptcha.py b/pyload/plugins/hook/DeathByCaptcha.py
new file mode 100644
index 000000000..dc737020c
--- /dev/null
+++ b/pyload/plugins/hook/DeathByCaptcha.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from base64 import b64encode
+from pycurl import FORM_FILE, HTTPHEADER
+from time import sleep
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugins.internal.Addon import Hook
+
+
+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.04"
+
+ __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/"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def call_api(self, api="captcha", post=False, multipart=False):
+ req = getRequest()
+ req.c.setopt(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.call_api("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.call_api("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):
+ #workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig("passkey")):
+ multipart = True
+ data = (FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = "base64:" + b64encode(data)
+
+ res = self.call_api("captcha", {"captchafile": data}, multipart)
+
+ if "captcha" not in res:
+ raise DeathByCaptchaException(res)
+ ticket = res['captcha']
+
+ for _i in xrange(24):
+ sleep(5)
+ res = self.call_api("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.call_api("captcha/%d/report" % task.data['ticket'], True)
+
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+
+ except Exception, e:
+ self.logError(e)
+
+
+ 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/plugins/hook/DebridItaliaCom.py b/pyload/plugins/hook/DebridItaliaCom.py
new file mode 100644
index 000000000..01e085ad2
--- /dev/null
+++ b/pyload/plugins/hook/DebridItaliaCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class DebridItaliaCom(MultiHoster):
+ __name__ = "DebridItaliaCom"
+ __type__ = "hook"
+ __version__ = "0.08"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Debriditalia.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def getHoster(self):
+ html = getURL("http://www.debriditalia.com/status.php")
+ return re.findall(r'title="(.+?)"> \1</td><td><img src="/images/(?:attivo|testing)', html)
diff --git a/pyload/plugins/hook/EasybytezCom.py b/pyload/plugins/hook/EasybytezCom.py
new file mode 100644
index 000000000..9d92b96f7
--- /dev/null
+++ b/pyload/plugins/hook/EasybytezCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class EasybytezCom(MultiHoster):
+ __name__ = "EasybytezCom"
+ __type__ = "hook"
+ __version__ = "0.03"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """EasyBytez.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def getHoster(self):
+ self.account = self.core.accountManager.getAccountPlugin(self.__name__)
+ user = self.account.selectAccount()[0]
+
+ try:
+ req = self.account.getAccountRequest(user)
+ page = req.load("http://www.easybytez.com")
+
+ hosters = re.search(r'</textarea>\s*Supported sites:(.*)', page).group(1).split(',')
+
+ except Exception, e:
+ self.logWarning(_("Unable to load supported hoster list, using last known"))
+ self.logDebug(e)
+
+ hosters = ["bitshare.com", "crocko.com", "ddlstorage.com", "depositfiles.com", "extabit.com", "hotfile.com",
+ "mediafire.com", "netload.in", "rapidgator.net", "rapidshare.com", "uploading.com", "uload.to",
+ "uploaded.to"]
+ finally:
+ return hosters
diff --git a/pyload/plugins/hook/ExpertDecoders.py b/pyload/plugins/hook/ExpertDecoders.py
new file mode 100644
index 000000000..dbd03c30c
--- /dev/null
+++ b/pyload/plugins/hook/ExpertDecoders.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from base64 import b64encode
+from pycurl import LOW_SPEED_TIME
+from uuid import uuid4
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.internal.Addon import Hook
+
+
+class ExpertDecoders(Hook):
+ __name__ = "ExpertDecoders"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __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"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ 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
+
+
+ def processCaptcha(self, task):
+ task.data['ticket'] = ticket = uuid4()
+ result = None
+
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+ data = b64encode(data)
+
+ req = getRequest()
+ #raise timeout threshold
+ req.c.setopt(LOW_SPEED_TIME, 80)
+
+ try:
+ result = req.load(self.API_URL, post={"action": "upload", "key": self.getConfig("passkey"),
+ "file": 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/pyload/plugins/hook/FastixRu.py b/pyload/plugins/hook/FastixRu.py
new file mode 100644
index 000000000..0ae853544
--- /dev/null
+++ b/pyload/plugins/hook/FastixRu.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class FastixRu(MultiHoster):
+ __name__ = "FastixRu"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Fastix.ru hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def getHoster(self):
+ page = getURL("http://fastix.ru/api_v2",
+ get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y",
+ 'sub' : "allowed_sources"})
+ host_list = json_loads(page)
+ host_list = host_list['allow']
+ return host_list
diff --git a/pyload/plugins/hook/FreeWayMe.py b/pyload/plugins/hook/FreeWayMe.py
new file mode 100644
index 000000000..5d955e156
--- /dev/null
+++ b/pyload/plugins/hook/FreeWayMe.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class FreeWayMe(MultiHoster):
+ __name__ = "FreeWayMe"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """FreeWay.me hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def getHoster(self):
+ hostis = getURL("https://www.free-way.me/ajax/jd.php", get={'id': 3}).replace("\"", "").strip()
+ self.logDebug("Hosters", hostis)
+ return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/pyload/plugins/hook/ImageTyperz.py b/pyload/plugins/hook/ImageTyperz.py
new file mode 100644
index 000000000..c0b046787
--- /dev/null
+++ b/pyload/plugins/hook/ImageTyperz.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from base64 import b64encode
+from pycurl import FORM_FILE, LOW_SPEED_TIME
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.internal.Addon import Hook
+
+
+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.05"
+
+ __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"
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ 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(LOW_SPEED_TIME, 80)
+
+ try:
+ #workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig("passkey")):
+ multipart = True
+ data = (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)
+
+
+ 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/plugins/hook/LinkdecrypterCom.py b/pyload/plugins/hook/LinkdecrypterCom.py
new file mode 100644
index 000000000..15697e0a2
--- /dev/null
+++ b/pyload/plugins/hook/LinkdecrypterCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Addon import Hook
+from pyload.utils import remove_chars
+
+
+class LinkdecrypterCom(Hook):
+ __name__ = "LinkdecrypterCom"
+ __type__ = "hook"
+ __version__ = "0.21"
+
+ __description__ = """Linkdecrypter.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def coreReady(self):
+ try:
+ self.loadPatterns()
+ except Exception, e:
+ self.logError(e)
+
+
+ def loadPatterns(self):
+ html = getURL("http://linkdecrypter.com/")
+
+ m = re.search(r'<title>', html)
+ if m is None:
+ self.logError(_("Linkdecrypter site is down"))
+ return
+
+ m = re.search(r'<b>Supported\(\d+\)</b>: <i>([^+<]*)', html)
+ if m is None:
+ self.logError(_("Crypter list not found"))
+ return
+
+ builtin = [name.lower() for name in self.core.pluginManager.crypterPlugins.keys()]
+ builtin.append("downloadserienjunkiesorg")
+
+ crypter_pattern = re.compile("(\w[\w.-]+)")
+ online = []
+ for crypter in m.group(1).split(', '):
+ m = re.match(crypter_pattern, crypter)
+ if m and remove_chars(m.group(1), "-.") not in builtin:
+ online.append(m.group(1).replace(".", "\\."))
+
+ if not online:
+ self.logError(_("Crypter list is empty"))
+ return
+
+ regexp = r'https?://([^.]+\.)*?(%s)/.*' % '|'.join(online)
+
+ dict = self.core.pluginManager.crypterPlugins[self.__name__]
+ dict['pattern'] = regexp
+ dict['re'] = re.compile(regexp)
+
+ self.logDebug("Loaded pattern: %s" % regexp)
diff --git a/pyload/plugins/hook/LinksnappyCom.py b/pyload/plugins/hook/LinksnappyCom.py
new file mode 100644
index 000000000..381eb6a2a
--- /dev/null
+++ b/pyload/plugins/hook/LinksnappyCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class LinksnappyCom(MultiHoster):
+ __name__ = "LinksnappyCom"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Linksnappy.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHoster(self):
+ json_data = 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/plugins/hook/MegaDebridEu.py b/pyload/plugins/hook/MegaDebridEu.py
new file mode 100644
index 000000000..5a52dbf41
--- /dev/null
+++ b/pyload/plugins/hook/MegaDebridEu.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class MegaDebridEu(MultiHoster):
+ __name__ = "MegaDebridEu"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("unloadFailing", "bool", "Revert to standard download if download fails", False)]
+
+ __description__ = """mega-debrid.eu hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ def getHoster(self):
+ reponse = 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/pyload/plugins/hook/MultishareCz.py b/pyload/plugins/hook/MultishareCz.py
new file mode 100644
index 000000000..f1a12acb8
--- /dev/null
+++ b/pyload/plugins/hook/MultishareCz.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class MultishareCz(MultiHoster):
+ __name__ = "MultishareCz"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "uloz.to")]
+
+ __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 getHoster(self):
+ page = getURL("http://www.multishare.cz/monitoring/")
+ return re.findall(self.HOSTER_PATTERN, page)
diff --git a/pyload/plugins/hook/MyfastfileCom.py b/pyload/plugins/hook/MyfastfileCom.py
new file mode 100644
index 000000000..07988d2b9
--- /dev/null
+++ b/pyload/plugins/hook/MyfastfileCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(MultiHoster):
+ __name__ = "MyfastfileCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Myfastfile.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+
+ def getHoster(self):
+ json_data = 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/plugins/hook/OverLoadMe.py b/pyload/plugins/hook/OverLoadMe.py
new file mode 100644
index 000000000..83ce3e842
--- /dev/null
+++ b/pyload/plugins/hook/OverLoadMe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class OverLoadMe(MultiHoster):
+ __name__ = "OverLoadMe"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("https", "bool", "Enable HTTPS", True),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 12)]
+
+ __description__ = """Over-Load.me hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("marley", "marley@over-load.me")]
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://api.over-load.me/hoster.php",
+ get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip()
+ self.logDebug("Hosterlist", page)
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hook/PremiumTo.py b/pyload/plugins/hook/PremiumTo.py
new file mode 100644
index 000000000..e3c9823f6
--- /dev/null
+++ b/pyload/plugins/hook/PremiumTo.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class PremiumTo(MultiHoster):
+ __name__ = "PremiumTo"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for downloads from supported hosters:", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Premium.to hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+
+ def getHoster(self):
+ page = getURL("http://premium.to/api/hosters.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ return [x.strip() for x in page.replace("\"", "").split(";")]
+
+
+ def coreReady(self):
+ self.account = self.core.accountManager.getAccountPlugin("PremiumTo")
+
+ user = self.account.selectAccount()[0]
+
+ if not user:
+ self.logError(_("Please add your premium.to account first and restart pyLoad"))
+ return
+
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hook/PremiumizeMe.py b/pyload/plugins/hook/PremiumizeMe.py
new file mode 100644
index 000000000..5824115b6
--- /dev/null
+++ b/pyload/plugins/hook/PremiumizeMe.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class PremiumizeMe(MultiHoster):
+ __name__ = "PremiumizeMe"
+ __type__ = "hook"
+ __version__ = "0.12"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Premiumize.me hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def getHoster(self):
+ # If no accounts are available there will be no hosters available
+ if not self.account or not self.account.canUse():
+ return []
+
+ # 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 = 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']
+
+
+ def coreReady(self):
+ # Get account plugin and check if there is a valid account available
+ self.account = self.core.accountManager.getAccountPlugin("PremiumizeMe")
+ if not self.account.canUse():
+ self.account = None
+ self.logError(_("Please add a valid premiumize.me account first and restart pyLoad"))
+ return
+
+ # Run the overwriten core ready which actually enables the multihoster hook
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hook/RPNetBiz.py b/pyload/plugins/hook/RPNetBiz.py
new file mode 100644
index 000000000..ed8835ce8
--- /dev/null
+++ b/pyload/plugins/hook/RPNetBiz.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RPNetBiz(MultiHoster):
+ __name__ = "RPNetBiz"
+ __type__ = "hook"
+ __version__ = "0.10"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """RPNet.biz hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Dman", "dmanugm@gmail.com")]
+
+
+ def getHoster(self):
+ # No hosts supported if no account
+ if not self.account or not self.account.canUse():
+ return []
+
+ # Get account data
+ (user, data) = self.account.selectAccount()
+
+ res = 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']
+
+
+ def coreReady(self):
+ # Get account plugin and check if there is a valid account available
+ self.account = self.core.accountManager.getAccountPlugin("RPNetBiz")
+ if not self.account.canUse():
+ self.account = None
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
+ return
+
+ # Run the overwriten core ready which actually enables the multihoster hook
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hook/RealdebridCom.py b/pyload/plugins/hook/RealdebridCom.py
new file mode 100644
index 000000000..a6f79a97d
--- /dev/null
+++ b/pyload/plugins/hook/RealdebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RealdebridCom(MultiHoster):
+ __name__ = "RealdebridCom"
+ __type__ = "hook"
+ __version__ = "0.43"
+
+ __config__ = [("https", "bool", "Enable HTTPS", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Real-Debrid.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hook/RehostTo.py b/pyload/plugins/hook/RehostTo.py
new file mode 100644
index 000000000..6c334bf06
--- /dev/null
+++ b/pyload/plugins/hook/RehostTo.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RehostTo(MultiHoster):
+ __name__ = "RehostTo"
+ __type__ = "hook"
+ __version__ = "0.43"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Rehost.to hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def getHoster(self):
+ page = getURL("http://rehost.to/api.php",
+ get={'cmd': "get_supported_och_dl", 'long_ses': self.long_ses})
+ return [x.strip() for x in page.replace("\"", "").split(",")]
+
+
+ def coreReady(self):
+ self.account = self.core.accountManager.getAccountPlugin("RehostTo")
+
+ user = self.account.selectAccount()[0]
+
+ if not user:
+ self.logError(_("Please add your rehost.to account first and restart pyLoad"))
+ return
+
+ data = self.account.getAccountInfo(user)
+ self.ses = data['ses']
+ self.long_ses = data['long_ses']
+
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hook/SimplyPremiumCom.py b/pyload/plugins/hook/SimplyPremiumCom.py
new file mode 100644
index 000000000..8c32cbf7a
--- /dev/null
+++ b/pyload/plugins/hook/SimplyPremiumCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class SimplyPremiumCom(MultiHoster):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", "False"),
+ ("interval", "int", "Reload interval in hours (0 to disable)", "24")]
+
+ __description__ = """Simply-Premium.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def getHoster(self):
+ json_data = 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/plugins/hook/SimplydebridCom.py b/pyload/plugins/hook/SimplydebridCom.py
new file mode 100644
index 000000000..89d8cb752
--- /dev/null
+++ b/pyload/plugins/hook/SimplydebridCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class SimplydebridCom(MultiHoster):
+ __name__ = "SimplydebridCom"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Simply-Debrid.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def getHoster(self):
+ page = getURL("http://simply-debrid.com/api.php", get={'list': 1})
+ return [x.strip() for x in page.rstrip(';').replace("\"", "").split(";")]
diff --git a/pyload/plugins/hook/UnrestrictLi.py b/pyload/plugins/hook/UnrestrictLi.py
new file mode 100644
index 000000000..255cee43d
--- /dev/null
+++ b/pyload/plugins/hook/UnrestrictLi.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class UnrestrictLi(MultiHoster):
+ __name__ = "UnrestrictLi"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24),
+ ("history", "bool", "Delete History", False)]
+
+ __description__ = """Unrestrict.li hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHoster(self):
+ json_data = getURL("http://unrestrict.li/api/jdownloader/hosts.php", get={'format': "json"})
+ json_data = json_loads(json_data)
+
+ host_list = [element['host'] for element in json_data['result']]
+
+ return host_list
diff --git a/pyload/plugins/hook/XFileSharingPro.py b/pyload/plugins/hook/XFileSharingPro.py
new file mode 100644
index 000000000..fc8c37127
--- /dev/null
+++ b/pyload/plugins/hook/XFileSharingPro.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Addon import Hook
+
+
+class XFileSharingPro(Hook):
+ __name__ = "XFileSharingPro"
+ __type__ = "hook"
+ __version__ = "0.26"
+
+ __config__ = [("activated", "bool", "Activated", True),
+ ("use_hoster_list", "bool", "Load listed hosters only", True),
+ ("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\.)?([\w.^_]+(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)',
+ r'https?://(?:[^/]+\.)?(%s)/(?:embed-)?\w+'),
+ 'crypter': (r'https?://(?:www\.)?([\w.^_]+(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+',
+ r'https?://(?:[^/]+\.)?(%s)/(?:user|folder)s?/\w+')}
+
+ HOSTER_LIST = [#WORKING HOSTERS:
+ "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "filevice.com", "hostingbulk.com",
+ "linestorage.com", "ravishare.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", "zalaa.com", "zomgupload.com",
+ #NOT WORKING:
+ "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"]
+ CRYPTER_LIST = []
+
+
+ # def pluginConfigChanged(self.__name__, plugin, name, value):
+ # self.loadPattern()
+
+
+ #@TODO: Remove in 0.4.10
+ def initPeriodical(self):
+ pass
+
+
+ def coreReady(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:
+ s = self.getConfig('%s_list' % type).replace('\\', '').replace('|', ',').replace(';', ',').lower()
+ plugin_list = set([x.strip() for x in s.split(',')])
+
+ if use_builtin_list:
+ plugin_list |= set([x.lower() for x in getattr(self, "%s_LIST" % type.upper())])
+
+ plugin_list -= set(('', u''))
+
+ if not plugin_list:
+ self.logInfo(_("No %s to handle") % type)
+ self._unload(type)
+ return
+
+ match_list = '|'.join(sorted(plugin_list))
+
+ len_match_list = len(plugin_list)
+ self.logInfo(_("Handling %d %s%s: %s") % (len_match_list, type, "" if len_match_list is 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 unload(self):
+ for type in ("hoster", "crypter"):
+ self._unload(type, "XFileSharingPro")
diff --git a/pyload/plugins/hook/ZeveraCom.py b/pyload/plugins/hook/ZeveraCom.py
new file mode 100644
index 000000000..21fdf6c92
--- /dev/null
+++ b/pyload/plugins/hook/ZeveraCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class ZeveraCom(MultiHoster):
+ __name__ = "ZeveraCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Real-Debrid.com hook plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def getHoster(self):
+ page = getURL("http://www.zevera.com/jDownloader.ashx", get={'cmd': "gethosters"})
+ return [x.strip() for x in page.replace("\"", "").split(",")]
diff --git a/pyload/plugins/hook/__init__.py b/pyload/plugins/hook/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/hook/__init__.py
diff --git a/pyload/plugins/hoster/AlldebridCom.py b/pyload/plugins/hoster/AlldebridCom.py
new file mode 100644
index 000000000..d575c305e
--- /dev/null
+++ b/pyload/plugins/hoster/AlldebridCom.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class AlldebridCom(Hoster):
+ __name__ = "AlldebridCom"
+ __type__ = "hoster"
+ __version__ = "0.34"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?alldebrid\..*'
+
+ __description__ = """Alldebrid.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "AllDebrid")
+ self.fail(_("No AllDebrid account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ password = self.getPassword().splitlines()[0] or ""
+
+ 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'])
+ new_url = data['link']
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({'error': "<title>An error occured while processing your request</title>",
+ 'empty': re.compile(r"^$")})
+
+ if check == "error":
+ self.retry(wait_time=60, reason=_("An error occured while generating link"))
+ elif check == "empty":
+ self.retry(wait_time=60, reason=_("Downloaded File was empty"))
diff --git a/pyload/plugins/hoster/BayfilesCom.py b/pyload/plugins/hoster/BayfilesCom.py
new file mode 100644
index 000000000..f3fa87d89
--- /dev/null
+++ b/pyload/plugins/hoster/BayfilesCom.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BayfilesCom(SimpleHoster):
+ __name__ = "BayfilesCom"
+ __type__ = "hoster"
+ __version__ = "0.08"
+
+ __pattern__ = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)'
+
+ __description__ = """Bayfiles.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'<p title="(?P<N>[^"]+)">[^<]*<strong>(?P<S>[\d .,]+)(?P<U>[\w^_]+)</strong></p>'
+ OFFLINE_PATTERN = r'(<p>The requested file could not be found.</p>|<title>404 Not Found</title>)'
+
+ WAIT_PATTERN = r'>Your IP [\d.]* has recently downloaded a file\. Upgrade to premium or wait (\d+) minutes\.<'
+ VARS_PATTERN = r'var vfid = (\d+);\s*var delay = (\d+);'
+ FREE_LINK_PATTERN = r'javascript:window\.location\.href = \'(.+?)\';'
+ PREMIUM_LINK_PATTERN = r'(?:<a class="highlighted-btn" href="|(?=http://s\d+\.baycdn\.com/dl/))(.*?)"'
+
+
+ def handleFree(self):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.retry(wait_time=int(m.group(1)) * 60)
+
+ # Get download token
+ m = re.search(self.VARS_PATTERN, self.html)
+ if m is None:
+ self.error(_("VARS_PATTERN not found"))
+ vfid, delay = m.groups()
+
+ res = json_loads(self.load('http://bayfiles.com/ajax_download',
+ get={"_": time() * 1000,
+ "action": "startTimer",
+ "vfid": vfid}, decode=True))
+
+ if not "token" in res or not res['token']:
+ self.fail(_("No token"))
+
+ self.wait(int(delay))
+
+ self.html = self.load('http://bayfiles.com/ajax_download', get={
+ "token": res['token'],
+ "action": "getLink",
+ "vfid": vfid})
+
+ # Get final link and download
+ m = re.search(self.FREE_LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free link"))
+ self.startDownload(m.group(1))
+
+
+ def handlePremium(self):
+ m = re.search(self.PREMIUM_LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Premium link"))
+ self.startDownload(m.group(1))
+
+
+ def startDownload(self, url):
+ self.logDebug("%s URL: %s" % ("Premium" if self.premium else "Free", url))
+ self.download(url)
+ # check download
+ check = self.checkDownload({
+ "waitforfreeslots": re.compile(r"<title>BayFiles</title>"),
+ "notfound": re.compile(r"<title>404 Not Found</title>")
+ })
+ if check == "waitforfreeslots":
+ self.retry(30, 5 * 60, "Wait for free slot")
+ elif check == "notfound":
+ self.retry(30, 5 * 60, "404 Not found")
+
+
+getInfo = create_getInfo(BayfilesCom)
diff --git a/pyload/plugins/hoster/BezvadataCz.py b/pyload/plugins/hoster/BezvadataCz.py
new file mode 100644
index 000000000..431fbfbeb
--- /dev/null
+++ b/pyload/plugins/hoster/BezvadataCz.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BezvadataCz(SimpleHoster):
+ __name__ = "BezvadataCz"
+ __type__ = "hoster"
+ __version__ = "0.25"
+
+ __pattern__ = r'http://(?:www\.)?bezvadata\.cz/stahnout/.*'
+
+ __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):
+ #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.download(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/pyload/plugins/hoster/BillionuploadsCom.py b/pyload/plugins/hoster/BillionuploadsCom.py
new file mode 100644
index 000000000..d5d8c6c0f
--- /dev/null
+++ b/pyload/plugins/hoster/BillionuploadsCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "billionuploads.com"
+
+ 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/pyload/plugins/hoster/BitshareCom.py b/pyload/plugins/hoster/BitshareCom.py
new file mode 100644
index 000000000..9fd98643c
--- /dev/null
+++ b/pyload/plugins/hoster/BitshareCom.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BitshareCom(SimpleHoster):
+ __name__ = "BitshareCom"
+ __type__ = "hoster"
+ __version__ = "0.51"
+
+ __pattern__ = r'http://(?:www\.)?bitshare\.com/(files/(?P<id1>\w+)(/(?P<name>.*?)\.html)?|\?f=(?P<id2>\w+))'
+
+ __description__ = """Bitshare.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Paul King", None),
+ ("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>'
+ OFFLINE_PATTERN = r'(>We are sorry, but the requested file was not found in our database|>Error - File not available<|The file was deleted either by the uploader, inactivity or due to copyright claim)'
+
+ COOKIES = [("bitshare.com", "language_selection", "EN")]
+
+ AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
+ TRAFFIC_USED_UP = r'Your Traffic is used up for today. Upgrade to premium to continue!'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if self.premium:
+ self.account.relogin(self.user)
+
+ self.pyfile = pyfile
+
+ # 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
+ url = self.getDownloadUrl()
+ self.download(url)
+
+ check = self.checkDownload({"404": ">404 Not Found<", "Error": ">Error occured<"})
+ if check == "404":
+ self.retry(3, 60, 'Error 404')
+ elif check == "error":
+ 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, cookies=True, 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):
+ challenge, response = 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/pyload/plugins/hoster/BoltsharingCom.py b/pyload/plugins/hoster/BoltsharingCom.py
new file mode 100644
index 000000000..4726fc769
--- /dev/null
+++ b/pyload/plugins/hoster/BoltsharingCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Boltsharing.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(BoltsharingCom)
diff --git a/pyload/plugins/hoster/CatShareNet.py b/pyload/plugins/hoster/CatShareNet.py
new file mode 100644
index 000000000..146b639de
--- /dev/null
+++ b/pyload/plugins/hoster/CatShareNet.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class CatShareNet(SimpleHoster):
+ __name__ = "CatShareNet"
+ __type__ = "hoster"
+ __version__ = "0.08"
+
+ __pattern__ = r'http://(?:www\.)?catshare\.net/\w{16}'
+
+ __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 = ur'Podany plik został usunięty\s*</div>'
+
+ IP_BLOCKED_PATTERN = ur'>Nasz serwis wykrył ÅŒe Twój adres IP nie pochodzi z Polski.<'
+ SECONDS_PATTERN = 'var\scount\s=\s(\d+);'
+ LINK_PATTERN = r'<form action="(.+?)" method="GET">'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def getFileInfo(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).getFileInfo()
+
+
+ def handleFree(self):
+ m = re.search(self.SECONDS_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1))
+ self.wait(wait_time, True)
+
+ recaptcha = ReCaptcha(self)
+
+ challenge, response = recaptcha.challenge()
+ self.html = self.load(self.pyfile.url,
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.invalidCaptcha()
+ self.retry(reason=_("Wrong captcha entered"))
+
+ dl_link = m.group(1)
+ self.download(dl_link, disposition=True)
+
+
+getInfo = create_getInfo(CatShareNet)
diff --git a/pyload/plugins/hoster/CloudzerNet.py b/pyload/plugins/hoster/CloudzerNet.py
new file mode 100644
index 000000000..ce16f6fce
--- /dev/null
+++ b/pyload/plugins/hoster/CloudzerNet.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __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/pyload/plugins/hoster/CramitIn.py b/pyload/plugins/hoster/CramitIn.py
new file mode 100644
index 000000000..2d2c0cbbc
--- /dev/null
+++ b/pyload/plugins/hoster/CramitIn.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "cramit.in"
+
+ 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/pyload/plugins/hoster/CrockoCom.py b/pyload/plugins/hoster/CrockoCom.py
new file mode 100644
index 000000000..ea87cce6d
--- /dev/null
+++ b/pyload/plugins/hoster/CrockoCom.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class CrockoCom(SimpleHoster):
+ __name__ = "CrockoCom"
+ __type__ = "hoster"
+ __version__ = "0.17"
+
+ __pattern__ = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+'
+
+ __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 = re.compile(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):
+ 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_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge()
+ self.download(action, post=inputs)
+
+ check = self.checkDownload({
+ "captcha_err": recaptcha.KEY_AJAX_PATTERN
+ })
+
+ if check == "captcha_err":
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("No valid captcha solution received"))
+
+
+getInfo = create_getInfo(CrockoCom)
diff --git a/pyload/plugins/hoster/CyberlockerCh.py b/pyload/plugins/hoster/CyberlockerCh.py
new file mode 100644
index 000000000..48109101d
--- /dev/null
+++ b/pyload/plugins/hoster/CyberlockerCh.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class CyberlockerCh(DeadHoster):
+ __name__ = "CyberlockerCh"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?cyberlocker\.ch/\w+'
+
+ __description__ = """Cyberlocker.ch hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(CyberlockerCh)
diff --git a/pyload/plugins/hoster/CzshareCom.py b/pyload/plugins/hoster/CzshareCom.py
new file mode 100644
index 000000000..92a38f49d
--- /dev/null
+++ b/pyload/plugins/hoster/CzshareCom.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://czshare.com/5278880/random.bin
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from pyload.utils import parseFileSize
+
+
+class CzshareCom(SimpleHoster):
+ __name__ = "CzshareCom"
+ __type__ = "hoster"
+ __version__ = "0.95"
+
+ __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).*'
+
+ __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/')]
+
+ FORCE_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, cookies=True, 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):
+ # 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)
+ self.checkDownloadedFile()
+
+
+ def handleFree(self):
+ # 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, cookies=True, 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))
+ self.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, cookies=True, 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"))
+
+ url = "http://%s/download.php?%s" % (m.group(1), m.group(2))
+
+ self.wait()
+ self.download(url)
+ self.checkDownloadedFile()
+
+
+ def checkDownloadedFile(self):
+ # 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_err": "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
+ })
+
+ if check == "temp_offline":
+ self.fail(_("File not available - try later"))
+ if check == "credit":
+ self.resetAccount()
+ elif check == "multi_dl":
+ self.longWait(5 * 60, 12)
+ elif check == "captcha_err":
+ self.invalidCaptcha()
+ self.retry()
+
+
+getInfo = create_getInfo(CzshareCom)
diff --git a/pyload/plugins/hoster/DailymotionCom.py b/pyload/plugins/hoster/DailymotionCom.py
new file mode 100644
index 000000000..201897719
--- /dev/null
+++ b/pyload/plugins/hoster/DailymotionCom.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.datatype.PyFile import statusMap
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.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")
+ page = getURL(apiurl % id, get=request)
+ info = json_loads(page)
+
+ 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 reversed([item for item in enumerate(streams)]):
+ 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/plugins/hoster/DataHu.py b/pyload/plugins/hoster/DataHu.py
new file mode 100644
index 000000000..6210f18f4
--- /dev/null
+++ b/pyload/plugins/hoster/DataHu.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://data.hu/get/6381232/random.bin
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DataHu(SimpleHoster):
+ __name__ = "DataHu"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?data\.hu/get/\w+'
+
+ __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_PATTERN = r'<div class="download_box_button"><a href="([^"]+)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ self.download(m.group(1), disposition=True)
+
+
+getInfo = create_getInfo(DataHu)
diff --git a/pyload/plugins/hoster/DataportCz.py b/pyload/plugins/hoster/DataportCz.py
new file mode 100644
index 000000000..65ae38ff1
--- /dev/null
+++ b/pyload/plugins/hoster/DataportCz.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DataportCz(SimpleHoster):
+ __name__ = "DataportCz"
+ __type__ = "hoster"
+ __version__ = "0.40"
+
+ __pattern__ = r'http://(?:www\.)?dataport\.cz/file/(.*)'
+
+ __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):
+ 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.html = 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(self.pyfile.url, decode=True)
+ continue
+ else:
+ break
+
+
+getInfo = create_getInfo(DataportCz)
diff --git a/pyload/plugins/hoster/DateiTo.py b/pyload/plugins/hoster/DateiTo.py
new file mode 100644
index 000000000..bcd99ab0c
--- /dev/null
+++ b/pyload/plugins/hoster/DateiTo.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DateiTo(SimpleHoster):
+ __name__ = "DateiTo"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
+
+ __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):
+ 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_challenge_field'], data['recaptcha_response_field'] = recaptcha.challenge()
+ else:
+ self.fail(_("Too bad..."))
+
+ self.download(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/pyload/plugins/hoster/DdlstorageCom.py b/pyload/plugins/hoster/DdlstorageCom.py
new file mode 100644
index 000000000..4a46c4541
--- /dev/null
+++ b/pyload/plugins/hoster/DdlstorageCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class DdlstorageCom(DeadHoster):
+ __name__ = "DdlstorageCom"
+ __type__ = "hoster"
+ __version__ = "1.02"
+
+ __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/\w+'
+
+ __description__ = """DDLStorage.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(DdlstorageCom)
diff --git a/pyload/plugins/hoster/DebridItaliaCom.py b/pyload/plugins/hoster/DebridItaliaCom.py
new file mode 100644
index 000000000..a629b06a9
--- /dev/null
+++ b/pyload/plugins/hoster/DebridItaliaCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.internal.SimpleHoster import replace_patterns
+
+
+class DebridItaliaCom(Hoster):
+ __name__ = "DebridItaliaCom"
+ __type__ = "hoster"
+ __version__ = "0.07"
+
+ __pattern__ = r'http://s\d+\.debriditalia\.com/dl/\d+'
+
+ __description__ = """Debriditalia.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(r'(/dl/\d+)$', '\1/')]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, cls.URL_REPLACEMENTS)
+
+ if re.match(self.__pattern__, pyfile.url):
+ link = pyfile.url
+
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "DebridItalia")
+ self.fail(_("No DebridItalia account provided"))
+
+ else:
+ html = self.load("http://www.debriditalia.com/api.php", get={'generate': "", 'link': pyfile.url})
+
+ if "ERROR" in html:
+ self.fail(re.search(r'ERROR:(.*)', html).strip())
+
+ link = html.strip()
+
+ self.download(link, disposition=True)
+
+ check = self.checkDownload({'empty': re.compile(r'^$')})
+
+ if check == "empty":
+ self.retry(5, 2 * 60, "Empty file downloaded")
diff --git a/pyload/plugins/hoster/DepositfilesCom.py b/pyload/plugins/hoster/DepositfilesCom.py
new file mode 100644
index 000000000..e78e0bb27
--- /dev/null
+++ b/pyload/plugins/hoster/DepositfilesCom.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DepositfilesCom(SimpleHoster):
+ __name__ = "DepositfilesCom"
+ __type__ = "hoster"
+ __version__ = "0.51"
+
+ __pattern__ = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
+
+ __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")]
+
+ FREE_LINK_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
+ PREMIUM_LINK_PATTERN = r'class="repeat"><a href="(.+?)"'
+ PREMIUM_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
+
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, post={"gateway_result": "1"}, cookies=True)
+
+ if re.search(r'File is checked, please try again in a minute.', self.html) is not None:
+ self.logInfo(_("The file is being checked. Waiting 1 minute"))
+ self.retry(wait_time=60)
+
+ wait = re.search(r'html_download_api-limit_interval\">(\d+)</span>', self.html)
+ if wait:
+ wait_time = int(wait.group(1))
+ self.logInfo(_("Traffic used up. Waiting %d seconds") % wait_time)
+ self.wait(wait_time, True)
+ self.retry()
+
+ wait = re.search(r'>Try in (\d+) minutes or use GOLD account', self.html)
+ if wait:
+ wait_time = int(wait.group(1))
+ self.logInfo(_("All free slots occupied. Waiting %d minutes") % wait_time)
+ self.setWait(wait_time * 60, False)
+
+ wait = re.search(r'Please wait (\d+) sec', self.html)
+ if wait:
+ self.setWait(int(wait.group(1)))
+
+ 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.wait()
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+ if captcha_key is None:
+ self.error(_("ReCaptcha key not found"))
+
+ for _i in xrange(5):
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
+ if 'response' in params:
+ self.invalidCaptcha()
+ params['challenge'], params['response'] = recaptcha.challenge(captcha_key)
+ self.logDebug(params)
+ continue
+
+ m = re.search(self.FREE_LINK_PATTERN, self.html)
+ if m:
+ if 'response' in params:
+ self.correctCaptcha()
+ link = unquote(m.group(1))
+ self.logDebug("LINK: %s" % link)
+ break
+ else:
+ self.error(_("Download link"))
+ else:
+ self.fail(_("No valid captcha response received"))
+
+ try:
+ self.download(link, disposition=True)
+ except Exception:
+ self.retry(wait_time=60)
+
+
+ def handlePremium(self):
+ 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.PREMIUM_LINK_PATTERN, self.html)
+ mirror = re.search(self.PREMIUM_MIRROR_PATTERN, self.html)
+ if link:
+ dlink = link.group(1)
+ elif mirror:
+ dlink = mirror.group(1)
+ else:
+ self.error(_("No direct download link or mirror found"))
+ self.download(dlink, disposition=True)
+
+
+getInfo = create_getInfo(DepositfilesCom)
diff --git a/pyload/plugins/hoster/DlFreeFr.py b/pyload/plugins/hoster/DlFreeFr.py
new file mode 100644
index 000000000..050fa0442
--- /dev/null
+++ b/pyload/plugins/hoster/DlFreeFr.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.network.Browser import Browser
+from pyload.network.CookieJar import CookieJar
+from pyload.plugins.internal.captcha import AdYouLike
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, 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.26"
+
+ __pattern__ = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)'
+
+ __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()
+ else:
+ # Direct access to requested file for users using free.fr as Internet Service Provider.
+ self.download(valid_url, disposition=True)
+ elif headers.get('code') == 404:
+ self.offline()
+ else:
+ self.fail(_("Invalid return code: ") + str(headers.get('code')))
+
+
+ def handleFree(self):
+ action, inputs = self.parseHtmlForm('action="getfile.pl"')
+
+ adyoulike = AdYouLike(self)
+ inputs.update(adyoulike.challenge())
+
+ 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"))
+ location = headers.get("location")
+ self.req.setCookieJar(cj)
+ self.download(location, disposition=True)
+ 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/pyload/plugins/hoster/DodanePl.py b/pyload/plugins/hoster/DodanePl.py
new file mode 100644
index 000000000..3f1ea3d6d
--- /dev/null
+++ b/pyload/plugins/hoster/DodanePl.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Dodane.pl hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("z00nx", "z00nx0@gmail.com")]
+
+
+getInfo = create_getInfo(DodanePl)
diff --git a/pyload/plugins/hoster/DuploadOrg.py b/pyload/plugins/hoster/DuploadOrg.py
new file mode 100644
index 000000000..dae4c847e
--- /dev/null
+++ b/pyload/plugins/hoster/DuploadOrg.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Dupload.grg hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(DuploadOrg)
diff --git a/pyload/plugins/hoster/EasybytezCom.py b/pyload/plugins/hoster/EasybytezCom.py
new file mode 100644
index 000000000..d02cb371b
--- /dev/null
+++ b/pyload/plugins/hoster/EasybytezCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "easybytez.com"
+
+ 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/pyload/plugins/hoster/EdiskCz.py b/pyload/plugins/hoster/EdiskCz.py
new file mode 100644
index 000000000..3b256a89a
--- /dev/null
+++ b/pyload/plugins/hoster/EdiskCz.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class EdiskCz(SimpleHoster):
+ __name__ = "EdiskCz"
+ __type__ = "hoster"
+ __version__ = "0.22"
+
+ __pattern__ = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.*'
+
+ __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_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_PATTERN, url):
+ self.fail(_("Unexpected server response"))
+
+ self.download(url)
+
+
+getInfo = create_getInfo(EdiskCz)
diff --git a/pyload/plugins/hoster/EgoFilesCom.py b/pyload/plugins/hoster/EgoFilesCom.py
new file mode 100644
index 000000000..20176751a
--- /dev/null
+++ b/pyload/plugins/hoster/EgoFilesCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class EgoFilesCom(DeadHoster):
+ __name__ = "EgoFilesCom"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'https?://(?:www\.)?egofiles\.com/\w+'
+
+ __description__ = """Egofiles.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(EgoFilesCom)
diff --git a/pyload/plugins/hoster/EnteruploadCom.py b/pyload/plugins/hoster/EnteruploadCom.py
new file mode 100644
index 000000000..c535d6f48
--- /dev/null
+++ b/pyload/plugins/hoster/EnteruploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class EnteruploadCom(DeadHoster):
+ __name__ = "EnteruploadCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?enterupload\.com/\w+'
+
+ __description__ = """EnterUpload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(EnteruploadCom)
diff --git a/pyload/plugins/hoster/EpicShareNet.py b/pyload/plugins/hoster/EpicShareNet.py
new file mode 100644
index 000000000..a06b4f1dc
--- /dev/null
+++ b/pyload/plugins/hoster/EpicShareNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """EpicShare.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+getInfo = create_getInfo(EpicShareNet)
diff --git a/pyload/plugins/hoster/EuroshareEu.py b/pyload/plugins/hoster/EuroshareEu.py
new file mode 100644
index 000000000..e170facca
--- /dev/null
+++ b/pyload/plugins/hoster/EuroshareEu.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class EuroshareEu(SimpleHoster):
+ __name__ = "EuroshareEu"
+ __type__ = "hoster"
+ __version__ = "0.26"
+
+ __pattern__ = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.*'
+
+ __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!'
+
+ FREE_URL_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 setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.req.setOption("timeout", 120)
+
+
+ def handlePremium(self):
+ if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
+ self.account.relogin(self.user)
+ self.retry(reason=_("User not logged in"))
+
+ self.download(self.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):
+ if re.search(self.ERR_PARDL_PATTERN, self.html) is not None:
+ self.longWait(5 * 60, 12)
+
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.error(_("FREE_URL_PATTERN not found"))
+ parsed_url = "http://euroshare.eu%s" % m.group(1)
+ self.logDebug("URL", parsed_url)
+ self.download(parsed_url, disposition=True)
+
+ check = self.checkDownload({"multi_dl": re.compile(self.ERR_PARDL_PATTERN)})
+ if check == "multi_dl":
+ self.longWait(5 * 60, 12)
+
+
+getInfo = create_getInfo(EuroshareEu)
diff --git a/pyload/plugins/hoster/ExtabitCom.py b/pyload/plugins/hoster/ExtabitCom.py
new file mode 100644
index 000000000..04f78dafb
--- /dev/null
+++ b/pyload/plugins/hoster/ExtabitCom.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class ExtabitCom(SimpleHoster):
+ __name__ = "ExtabitCom"
+ __type__ = "hoster"
+ __version__ = "0.62"
+
+ __pattern__ = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
+
+ __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_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]'
+
+
+ def handleFree(self):
+ 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('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['challenge'], get_data['capture'] = 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_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ url = m.group(1)
+ self.download(url)
+
+
+getInfo = create_getInfo(ExtabitCom)
diff --git a/pyload/plugins/hoster/FastixRu.py b/pyload/plugins/hoster/FastixRu.py
new file mode 100644
index 000000000..42be856ed
--- /dev/null
+++ b/pyload/plugins/hoster/FastixRu.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class FastixRu(Hoster):
+ __name__ = "FastixRu"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?fastix\.(ru|it)/file/(?P<ID>\w{24})'
+
+ __description__ = """Fastix hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+
+ def setup(self):
+ self.chunkLimit = 3
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Fastix")
+ self.fail(_("No Fastix account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ api_key = self.account.getAccountData(self.user)
+ api_key = api_key['api']
+
+ page = self.load("http://fastix.ru/api_v2/",
+ get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url})
+ data = json_loads(page)
+
+ self.logDebug("Json data", data)
+
+ if "error\":true" in page:
+ self.offline()
+ else:
+ new_url = data['downloadlink']
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"error": "<title>An error occurred while processing your request</title>",
+ "empty": re.compile(r"^$")})
+
+ if check == "error":
+ self.retry(wait_time=60, reason=_("An error occurred while generating link"))
+ elif check == "empty":
+ self.retry(wait_time=60, reason=_("Downloaded File was empty"))
diff --git a/pyload/plugins/hoster/FastshareCz.py b/pyload/plugins/hoster/FastshareCz.py
new file mode 100644
index 000000000..11ae4ca42
--- /dev/null
+++ b/pyload/plugins/hoster/FastshareCz.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FastshareCz(SimpleHoster):
+ __name__ = "FastshareCz"
+ __type__ = "hoster"
+ __version__ = "0.24"
+
+ __pattern__ = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
+
+ __description__ = """FastShare.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ URL_REPLACEMENTS = [("#.*", "")]
+
+ COOKIES = [("fastshare.cz", "lang", "en")]
+ CONTENT_DISPOSITION = True
+
+ INFO_PATTERN = r'<h1 class="dwp">(?P<N>[^<]+)</h1>\s*<div class="fileinfo">\s*Size\s*: (?P<S>\d+) (?P<U>[\w^_]+),'
+ OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
+
+ LINK_FREE_PATTERN = r'action=(/free/.*?)>\s*<img src="([^"]*)"><br'
+ LINK_PREMIUM_PATTERN = r'(http://data\d+\.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):
+ 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(urljoin(baseurl, captcha_src))
+ self.download(urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18})
+
+
+ def checkFile(self):
+ 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()
+
+
+getInfo = create_getInfo(FastshareCz)
diff --git a/pyload/plugins/hoster/FileApeCom.py b/pyload/plugins/hoster/FileApeCom.py
new file mode 100644
index 000000000..307e0df8d
--- /dev/null
+++ b/pyload/plugins/hoster/FileApeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """FileApe.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("espes", None)]
+
+
+getInfo = create_getInfo(FileApeCom)
diff --git a/pyload/plugins/hoster/FileParadoxIn.py b/pyload/plugins/hoster/FileParadoxIn.py
new file mode 100644
index 000000000..174aa7211
--- /dev/null
+++ b/pyload/plugins/hoster/FileParadoxIn.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class FileParadoxIn(XFSHoster):
+ __name__ = "FileParadoxIn"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'https?://(?:www\.)?fileparadox\.in/\w{12}'
+
+ __description__ = """FileParadox.in hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RazorWing", "muppetuk1@hotmail.com")]
+
+
+ HOSTER_DOMAIN = "fileparadox.in"
+
+ SIZE_PATTERN = r'</font>\s*\(\s*(?P<S>[^)]+)\s*\)</font>'
+
+
+getInfo = create_getInfo(FileParadoxIn)
diff --git a/pyload/plugins/hoster/FileSharkPl.py b/pyload/plugins/hoster/FileSharkPl.py
new file mode 100644
index 000000000..ad8321d2d
--- /dev/null
+++ b/pyload/plugins/hoster/FileSharkPl.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FileSharkPl(SimpleHoster):
+ __name__ = "FileSharkPl"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?fileshark\.pl/pobierz/\d{6}/\w{5}'
+
+ __description__ = """FileShark.pl hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("prOq", None),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ CONTENT_DISPOSITION = True
+
+ 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 = '(P|p)lik zosta. (usuni.ty|przeniesiony)'
+
+ LINK_FREE_PATTERN = r'<a href="(.*?)" class="btn-upload-free">'
+ LINK_PREMIUM_PATTERN = r'<a 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 = '<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)
+
+
+ #@NOTE: handlePremium method was never been tested
+ def handlePremium(self):
+ super(FilerNet, self).handlePremium()
+ if self.link:
+ self.link = urljoin("http://fileshark.pl/", self.link)
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ link = urljoin("http://fileshark.pl", m.group(1))
+
+ 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, cookies=True, disposition=True)
+
+
+ def checkFile(self):
+ check = self.checkDownload({'wrong_captcha': re.compile(r'<label for="form_captcha" generated="true" class="error">(.*?)</label>'),
+ 'wait_pattern' : re.compile(self.SECONDS_PATTERN),
+ 'DL-found' : re.compile('<a href="(.*)">')})
+
+ if check == "DL-found":
+ self.correctCaptcha()
+
+ elif check == "wrong_captcha":
+ self.invalidCaptcha()
+ self.retry(10, 1, _("Wrong captcha solution"))
+
+ elif check == "wait_pattern":
+ self.retry()
+
+
+ def _decode64(self, data, *args, **kwargs):
+ return data.decode("base64")
+
+
+getInfo = create_getInfo(FileSharkPl)
diff --git a/pyload/plugins/hoster/FileStoreTo.py b/pyload/plugins/hoster/FileStoreTo.py
new file mode 100644
index 000000000..f08474d28
--- /dev/null
+++ b/pyload/plugins/hoster/FileStoreTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FileStoreTo(SimpleHoster):
+ __name__ = "FileStoreTo"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
+
+ __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>.+)</span><br />Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self):
+ self.wait(10)
+ ldc = re.search(r'wert="(\w+)"', self.html).group(1)
+ link = self.load("http://filestore.to/ajax/download.php", get={"LDC": ldc})
+ self.download(link)
+
+
+getInfo = create_getInfo(FileStoreTo)
diff --git a/pyload/plugins/hoster/FilebeerInfo.py b/pyload/plugins/hoster/FilebeerInfo.py
new file mode 100644
index 000000000..dcc6d1513
--- /dev/null
+++ b/pyload/plugins/hoster/FilebeerInfo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+).*'
+
+ __description__ = """Filebeer.info plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(FilebeerInfo)
diff --git a/pyload/plugins/hoster/FilecloudIo.py b/pyload/plugins/hoster/FilecloudIo.py
new file mode 100644
index 000000000..deca28007
--- /dev/null
+++ b/pyload/plugins/hoster/FilecloudIo.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilecloudIo(SimpleHoster):
+ __name__ = "FilecloudIo"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+).*'
+
+ __description__ = """Filecloud.io hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
+ NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
+ 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_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ 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"))
+
+ if not self.account:
+ self.fail(_("User not logged in"))
+ elif not self.account.logged_in:
+ challenge, response = 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_challenge'], data['recaptcha_response'] = 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_PATTERN % self.info['pattern']['ID'], self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ if "size" in self.info and self.info['size']:
+ self.check_data = {"size": int(self.info['size'])}
+
+ download_url = m.group(1)
+ self.download(download_url)
+ else:
+ self.fail(_("Unexpected server response"))
+
+
+ def handlePremium(self):
+ 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.download(rep['download_url'], disposition=True)
+ else:
+ self.fail(rep['message'])
+
+
+getInfo = create_getInfo(FilecloudIo)
diff --git a/pyload/plugins/hoster/FilefactoryCom.py b/pyload/plugins/hoster/FilefactoryCom.py
new file mode 100644
index 000000000..ac7899ec5
--- /dev/null
+++ b/pyload/plugins/hoster/FilefactoryCom.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.network.RequestFactory import getURL
+from pyload.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.52"
+
+ __pattern__ = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+'
+
+ __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_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):
+ 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_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ dl_link = m.group(1)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(int(m.group(1)))
+
+ self.download(dl_link, disposition=True)
+
+ 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"))
+
+
+ def handlePremium(self):
+ header = self.load(self.pyfile.url, just_header=True)
+
+ if 'location' in header:
+ url = header['location'].strip()
+ if not url.startswith("http://"):
+ url = urljoin("http://www.filefactory.com", url)
+ elif 'content-disposition' in header:
+ url = self.pyfile.url
+ else:
+ html = self.load(self.pyfile.url)
+ m = re.search(self.LINK_PATTERN, html)
+ if m:
+ url = m.group(1)
+ else:
+ self.error(_("Premium download link not found"))
+
+ self.download(url, disposition=True)
diff --git a/pyload/plugins/hoster/FilejungleCom.py b/pyload/plugins/hoster/FilejungleCom.py
new file mode 100644
index 000000000..1cfa4c29e
--- /dev/null
+++ b/pyload/plugins/hoster/FilejungleCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.FileserveCom import FileserveCom, checkFile
+from pyload.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/pyload/plugins/hoster/FileomCom.py b/pyload/plugins/hoster/FileomCom.py
new file mode 100644
index 000000000..128039c2e
--- /dev/null
+++ b/pyload/plugins/hoster/FileomCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://fileom.com/gycaytyzdw3g/random.bin.html
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "fileom.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/pyload/plugins/hoster/FilepostCom.py b/pyload/plugins/hoster/FilepostCom.py
new file mode 100644
index 000000000..e80ec56bc
--- /dev/null
+++ b/pyload/plugins/hoster/FilepostCom.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilepostCom(SimpleHoster):
+ __name__ = "FilepostCom"
+ __type__ = "hoster"
+ __version__ = "0.30"
+
+ __pattern__ = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)'
+
+ __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):
+ 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() * 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
+ for file_pass in self.getPassword().splitlines():
+ get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
+ post_dict['file_pass'] = file_pass
+ self.logInfo(_("Password protected link, trying ") + file_pass)
+
+ download_url = self.getJsonResponse(get_dict, post_dict, 'link')
+ if download_url:
+ break
+
+ else:
+ self.fail(_("No or incorrect password"))
+
+ else:
+ # Solve recaptcha
+ recaptcha = ReCaptcha(self)
+
+ for i in xrange(5):
+ get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
+ if i:
+ post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field'] = recaptcha.challenge(
+ captcha_key)
+ self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
+ captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
+
+ download_url = self.getJsonResponse(get_dict, post_dict, 'link')
+ if download_url:
+ if i:
+ self.correctCaptcha()
+ break
+ elif i:
+ self.invalidCaptcha()
+
+ else:
+ self.fail(_("Invalid captcha"))
+
+ # Download
+ self.download(download_url)
+
+
+ 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/pyload/plugins/hoster/FilepupNet.py b/pyload/plugins/hoster/FilepupNet.py
new file mode 100644
index 000000000..0b1247bf4
--- /dev/null
+++ b/pyload/plugins/hoster/FilepupNet.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.filepup.net/files/k5w4ZVoF1410184283.html
+# http://www.filepup.net/files/R4GBq9XH1410186553.html
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilepupNet(SimpleHoster):
+ __name__ = "FilepupNet"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?filepup\.net/files/\w+'
+
+ __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_PATTERN = r'(http://www\.filepup\.net/get/.+?)\''
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_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"})
+
+ check = self.checkDownload({'html': re.compile("html")})
+ if check == "html":
+ self.error(_("Downloaded file is an html page"))
+
+
+getInfo = create_getInfo(FilepupNet)
diff --git a/pyload/plugins/hoster/FilerNet.py b/pyload/plugins/hoster/FilerNet.py
new file mode 100644
index 000000000..04b04219f
--- /dev/null
+++ b/pyload/plugins/hoster/FilerNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filer.net/get/ivgf5ztw53et3ogd
+# http://filer.net/get/hgo14gzcng3scbvv
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilerNet(SimpleHoster):
+ __name__ = "FilerNet"
+ __type__ = "hoster"
+ __version__ = "0.09"
+
+ __pattern__ = r'https?://(?:www\.)?filer\.net/get/\w+'
+
+ __description__ = """Filer.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ CONTENT_DISPOSITION = True
+
+ 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'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>'
+
+
+ def checkErrors(self):
+ # Wait between downloads
+ m = re.search(r'musst du <span id="time">(\d+)</span> Sekunden warten', self.html)
+ if m:
+ errmsg = self.info['error'] = _("Wait between free downloads")
+ self.retry(wait_time=int(m.group(1)), reason=errmsg)
+
+ self.info.pop('error', None)
+
+
+ def handleFree(self):
+ 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(self.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)
+
+ for _i in xrange(5):
+ challenge, response = recaptcha.challenge()
+
+ header = self.load(self.pyfile.url,
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response,
+ 'hash' : inputs['hash']})
+
+ if 'location' in header and header['location']:
+ self.correctCaptcha()
+ self.link = urljoin('http://filer.net', header['location'])
+ return
+ else:
+ self.invalidCaptcha()
+
+
+ def handlePremium(self):
+ super(FilerNet, self).handlePremium()
+ if self.link:
+ self.link = urljoin("http://filer.net/", self.link)
+
+
+getInfo = create_getInfo(FilerNet)
diff --git a/pyload/plugins/hoster/FilerioCom.py b/pyload/plugins/hoster/FilerioCom.py
new file mode 100644
index 000000000..afa1356fc
--- /dev/null
+++ b/pyload/plugins/hoster/FilerioCom.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "filerio.in"
+
+ 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/pyload/plugins/hoster/FilesMailRu.py b/pyload/plugins/hoster/FilesMailRu.py
new file mode 100644
index 000000000..4a5118fda
--- /dev/null
+++ b/pyload/plugins/hoster/FilesMailRu.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.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.31"
+
+ __pattern__ = r'http://(?:www\.)?files\.mail\.ru/.*'
+
+ __description__ = """Files.mail.ru hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("oZiRiz", "ich@oziriz.de")]
+
+
+ def setup(self):
+ if not self.account:
+ self.multiDL = False
+
+
+ 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/plugins/hoster/FileserveCom.py b/pyload/plugins/hoster/FileserveCom.py
new file mode 100644
index 000000000..d98de9e6b
--- /dev/null
+++ b/pyload/plugins/hoster/FileserveCom.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import ReCaptcha
+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.52"
+
+ __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=\'(?P<key>.+?)\''
+ 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("key")
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ challenge, response = 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:
+ check = self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)})
+
+ if check == "login":
+ 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/plugins/hoster/FileshareInUa.py b/pyload/plugins/hoster/FileshareInUa.py
new file mode 100644
index 000000000..8bb1a72c5
--- /dev/null
+++ b/pyload/plugins/hoster/FileshareInUa.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Fileshare.in.ua hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("fwannmacher", "felipe@warhammerproject.com")]
+
+
+getInfo = create_getInfo(FileshareInUa)
diff --git a/pyload/plugins/hoster/FilesonicCom.py b/pyload/plugins/hoster/FilesonicCom.py
new file mode 100644
index 000000000..323983169
--- /dev/null
+++ b/pyload/plugins/hoster/FilesonicCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Filesonic.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", None)]
+
+
+getInfo = create_getInfo(FilesonicCom)
diff --git a/pyload/plugins/hoster/FilezyNet.py b/pyload/plugins/hoster/FilezyNet.py
new file mode 100644
index 000000000..f36ecd51e
--- /dev/null
+++ b/pyload/plugins/hoster/FilezyNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Filezy.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = []
+
+
+getInfo = create_getInfo(FilezyNet)
diff --git a/pyload/plugins/hoster/FiredriveCom.py b/pyload/plugins/hoster/FiredriveCom.py
new file mode 100644
index 000000000..3c7ec25f2
--- /dev/null
+++ b/pyload/plugins/hoster/FiredriveCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)'
+
+ __description__ = """Firedrive.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+getInfo = create_getInfo(FiredriveCom)
diff --git a/pyload/plugins/hoster/FlyFilesNet.py b/pyload/plugins/hoster/FlyFilesNet.py
new file mode 100644
index 000000000..6a9a3d63f
--- /dev/null
+++ b/pyload/plugins/hoster/FlyFilesNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+class FlyFilesNet(SimpleHoster):
+ __name__ = "FlyFilesNet"
+ __type__ = "hoster"
+ __version__ = "0.10"
+
+ __pattern__ = r'http://(?:www\.)?flyfiles\.net/.*'
+
+ __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 = 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}, cookies=True)
+ 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()
+
+ download_url = parsed_url.replace('#downlink|', '')
+
+ self.download(download_url)
diff --git a/pyload/plugins/hoster/FourSharedCom.py b/pyload/plugins/hoster/FourSharedCom.py
new file mode 100644
index 000000000..79e99aee4
--- /dev/null
+++ b/pyload/plugins/hoster/FourSharedCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FourSharedCom(SimpleHoster):
+ __name__ = "FourSharedCom"
+ __type__ = "hoster"
+ __version__ = "0.30"
+
+ __pattern__ = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+?/.*'
+
+ __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 = [(",", "")]
+
+ DOWNLOAD_URL_PATTERN = r'name="d3link" value="(.*?)"'
+ DOWNLOAD_BUTTON_PATTERN = r'id="btnLink" href="(.*?)"'
+ FID_PATTERN = r'name="d3fid" value="(.*?)"'
+
+
+ def handleFree(self):
+ if not self.account:
+ self.fail(_("User not logged in"))
+
+ m = re.search(self.DOWNLOAD_BUTTON_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ else:
+ link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', self.pyfile.url)
+
+ self.html = self.load(link)
+
+ m = re.search(self.DOWNLOAD_URL_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link"))
+ link = m.group(1)
+
+ try:
+ m = re.search(self.FID_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)
+ self.download(link)
+
+
+getInfo = create_getInfo(FourSharedCom)
diff --git a/pyload/plugins/hoster/FreakshareCom.py b/pyload/plugins/hoster/FreakshareCom.py
new file mode 100644
index 000000000..ba4b7ddb0
--- /dev/null
+++ b/pyload/plugins/hoster/FreakshareCom.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import ReCaptcha
+
+
+class FreakshareCom(Hoster):
+ __name__ = "FreakshareCom"
+ __type__ = "hoster"
+ __version__ = "0.39"
+
+ __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:
+ file_name = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
+ if file_name is not None:
+ file_name = file_name.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:
+ file_size_check = re.search(
+ r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
+ if file_size_check is not None:
+ units = float(file_size_check.group(1).replace(",", ""))
+ pow = {'KB': 1, 'MB': 2, 'GB': 3}[file_size_check.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) is not None:
+ 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/plugins/hoster/FreeWayMe.py b/pyload/plugins/hoster/FreeWayMe.py
new file mode 100644
index 000000000..153b41b32
--- /dev/null
+++ b/pyload/plugins/hoster/FreeWayMe.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class FreeWayMe(Hoster):
+ __name__ = "FreeWayMe"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'https://(?:www\.)?free-way\.me/.*'
+
+ __description__ = """FreeWayMe 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 process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "FreeWayMe")
+ self.fail(_("No FreeWay account provided"))
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ (user, data) = self.account.selectAccount()
+
+ self.download(
+ "https://www.free-way.me/load.php",
+ get={"multiget": 7, "url": pyfile.url, "user": user, "pw": self.account.getpw(user), "json": ""},
+ disposition=True)
diff --git a/pyload/plugins/hoster/FreevideoCz.py b/pyload/plugins/hoster/FreevideoCz.py
new file mode 100644
index 000000000..ad6d4c336
--- /dev/null
+++ b/pyload/plugins/hoster/FreevideoCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/.+'
+
+ __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/pyload/plugins/hoster/FshareVn.py b/pyload/plugins/hoster/FshareVn.py
new file mode 100644
index 000000000..d851209a2
--- /dev/null
+++ b/pyload/plugins/hoster/FshareVn.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime, gmtime
+
+from pyload.network.RequestFactory import getURL
+from pyload.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.17"
+
+ __pattern__ = r'http://(?:www\.)?fshare\.vn/file/.*'
+
+ __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_PATTERN = r'action="(http://download.*?)[#"]'
+ WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
+
+
+ def process(self, pyfile):
+ self.html = self.load('http://www.fshare.vn/check_link.php', post={
+ "action": "check_link",
+ "arrlinks": pyfile.url
+ }, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ self.checkDownloadedFile()
+
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('frm_download')
+ self.url = self.pyfile.url + action
+
+ if not inputs:
+ self.error(_("No FORM"))
+ elif 'link_file_pwd_dl' in inputs:
+ for password in self.getPassword().splitlines():
+ self.logInfo(_("Password protected link, trying ") + password)
+ inputs['link_file_pwd_dl'] = password
+ self.html = self.load(self.url, post=inputs, decode=True)
+ if not 'name="link_file_pwd_dl"' in self.html:
+ break
+ else:
+ self.fail(_("No or incorrect password"))
+ else:
+ self.html = self.load(self.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_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+ self.url = m.group(1)
+ self.logDebug("FREE DL URL: %s" % self.url)
+
+ self.wait()
+ self.download(self.url)
+
+
+ def handlePremium(self):
+ self.download(self.pyfile.url)
+
+
+ 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 = mktime(strptime(m.group(1), "%d/%m/%Y %H:%M"))
+ self.wait(wait_until - mktime(gmtime()) - 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)
+
+
+ def checkDownloadedFile(self):
+ # check download
+ check = self.checkDownload({
+ "not_found": "<head><title>404 Not Found</title></head>"
+ })
+
+ if check == "not_found":
+ self.fail(_("File not m on server"))
diff --git a/pyload/plugins/hoster/Ftp.py b/pyload/plugins/hoster/Ftp.py
new file mode 100644
index 000000000..c130cc395
--- /dev/null
+++ b/pyload/plugins/hoster/Ftp.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from urllib import quote, unquote
+from urlparse import urlparse
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class Ftp(Hoster):
+ __name__ = "Ftp"
+ __type__ = "hoster"
+ __version__ = "0.43"
+
+ __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(pyfile.url)
+ netloc = parsed_url.netloc
+
+ pyfile.name = parsed_url.path.rpartition('/')[2]
+ try:
+ pyfile.name = 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.accounts[netloc]['password'])
+ else:
+ for pwd in self.getPassword().splitlines():
+ if ":" in pwd:
+ self.req.addAuth(pwd.strip())
+ break
+
+ 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(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 + 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/plugins/hoster/GamefrontCom.py b/pyload/plugins/hoster/GamefrontCom.py
new file mode 100644
index 000000000..50a5be554
--- /dev/null
+++ b/pyload/plugins/hoster/GamefrontCom.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.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/plugins/hoster/GigapetaCom.py b/pyload/plugins/hoster/GigapetaCom.py
new file mode 100644
index 000000000..419a97558
--- /dev/null
+++ b/pyload/plugins/hoster/GigapetaCom.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+from random import randint
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class GigapetaCom(SimpleHoster):
+ __name__ = "GigapetaCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
+
+ __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):
+ captcha_key = str(randint(1, 100000000))
+ captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+
+ for _i in xrange(5):
+ self.checkErrors()
+
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(self.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:
+ download_url = 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"))
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+ self.download(download_url)
+
+
+ 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/pyload/plugins/hoster/GooIm.py b/pyload/plugins/hoster/GooIm.py
new file mode 100644
index 000000000..f3626cc57
--- /dev/null
+++ b/pyload/plugins/hoster/GooIm.py
@@ -0,0 +1,39 @@
+# -*- 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.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class GooIm(SimpleHoster):
+ __name__ = "GooIm"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?goo\.im/.+'
+
+ __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):
+ url = self.pyfile.url
+ self.html = self.load(url, cookies=True)
+ self.wait(10)
+ self.download(url, cookies=True)
+
+
+getInfo = create_getInfo(GooIm)
diff --git a/pyload/plugins/hoster/HellshareCz.py b/pyload/plugins/hoster/HellshareCz.py
new file mode 100644
index 000000000..90dfb4dd6
--- /dev/null
+++ b/pyload/plugins/hoster/HellshareCz.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class HellshareCz(SimpleHoster):
+ __name__ = "HellshareCz"
+ __type__ = "hoster"
+ __version__ = "0.83"
+
+ __pattern__ = r'(http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+).*'
+
+ __description__ = """Hellshare.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ 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>'
+ SHOW_WINDOW_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True if self.account else False
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.fail(_("User not logged in"))
+ pyfile.url = re.match(self.__pattern__, pyfile.url).group(1)
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+ if not self.checkTrafficLeft():
+ self.fail(_("Not enough traffic left for user ") + self.user)
+
+ m = re.search(self.SHOW_WINDOW_PATTERN, self.html)
+ if m is None:
+ self.error(_("SHOW_WINDOW_PATTERN not found"))
+
+ self.url = "http://www.hellshare.com" + m.group(1)
+ self.download(self.url)
+
+
+getInfo = create_getInfo(HellshareCz)
diff --git a/pyload/plugins/hoster/HellspyCz.py b/pyload/plugins/hoster/HellspyCz.py
new file mode 100644
index 000000000..f3578c0f3
--- /dev/null
+++ b/pyload/plugins/hoster/HellspyCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)/?.*'
+
+ __description__ = """HellSpy.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(HellspyCz)
diff --git a/pyload/plugins/hoster/HotfileCom.py b/pyload/plugins/hoster/HotfileCom.py
new file mode 100644
index 000000000..bede362e5
--- /dev/null
+++ b/pyload/plugins/hoster/HotfileCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __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/pyload/plugins/hoster/HugefilesNet.py b/pyload/plugins/hoster/HugefilesNet.py
new file mode 100644
index 000000000..1c32b7378
--- /dev/null
+++ b/pyload/plugins/hoster/HugefilesNet.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "hugefiles.net"
+
+ 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/pyload/plugins/hoster/HundredEightyUploadCom.py b/pyload/plugins/hoster/HundredEightyUploadCom.py
new file mode 100644
index 000000000..717344efa
--- /dev/null
+++ b/pyload/plugins/hoster/HundredEightyUploadCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://180upload.com/js9qdm6kjnrs
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class HundredEightyUploadCom(XFSHoster):
+ __name__ = "HundredEightyUploadCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?180upload\.com/\w{12}'
+
+ __description__ = """180upload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ HOSTER_DOMAIN = "180upload.com"
+
+ NAME_PATTERN = r'Filename:</b></td><td nowrap>(?P<N>.+)</td></tr>-->'
+ SIZE_PATTERN = r'Size:</b></td><td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)\s*<small>'
+
+
+getInfo = create_getInfo(HundredEightyUploadCom)
diff --git a/pyload/plugins/hoster/IFileWs.py b/pyload/plugins/hoster/IFileWs.py
new file mode 100644
index 000000000..d92ec087d
--- /dev/null
+++ b/pyload/plugins/hoster/IFileWs.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Ifile.ws hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("z00nx", "z00nx0@gmail.com")]
+
+
+getInfo = create_getInfo(IFileWs)
diff --git a/pyload/plugins/hoster/IcyFilesCom.py b/pyload/plugins/hoster/IcyFilesCom.py
new file mode 100644
index 000000000..126b9208d
--- /dev/null
+++ b/pyload/plugins/hoster/IcyFilesCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class IcyFilesCom(DeadHoster):
+ __name__ = "IcyFilesCom"
+ __type__ = "hoster"
+ __version__ = "0.06"
+
+ __pattern__ = r'http://(?:www\.)?icyfiles\.com/(.*)'
+
+ __description__ = """IcyFiles.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("godofdream", "soilfiction@gmail.com")]
+
+
+getInfo = create_getInfo(IcyFilesCom)
diff --git a/pyload/plugins/hoster/IfileIt.py b/pyload/plugins/hoster/IfileIt.py
new file mode 100644
index 000000000..884bf8582
--- /dev/null
+++ b/pyload/plugins/hoster/IfileIt.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class IfileIt(SimpleHoster):
+ __name__ = "IfileIt"
+ __type__ = "hoster"
+ __version__ = "0.28"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """Ifile.it"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'</span> If it doesn\'t, <a target="_blank" href="([^"]+)">'
+ RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\''
+ INFO_PATTERN = r'<span style="cursor: default;[^>]*>\s*(?P<N>.*?)\s*&nbsp;\s*<strong>\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\s*</strong>\s*</span>'
+ OFFLINE_PATTERN = r'<span style="cursor: default;[^>]*>\s*&nbsp;\s*<strong>\s*</strong>\s*</span>'
+ TEMP_OFFLINE_PATTERN = r'<span class="msg_red">Downloading of this file is temporarily disabled</span>'
+
+
+ def handleFree(self):
+ ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
+ json_url = 'http://ifile.it/new_download-request.json'
+ post_data = {"ukey": ukey, "ab": "0"}
+ res = json_loads(self.load(json_url, post=post_data))
+
+ self.logDebug(res)
+
+ if res['status'] == 3:
+ self.offline()
+
+ if res['captcha']:
+ captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1)
+
+ recaptcha = ReCaptcha(self)
+ post_data['ctype'] = "recaptcha"
+
+ for _i in xrange(5):
+ challenge, response = recaptcha.challenge(captcha_key)
+ post_data.update({'recaptcha_challenge': challenge,
+ 'recaptcha_response' : response})
+ res = json_loads(self.load(json_url, post=post_data))
+ self.logDebug(res)
+
+ if res['retry']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("Incorrect captcha"))
+
+ if not "ticket_url" in res:
+ self.error(_("No download URL"))
+
+ self.download(res['ticket_url'])
+
+
+getInfo = create_getInfo(IfileIt)
diff --git a/pyload/plugins/hoster/IfolderRu.py b/pyload/plugins/hoster/IfolderRu.py
new file mode 100644
index 000000000..2a961f780
--- /dev/null
+++ b/pyload/plugins/hoster/IfolderRu.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class IfolderRu(SimpleHoster):
+ __name__ = "IfolderRu"
+ __type__ = "hoster"
+ __version__ = "0.38"
+
+ __pattern__ = r'http://(?:www\.)?(?:ifolder\.ru|rusfolder\.(?:com|net|ru))/(?:files/)?(?P<ID>\d+).*'
+
+ __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'<a href=(http://ints\.(?:rusfolder\.com|ifolder\.ru)/ints/sponsor/\?bi=\d*&session=([^&]+)&u=[^>]+)>'
+ INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "([^"]+)";\}'
+ HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\''
+ LINK_PATTERN = r'<a id="download_file_href" href="([^"]+)"'
+ WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True if self.account else False
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ file_id = re.match(self.__pattern__, pyfile.url).group('ID')
+ self.html = self.load("http://rusfolder.com/%s" % file_id, cookies=True, decode=True)
+ self.getFileInfo()
+
+ url = re.search(r"location\.href = '(http://ints\..*?=)'", self.html).group(1)
+ self.html = self.load(url, cookies=True, decode=True)
+
+ url, session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
+ self.html = self.load(url, cookies=True, decode=True)
+
+ url = "http://ints.rusfolder.com/ints/frame/?session=%s" % session_id
+ self.html = self.load(url, cookies=True)
+
+ self.wait(31, False)
+
+ captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
+ for _i in xrange(5):
+ self.html = self.load(url, cookies=True)
+ action, inputs = self.parseHtmlForm('ID="Form1"')
+ inputs['ints_session'] = re.search(self.INTS_SESSION_PATTERN, self.html).group(1)
+ inputs[re.search(self.HIDDEN_INPUT_PATTERN, self.html).group(1)] = '1'
+ inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
+ inputs['action'] = '1'
+ self.logDebug(inputs)
+
+ self.html = self.load(url, decode=True, cookies=True, post=inputs)
+ if self.WRONG_CAPTCHA_PATTERN in self.html:
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("Invalid captcha"))
+
+ download_url = re.search(self.LINK_PATTERN, self.html).group(1)
+ self.correctCaptcha()
+ self.download(download_url)
+
+
+getInfo = create_getInfo(IfolderRu)
diff --git a/pyload/plugins/hoster/JumbofilesCom.py b/pyload/plugins/hoster/JumbofilesCom.py
new file mode 100644
index 000000000..6b8611a45
--- /dev/null
+++ b/pyload/plugins/hoster/JumbofilesCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class JumbofilesCom(SimpleHoster):
+ __name__ = "JumbofilesCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?jumbofiles\.com/(\w{12}).*'
+
+ __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_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self):
+ ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
+ post_data = {"id": ukey, "op": "download3", "rand": ""}
+ html = self.load(self.pyfile.url, post=post_data, decode=True)
+ url = re.search(self.LINK_PATTERN, html).group(1)
+ self.download(url)
+
+
+getInfo = create_getInfo(JumbofilesCom)
diff --git a/pyload/plugins/hoster/JunocloudMe.py b/pyload/plugins/hoster/JunocloudMe.py
new file mode 100644
index 000000000..cef475c1b
--- /dev/null
+++ b/pyload/plugins/hoster/JunocloudMe.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "junocloud.me"
+
+ URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")]
+
+ SIZE_PATTERN = r'<p class="request_filesize">Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)</p>'
+
+ 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/pyload/plugins/hoster/Keep2shareCc.py b/pyload/plugins/hoster/Keep2shareCc.py
new file mode 100644
index 000000000..44587c946
--- /dev/null
+++ b/pyload/plugins/hoster/Keep2shareCc.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin, urlparse
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import _isDirectLink, SimpleHoster, create_getInfo
+
+
+class Keep2shareCc(SimpleHoster):
+ __name__ = "Keep2shareCc"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
+
+ __description__ = """Keep2share.cc hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern__ + ".*", "http://k2s.cc/file/\g<ID>")]
+
+ CONTENT_DISPOSITION = True
+
+ 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 = LINK_PREMIUM_PATTERN = r'"([^"]+url.html?file=.+?)"|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):
+ self.fid = re.search(r'<input type="hidden" name="slow_id" value="([^"]+)">', self.html).group(1)
+ self.html = self.load(self.pyfile.url, post={'yt0': '', 'slow_id': self.fid})
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+
+ if m is None:
+ self.handleCaptcha()
+
+ self.wait(30)
+
+ self.html = self.load(self.pyfile.url, post={'uniqueId': self.fid, 'free': 1})
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = self._getDownloadLink(m.group(1))
+
+
+ def handlePremium(self):
+ super(Keep2shareCc, self).handlePremium()
+ if self.link:
+ self.link = self._getDownloadLink(self.link)
+
+
+ def handleCaptcha(self):
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ post_data = {'free' : 1,
+ 'freeDownloadRequest': 1,
+ 'uniqueId' : self.fid,
+ 'yt0' : ''}
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_url = urljoin(self.base, m.group(1))
+ post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url)
+ else:
+ challenge, response = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(self.pyfile.url, post=post_data)
+
+ if 'recaptcha' not in self.html:
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("All captcha attempts failed"))
+
+
+ def _getDownloadLink(self, url):
+ p = urlparse(self.pyfile.url)
+ base = "%s://%s" % (p.scheme, p.netloc)
+ link = _isDirectLink(self, url, self.premium)
+ return urljoin(base, link) if link else ""
+
+
+getInfo = create_getInfo(Keep2shareCc)
diff --git a/pyload/plugins/hoster/KickloadCom.py b/pyload/plugins/hoster/KickloadCom.py
new file mode 100644
index 000000000..88c84390c
--- /dev/null
+++ b/pyload/plugins/hoster/KickloadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class KickloadCom(DeadHoster):
+ __name__ = "KickloadCom"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?kickload\.com/get/.+'
+
+ __description__ = """Kickload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("mkaay", "mkaay@mkaay.de")]
+
+
+getInfo = create_getInfo(KickloadCom)
diff --git a/pyload/plugins/hoster/LemUploadsCom.py b/pyload/plugins/hoster/LemUploadsCom.py
new file mode 100644
index 000000000..0ef739a56
--- /dev/null
+++ b/pyload/plugins/hoster/LemUploadsCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """LemUploads.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+getInfo = create_getInfo(LemUploadsCom)
diff --git a/pyload/plugins/hoster/LetitbitNet.py b/pyload/plugins/hoster/LetitbitNet.py
new file mode 100644
index 000000000..e13fe8a66
--- /dev/null
+++ b/pyload/plugins/hoster/LetitbitNet.py
@@ -0,0 +1,142 @@
+# -*- 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
+
+from urllib import urlencode, urlopen
+from urlparse import urljoin
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+def api_download_info(url):
+ json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
+ post_data = urlencode({'r': json_dumps(json_data)})
+ api_rep = urlopen("http://api.letitbit.net/json", data=post_data).read()
+ return json_loads(api_rep)
+
+
+def getInfo(urls):
+ for url in urls:
+ api_rep = api_download_info(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.26"
+
+ __pattern__ = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.*'
+
+ __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\'(?P<value>.+?)\''
+
+
+ def setup(self):
+ self.resumeDownload = True
+
+
+ def getFileInfo(self):
+ api_rep = api_download_info(self.pyfile.url)
+ if api_rep['status'] == 'OK':
+ self.api_data = api_rep['data'][0]
+ self.pyfile.name = self.api_data['name']
+ self.pyfile.size = self.api_data['size']
+ else:
+ self.offline()
+
+
+ def handleFree(self):
+ action, inputs = self.parseHtmlForm('id="ifree_form"')
+ if not action:
+ self.error(_("ifree_form"))
+
+ self.pyfile.size = float(inputs['sssize'])
+ self.logDebug(action, inputs)
+ inputs['desc'] = ""
+
+ self.html = self.load(urljoin("http://letitbit.net/", action), post=inputs, cookies=True)
+
+ 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=" ", cookies=True)
+ if res != '1':
+ self.error(_("Unknown response - ajax_check_url"))
+ self.logDebug(res)
+
+ recaptcha = ReCaptcha(self)
+ challenge, response = 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, cookies=True)
+ 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.correctCaptcha()
+
+ for download_url in urls:
+ try:
+ self.download(download_url)
+ break
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.fail(_("Download did not finish correctly"))
+
+
+ def handlePremium(self):
+ api_key = self.user
+ premium_key = self.account.getAccountData(self.user)['password']
+
+ json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": self.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.download(api_rep['data'][0][0], disposition=True)
diff --git a/pyload/plugins/hoster/LinksnappyCom.py b/pyload/plugins/hoster/LinksnappyCom.py
new file mode 100644
index 000000000..d7991ff3c
--- /dev/null
+++ b/pyload/plugins/hoster/LinksnappyCom.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urlsplit
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class LinksnappyCom(Hoster):
+ __name__ = "LinksnappyCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?linksnappy\.com'
+
+ __description__ = """Linksnappy.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ SINGLE_CHUNK_HOSTERS = ('easybytez.com')
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Linksnappy.com")
+ self.fail(_("No Linksnappy.com account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ 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']:
+ msg = _("Error converting the link")
+ self.logError(msg, j['error'])
+ self.fail(msg)
+
+ pyfile.name = j['filename']
+ new_url = j['generated']
+
+ if host in self.SINGLE_CHUNK_HOSTERS:
+ self.chunkLimit = 1
+ else:
+ self.setup()
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"html302": "<title>302 Found</title>"})
+ if check == "html302":
+ self.retry(wait_time=5, reason=_("Linksnappy returns only HTML data"))
+
+
+ @staticmethod
+ def _get_host(url):
+ host = urlsplit(url).netloc
+ return re.search(r'[\w-]+\.\w+$', host).group(0)
diff --git a/pyload/plugins/hoster/LoadTo.py b/pyload/plugins/hoster/LoadTo.py
new file mode 100644
index 000000000..adf951ed9
--- /dev/null
+++ b/pyload/plugins/hoster/LoadTo.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.load.to/JWydcofUY6/random.bin
+# http://www.load.to/oeSmrfkXE/random100.bin
+
+import re
+
+from pyload.plugins.internal.captcha import SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class LoadTo(SimpleHoster):
+ __name__ = "LoadTo"
+ __type__ = "hoster"
+ __version__ = "0.18"
+
+ __pattern__ = r'http://(?:www\.)?load\.to/\w+'
+
+ __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_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):
+ # Search for Download URL
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ download_url = m.group(1)
+
+ # Set Timer - may be obsolete
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(int(m.group(1)))
+
+ # Load.to is using solvemedia captchas since ~july 2014:
+ solvemedia = SolveMedia(self)
+ captcha_key = solvemedia.detect_key()
+
+ if captcha_key is None:
+ self.download(download_url)
+ else:
+ challenge, response = solvemedia.challenge(captcha_key)
+
+ self.download(download_url, post={"adcopy_challenge": challenge, "adcopy_response": response})
+
+ check = self.checkDownload({'404': re.compile("\A<h1>404 Not Found</h1>"), 'html': re.compile("html")})
+
+ if check == "404":
+ self.invalidCaptcha()
+ self.retry()
+ elif check == "html":
+ self.logWarning(_("Downloaded file is an html page, will retry"))
+ self.retry()
+
+
+getInfo = create_getInfo(LoadTo)
diff --git a/pyload/plugins/hoster/LomafileCom.py b/pyload/plugins/hoster/LomafileCom.py
new file mode 100644
index 000000000..2c4bd37a2
--- /dev/null
+++ b/pyload/plugins/hoster/LomafileCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class LomafileCom(XFSHoster):
+ __name__ = "LomafileCom"
+ __type__ = "hoster"
+ __version__ = "0.51"
+
+ __pattern__ = r'http://lomafile\.com/\w{12}'
+
+ __description__ = """Lomafile.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com"),
+ ("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "lomafile.com"
+
+ NAME_PATTERN = r'<a href="http://lomafile\.com/w{12}/(?P<N>.+?)">'
+ SIZE_PATTERN = r'Size:</b></td><td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>(No such file|Software error:<)'
+ TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
+
+ CAPTCHA_PATTERN = r'(http://lomafile\.com/captchas/[^"\']+)'
+
+
+getInfo = create_getInfo(LomafileCom)
diff --git a/pyload/plugins/hoster/LuckyShareNet.py b/pyload/plugins/hoster/LuckyShareNet.py
new file mode 100644
index 000000000..a32ac6be9
--- /dev/null
+++ b/pyload/plugins/hoster/LuckyShareNet.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from bottle import json_loads
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class LuckyShareNet(SimpleHoster):
+ __name__ = "LuckyShareNet"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})'
+
+ __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):
+ 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(int(json['time']))
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ challenge, response = 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.download(json['link'])
+
+
+getInfo = create_getInfo(LuckyShareNet)
diff --git a/pyload/plugins/hoster/MediafireCom.py b/pyload/plugins/hoster/MediafireCom.py
new file mode 100644
index 000000000..c9ab49643
--- /dev/null
+++ b/pyload/plugins/hoster/MediafireCom.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+from pyload.network.RequestFactory import getURL
+
+
+def replace_eval(js_expr):
+ return js_expr.replace(r'eval("', '').replace(r"\'", r"'").replace(r'\"', r'"')
+
+
+def checkHTMLHeader(url):
+ try:
+ for _i in xrange(3):
+ header = getURL(url, just_header=True)
+ for line in header.splitlines():
+ line = line.lower()
+ if 'location' in line:
+ url = line.split(':', 1)[1].strip()
+ if 'error.php?errno=320' in url:
+ return url, 1
+ if not url.startswith('http://'):
+ url = 'http://www.mediafire.com' + url
+ break
+ elif 'content-disposition' in line:
+ return url, 2
+ else:
+ break
+ except Exception:
+ return url, 3
+
+ return url, 0
+
+
+def getInfo(urls):
+ for url in urls:
+ location, status = checkHTMLHeader(url)
+
+ if status:
+ file_info = (url, 0, status, url)
+ else:
+ file_info = parseFileInfo(MediafireCom, url, getURL(url, decode=True))
+
+ yield file_info
+
+
+class MediafireCom(SimpleHoster):
+ __name__ = "MediafireCom"
+ __type__ = "hoster"
+ __version__ = "0.80"
+
+ __pattern__ = r'http://(?:www\.)?mediafire\.com/(file/|(view/?|download\.php)?\?)(\w{11}|\w{15})($|/)'
+
+ __description__ = """Mediafire.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<div class="download_link"[^>]*(?:z-index:(?P<zindex>\d+))?[^>]*>\s*<a href="(?P<href>http://[^"]+)"'
+ JS_KEY_PATTERN = r'DoShow\(\'mfpromo1\'\);[^{]*{((\w+)=\'\';.*?)eval\(\2\);'
+ JS_ZMODULO_PATTERN = r'\(\'z-index\'\)\) \% (\d+)\)\);'
+ PAGE1_ACTION_PATTERN = r'<link rel="canonical" href="([^"]+)"/>'
+ PASSWORD_PATTERN = r'<form name="form_password"'
+
+ NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>[^"]+)"/>'
+ INFO_PATTERN = r'oFileSharePopup\.ald\(\'(?P<ID>[^\']*)\',\'(?P<N>[^\']*)\',\'(?P<S>[^\']*)\',\'\',\'(?P<sha256>[^\']*)\'\)'
+ OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>'
+
+
+ def setup(self):
+ self.multiDL = False
+
+
+ def process(self, pyfile):
+ pyfile.url = re.sub(r'/view/?\?', '/?', pyfile.url)
+
+ self.url, result = checkHTMLHeader(pyfile.url)
+ self.logDebug("Location (%d): %s" % (result, self.url))
+
+ if result == 0:
+ self.html = self.load(self.url, decode=True)
+ self.checkCaptcha()
+ self.multiDL = True
+ self.check_data = self.getFileInfo()
+
+ if self.account:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ elif result == 1:
+ self.offline()
+ else:
+ self.multiDL = True
+ self.download(self.url, disposition=True)
+
+
+ def handleFree(self):
+ passwords = self.getPassword().splitlines()
+ while self.PASSWORD_PATTERN in self.html:
+ if len(passwords):
+ password = passwords.pop(0)
+ self.logInfo(_("Password protected link, trying ") + password)
+ self.html = self.load(self.url, post={"downloadp": password})
+ else:
+ self.fail(_("No or incorrect password"))
+
+ m = re.search(r'kNO = r"(http://.*?)";', self.html)
+ if m is None:
+ self.error(_("No download URL"))
+
+ download_url = m.group(1)
+ self.download(download_url)
+
+
+ def checkCaptcha(self):
+ solvemedia = SolveMedia(self)
+ challenge, response = solvemedia.challenge()
+ self.html = self.load(self.url,
+ post={'adcopy_challenge': challenge,
+ 'adcopy_response' : response},
+ decode=True)
diff --git a/pyload/plugins/hoster/MegaCoNz.py b/pyload/plugins/hoster/MegaCoNz.py
new file mode 100644
index 000000000..182c0c9b9
--- /dev/null
+++ b/pyload/plugins/hoster/MegaCoNz.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from array import array
+from base64 import standard_b64decode
+from os import remove
+
+from Crypto.Cipher import AES
+from Crypto.Util import Counter
+from pycurl import SSL_CIPHER_LIST
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.internal.Hoster import Hoster
+
+############################ 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.16"
+
+ __pattern__ = r'https?://(\w+\.)?mega\.co\.nz/#!([\w!-]+)'
+
+ __description__ = """Mega.co.nz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "ranan@pyload.org")]
+
+ API_URL = "https://g.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("I", key)
+ key_array = array("I", [a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]])
+ return key_array
+
+
+ def callApi(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.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):
+ cbc = AES.new(self.getCipherKey(key), AES.MODE_CBC, "\0" * 16)
+ attr = cbc.decrypt(self.b64_decode(data))
+ self.logDebug("Decrypted Attr: " + 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 = key[16:24]
+
+ # convert counter to long and shift bytes
+ ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
+ cipher = AES.new(self.getCipherKey(key), AES.MODE_CTR, counter=ctr)
+
+ self.pyfile.setStatus("decrypting")
+
+ file_crypted = 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(str(e))
+
+ # TODO: calculate CBC-MAC for checksum
+
+ size = 2 ** 15 # buffer size, 32k
+ while True:
+ buf = f.read(size)
+ if not buf:
+ break
+
+ df.write(cipher.decrypt(buf))
+
+ f.close()
+ df.close()
+ remove(file_crypted)
+
+ self.lastDownload = file_decrypted
+
+
+ def process(self, pyfile):
+ key = None
+
+ # match is guaranteed because plugin was chosen to handle url
+ node = re.match(self.__pattern__, pyfile.url).group(2)
+ if "!" in node:
+ node, key = node.split("!")
+
+ self.logDebug("File id: %s | Key: %s" % (node, key))
+
+ if not key:
+ self.fail(_("No file key provided in the URL"))
+
+ # g is for requesting a download url
+ # this is similar to the calls in the mega js app, documentation is very bad
+ dl = self.callApi(a="g", g=1, p=node, ssl=1)[0]
+
+ if "e" in dl:
+ e = dl['e']
+ # ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
+ if e == -18:
+ self.retry()
+ else:
+ self.fail(_("Error code:") + e)
+
+ # TODO: map other error codes, e.g
+ # EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
+
+ key = self.b64_decode(key)
+ attr = self.decryptAttr(dl['at'], key)
+
+ pyfile.name = attr['n'] + self.FILE_SUFFIX
+
+ self.req.http.c.setopt(SSL_CIPHER_LIST, "RC4-MD5:DEFAULT")
+
+ self.download(dl['g'])
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = attr['n']
diff --git a/pyload/plugins/hoster/MegaDebridEu.py b/pyload/plugins/hoster/MegaDebridEu.py
new file mode 100644
index 000000000..66b12b164
--- /dev/null
+++ b/pyload/plugins/hoster/MegaDebridEu.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote_plus
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class MegaDebridEu(Hoster):
+ __name__ = "MegaDebridEu"
+ __type__ = "hoster"
+ __version__ = "0.40"
+
+ __pattern__ = r'^https?://(?:w{3}\d+\.mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[^/]+/.+$'
+
+ __description__ = """mega-debrid.eu hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def getFilename(self, url):
+ try:
+ return unquote_plus(url.rsplit("/", 1)[1])
+ except IndexError:
+ return ""
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.exitOnFail("Please enter your %s account or deactivate this plugin" % "Mega-debrid.eu")
+ else:
+ if not self.connectToApi():
+ self.exitOnFail("Unable to connect to Mega-debrid.eu")
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+ new_url = self.debridLink(pyfile.url)
+ self.logDebug("New URL: " + new_url)
+
+ filename = self.getFilename(new_url)
+ if filename != "":
+ pyfile.name = filename
+ self.download(new_url, disposition=True)
+
+
+ def connectToApi(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 debridLink(self, linkToDebrid):
+ """
+ Debrid a link
+ Return The debrided link if succeed or original link if fail
+ """
+ jsonResponse = self.load(self.API_URL, get={'action': 'getLink', 'token': self.token},
+ post={"link": linkToDebrid})
+ res = json_loads(jsonResponse)
+
+ if res['response_code'] == "ok":
+ debridedLink = res['debridLink'][1:-1]
+ return debridedLink
+ else:
+ self.exitOnFail("Unable to debrid %s" % linkToDebrid)
+
+
+ def exitOnFail(self, msg):
+ """
+ exit the plugin on fail case
+ And display the reason of this failure
+ """
+ if self.getConfig("unloadFailing"):
+ self.logError(_(msg))
+ self.resetAccount()
+ else:
+ self.fail(_(msg))
diff --git a/pyload/plugins/hoster/MegaFilesSe.py b/pyload/plugins/hoster/MegaFilesSe.py
new file mode 100644
index 000000000..4bcaa22fb
--- /dev/null
+++ b/pyload/plugins/hoster/MegaFilesSe.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """MegaFiles.se hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+getInfo = create_getInfo(MegaFilesSe)
diff --git a/pyload/plugins/hoster/MegaRapidCz.py b/pyload/plugins/hoster/MegaRapidCz.py
new file mode 100644
index 000000000..f49d0e1ec
--- /dev/null
+++ b/pyload/plugins/hoster/MegaRapidCz.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.network.RequestFactory import getRequest
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ h = getRequest()
+ h.c.setopt(HTTPHEADER,
+ ["Accept: text/html",
+ "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"])
+
+ for url in urls:
+ html = h.load(url, decode=True)
+ yield parseFileInfo(MegaRapidCz, url, html)
+
+
+class MegaRapidCz(SimpleHoster):
+ __name__ = "MegaRapidCz"
+ __type__ = "hoster"
+ __version__ = "0.54"
+
+ __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
+
+ __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'
+
+ FORCE_CHECK_TRAFFIC = True
+
+ LINK_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):
+ try:
+ self.html = self.load(self.pyfile.url, decode=True)
+ except BadHeader, e:
+ self.account.relogin(self.user)
+ self.retry(wait_time=60, reason=str(e))
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ self.logDebug("Premium link: %s" % link)
+ self.download(link, disposition=True)
+ 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/plugins/hoster/MegacrypterCom.py b/pyload/plugins/hoster/MegacrypterCom.py
new file mode 100644
index 000000000..1ebd167f3
--- /dev/null
+++ b/pyload/plugins/hoster/MegacrypterCom.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads, json_dumps
+
+from pyload.plugins.hoster.MegaCoNz import MegaCoNz
+
+
+class MegacrypterCom(MegaCoNz):
+ __name__ = "MegacrypterCom"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __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 callApi(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(1)
+
+ # get Mega.co.nz link info
+ info = self.callApi(link=node, m="info")
+
+ # get crypted file URL
+ dl = self.callApi(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/plugins/hoster/MegareleaseOrg.py b/pyload/plugins/hoster/MegareleaseOrg.py
new file mode 100644
index 000000000..7f978c94e
--- /dev/null
+++ b/pyload/plugins/hoster/MegareleaseOrg.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Megarelease.org hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("derek3x", "derek3x@vmail.me"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(MegareleaseOrg)
diff --git a/pyload/plugins/hoster/MegasharesCom.py b/pyload/plugins/hoster/MegasharesCom.py
new file mode 100644
index 000000000..45bd01ffd
--- /dev/null
+++ b/pyload/plugins/hoster/MegasharesCom.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class MegasharesCom(SimpleHoster):
+ __name__ = "MegasharesCom"
+ __type__ = "hoster"
+ __version__ = "0.27"
+
+ __pattern__ = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+'
+
+ __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):
+ self.handleDownload(True)
+
+
+ def handleFree(self):
+ 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() * 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)
+
+ download_url = m.group(1)
+ self.logDebug("%s: %s" % (msg, download_url))
+ self.download(download_url)
+
+
+getInfo = create_getInfo(MegasharesCom)
diff --git a/pyload/plugins/hoster/MegauploadCom.py b/pyload/plugins/hoster/MegauploadCom.py
new file mode 100644
index 000000000..20700a3d0
--- /dev/null
+++ b/pyload/plugins/hoster/MegauploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Megaupload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("spoob", "spoob@pyload.org")]
+
+
+getInfo = create_getInfo(MegauploadCom)
diff --git a/pyload/plugins/hoster/MegavideoCom.py b/pyload/plugins/hoster/MegavideoCom.py
new file mode 100644
index 000000000..aa458fa2c
--- /dev/null
+++ b/pyload/plugins/hoster/MegavideoCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Megavideo.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de")]
+
+
+getInfo = create_getInfo(MegavideoCom)
diff --git a/pyload/plugins/hoster/MovReelCom.py b/pyload/plugins/hoster/MovReelCom.py
new file mode 100644
index 000000000..39216a295
--- /dev/null
+++ b/pyload/plugins/hoster/MovReelCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "movreel.com"
+
+ NAME_PATTERN = r'Filename: <b>(?P<N>.+?)<'
+ SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ LINK_PATTERN = r'<a href="([^"]+)">Download Link'
+
+
+getInfo = create_getInfo(MovReelCom)
diff --git a/pyload/plugins/hoster/MultishareCz.py b/pyload/plugins/hoster/MultishareCz.py
new file mode 100644
index 000000000..28a536089
--- /dev/null
+++ b/pyload/plugins/hoster/MultishareCz.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class MultishareCz(SimpleHoster):
+ __name__ = "MultishareCz"
+ __type__ = "hoster"
+ __version__ = "0.35"
+
+ __pattern__ = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+).*'
+
+ __description__ = """MultiShare.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SIZE_REPLACEMENTS = [('&nbsp;', '')]
+
+ 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 process(self, pyfile):
+ msurl = re.match(self.__pattern__, pyfile.url)
+ if msurl:
+ self.fileID = msurl.group('ID')
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ else:
+ self.handleOverriden()
+
+
+ def handleFree(self):
+ self.download("http://www.multishare.cz/html/download_free.php?ID=%s" % self.fileID)
+
+
+ def handlePremium(self):
+ if not self.checkCredit():
+ self.logWarning(_("Not enough credit left to download file"))
+ self.resetAccount()
+
+ self.download("http://www.multishare.cz/html/download_premium.php?ID=%s" % self.fileID)
+
+
+ def handleOverriden(self):
+ if not self.premium:
+ self.fail(_("Only premium users can download from other hosters"))
+
+ self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": self.pyfile.url}, decode=True)
+ self.getFileInfo()
+
+ if not self.checkCredit():
+ self.fail(_("Not enough credit left to download file"))
+
+ url = "http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random() * 10000 * random())
+ params = {"u_ID": self.acc_info['u_ID'], "u_hash": self.acc_info['u_hash'], "link": self.pyfile.url}
+ self.logDebug(url, params)
+ self.download(url, get=params)
+
+
+ def checkCredit(self):
+ self.acc_info = self.account.getAccountInfo(self.user, True)
+ self.logInfo(_("User %s has %i MB left") % (self.user, self.acc_info['trafficleft'] / 1024))
+
+ return self.pyfile.size / 1024 <= self.acc_info['trafficleft']
+
+
+getInfo = create_getInfo(MultishareCz)
diff --git a/pyload/plugins/hoster/MyfastfileCom.py b/pyload/plugins/hoster/MyfastfileCom.py
new file mode 100644
index 000000000..cb1148389
--- /dev/null
+++ b/pyload/plugins/hoster/MyfastfileCom.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Hoster):
+ __name__ = "MyfastfileCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
+
+ __description__ = """Myfastfile.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Myfastfile.com")
+ self.fail(_("No Myfastfile.com account provided"))
+ else:
+ self.logDebug("Original URL: %s" % pyfile.url)
+ page = 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: " + page)
+ page = json_loads(page)
+ if page['status'] != 'ok':
+ self.fail(_("Unable to unrestrict link"))
+ new_url = page['link']
+
+ if new_url != pyfile.url:
+ self.logDebug("Unrestricted URL: " + new_url)
+
+ self.download(new_url, disposition=True)
diff --git a/pyload/plugins/hoster/MyvideoDe.py b/pyload/plugins/hoster/MyvideoDe.py
new file mode 100644
index 000000000..e23856f0b
--- /dev/null
+++ b/pyload/plugins/hoster/MyvideoDe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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/plugins/hoster/NahrajCz.py b/pyload/plugins/hoster/NahrajCz.py
new file mode 100644
index 000000000..188e2b1e4
--- /dev/null
+++ b/pyload/plugins/hoster/NahrajCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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/.+'
+
+ __description__ = """Nahraj.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(NahrajCz)
diff --git a/pyload/plugins/hoster/NarodRu.py b/pyload/plugins/hoster/NarodRu.py
new file mode 100644
index 000000000..67d940519
--- /dev/null
+++ b/pyload/plugins/hoster/NarodRu.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class NarodRu(SimpleHoster):
+ __name__ = "NarodRu"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
+
+ __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_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
+
+
+ def handleFree(self):
+ for _i in xrange(5):
+ self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(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(self.pyfile.url, post=post_data, decode=True)
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ url = '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"))
+
+ self.download(url)
+
+
+getInfo = create_getInfo(NarodRu)
diff --git a/pyload/plugins/hoster/NetloadIn.py b/pyload/plugins/hoster/NetloadIn.py
new file mode 100644
index 000000000..0aabc8f57
--- /dev/null
+++ b/pyload/plugins/hoster/NetloadIn.py
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+from time import sleep, time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.captcha 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(1) + ";"
+
+ 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.47"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?netload\.in/(?:datei(.*?)(?:\.htm|/)|index\.php?id=10&file_id=)'
+
+ __description__ = """Netload.in hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("spoob", "spoob@pyload.org"),
+ ("RaNaN", "ranan@pyload.org"),
+ ("Gregy", "gregy@gregy.cz")]
+
+
+ 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.download_api_data()
+
+ 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 download_api_data(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(1)
+ 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(1), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
+ "md5": "1"}, decode=True).strip()
+ if not html and n <= 3:
+ sleep(0.2)
+ self.download_api_data(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(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(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() + 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 = urljoin("http://netload.in/", url_captcha_html)
+ break
+
+ self.html = self.load(url_captcha_html)
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ challenge, response = recaptcha.challenge()
+
+ 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 is not None:
+ 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(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/plugins/hoster/NosuploadCom.py b/pyload/plugins/hoster/NosuploadCom.py
new file mode 100644
index 000000000..f70d64551
--- /dev/null
+++ b/pyload/plugins/hoster/NosuploadCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "nosupload.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, ref=True, 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, ref=True, 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/pyload/plugins/hoster/NovafileCom.py b/pyload/plugins/hoster/NovafileCom.py
new file mode 100644
index 000000000..8d0677ec8
--- /dev/null
+++ b/pyload/plugins/hoster/NovafileCom.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://novafile.com/vfun4z6o2cit
+# http://novafile.com/s6zrr5wemuz4
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "novafile.com"
+
+ SIZE_PATTERN = r'<div class="size">(?P<S>.+?)</div>'
+ ERROR_PATTERN = r'class="alert[^"]*alert-separate"[^>]*>\s*(?:<p>)?(.*?)\s*</'
+ LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
+ WAIT_PATTERN = r'<p>Please wait <span id="count"[^>]*>(\d+)</span> seconds</p>'
+
+
+getInfo = create_getInfo(NovafileCom)
diff --git a/pyload/plugins/hoster/NowDownloadSx.py b/pyload/plugins/hoster/NowDownloadSx.py
new file mode 100644
index 000000000..1574de012
--- /dev/null
+++ b/pyload/plugins/hoster/NowDownloadSx.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from pyload.utils import fixup
+
+
+class NowDownloadSx(SimpleHoster):
+ __name__ = "NowDownloadSx"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?nowdownload\.(at|ch|co|eu|sx)/(dl/|download\.php\?id=)\w+'
+
+ __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_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):
+ 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)))
+
+ url = re.search(self.LINK_PATTERN, self.html)
+ if url is None:
+ self.error(_("Download link not found"))
+
+ self.download(str(url.group(1)))
+
+
+getInfo = create_getInfo(NowDownloadSx)
diff --git a/pyload/plugins/hoster/NowVideoSx.py b/pyload/plugins/hoster/NowVideoSx.py
new file mode 100644
index 000000000..4de1bfad7
--- /dev/null
+++ b/pyload/plugins/hoster/NowVideoSx.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class NowVideoSx(SimpleHoster):
+ __name__ = "NowVideoSx"
+ __type__ = "hoster"
+ __version__ = "0.07"
+
+ __pattern__ = r'http://(?:www\.)?nowvideo\.(at|ch|co|eu|sx)/(video|mobile/#/videos)/(?P<ID>\w+)'
+
+ __description__ = """NowVideo.sx hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.nowvideo.at/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):
+ self.html = self.load("http://www.nowvideo.at/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.download(m.group(1))
+
+
+getInfo = create_getInfo(NowVideoSx)
diff --git a/pyload/plugins/hoster/OboomCom.py b/pyload/plugins/hoster/OboomCom.py
new file mode 100644
index 000000000..2f50f5f87
--- /dev/null
+++ b/pyload/plugins/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.plugins.internal.Hoster import Hoster
+from pyload.plugins.internal.captcha import ReCaptcha
+
+
+class OboomCom(Hoster):
+ __name__ = "OboomCom"
+ __type__ = "hoster"
+ __version__ = "0.30"
+
+ __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):
+ challenge, response = 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/plugins/hoster/OneFichierCom.py b/pyload/plugins/hoster/OneFichierCom.py
new file mode 100644
index 000000000..0e1016b0a
--- /dev/null
+++ b/pyload/plugins/hoster/OneFichierCom.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class OneFichierCom(SimpleHoster):
+ __name__ = "OneFichierCom"
+ __type__ = "hoster"
+ __version__ = "0.74"
+
+ __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+))?'
+
+ __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")]
+
+
+ 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+)'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def handle(self, reconnect):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1)) * 60
+
+ self.wait(wait_time, reconnect)
+ self.retry(reason="You have to wait been each free download")
+
+ 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 handleFree(self):
+ return self.handle(True)
+
+
+ def handlePremium(self):
+ return self.handle(False)
+
+
+getInfo = create_getInfo(OneFichierCom)
diff --git a/pyload/plugins/hoster/OronCom.py b/pyload/plugins/hoster/OronCom.py
new file mode 100644
index 000000000..22d6e65b3
--- /dev/null
+++ b/pyload/plugins/hoster/OronCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Oron.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("chrox", "chrox@pyload.org"),
+ ("DHMH", "DHMH@pyload.org")]
+
+
+getInfo = create_getInfo(OronCom)
diff --git a/pyload/plugins/hoster/OverLoadMe.py b/pyload/plugins/hoster/OverLoadMe.py
new file mode 100644
index 000000000..3d07db489
--- /dev/null
+++ b/pyload/plugins/hoster/OverLoadMe.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class OverLoadMe(Hoster):
+ __name__ = "OverLoadMe"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://.*overload\.me.*'
+
+ __description__ = """Over-Load.me hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("marley", "marley@over-load.me")]
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): #: incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+
+ def setup(self):
+ self.chunkLimit = 5
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Over-Load")
+ self.fail(_("No Over-Load account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ 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("Returned Data: %s" % data)
+
+ if data['error'] == 1:
+ self.logWarning(data['msg'])
+ self.tempOffline()
+ else:
+ if pyfile.name is not None and pyfile.name.endswith('.tmp') and data['filename']:
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+ new_url = data['downloadlink']
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
+ # only use when name wasn't already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload(
+ {"error": "<title>An error occured while processing your request</title>"})
+
+ if check == "error":
+ # usual this download can safely be retried
+ self.retry(wait_time=60, reason=_("An error occured while generating link."))
diff --git a/pyload/plugins/hoster/PandaplaNet.py b/pyload/plugins/hoster/PandaplaNet.py
new file mode 100644
index 000000000..f3124aaae
--- /dev/null
+++ b/pyload/plugins/hoster/PandaplaNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Pandapla.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+getInfo = create_getInfo(PandaplaNet)
diff --git a/pyload/plugins/hoster/PornhostCom.py b/pyload/plugins/hoster/PornhostCom.py
new file mode 100644
index 000000000..3f1650bf0
--- /dev/null
+++ b/pyload/plugins/hoster/PornhostCom.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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', self.html) is not None or
+ re.search(r'You will be redirected to', self.html) is not None):
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/PornhubCom.py b/pyload/plugins/hoster/PornhubCom.py
new file mode 100644
index 000000000..7cbcabf16
--- /dev/null
+++ b/pyload/plugins/hoster/PornhubCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/PotloadCom.py b/pyload/plugins/hoster/PotloadCom.py
new file mode 100644
index 000000000..19da16b33
--- /dev/null
+++ b/pyload/plugins/hoster/PotloadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Potload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(PotloadCom)
diff --git a/pyload/plugins/hoster/PremiumTo.py b/pyload/plugins/hoster/PremiumTo.py
new file mode 100644
index 000000000..04e00849a
--- /dev/null
+++ b/pyload/plugins/hoster/PremiumTo.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from os import remove
+from os.path import exists
+from urllib import quote
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import fs_encode
+
+
+class PremiumTo(Hoster):
+ __name__ = "PremiumTo"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'https?://(?:www\.)?premium\.to/.+'
+
+ __description__ = """Premium.to hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "premium.to")
+ self.fail(_("No premium.to account provided"))
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ tra = self.getTraffic()
+
+ #raise timeout to 2min
+ self.req.setOption("timeout", 120)
+
+ self.download("http://premium.to/api/getfile.php",
+ get={'username': self.account.username,
+ 'password': self.account.password,
+ 'link' : quote(pyfile.url, "")},
+ disposition=True)
+
+ check = self.checkDownload({"nopremium": "No premium account available"})
+
+ if check == "nopremium":
+ self.retry(60, 5 * 60, "No premium account available")
+
+ err = ''
+ if self.req.http.code == '420':
+ # Custom error code send - fail
+ lastDownload = fs_encode(self.lastDownload)
+
+ if exists(lastDownload):
+ with open(lastDownload, "rb") as f:
+ err = f.read(256).strip()
+ remove(lastDownload)
+ else:
+ err = _('File does not exist')
+
+ trb = self.getTraffic()
+ self.logInfo(_("Filesize: %d, Traffic used %d, traffic left %d") % (pyfile.size, tra - trb, trb))
+
+ if err:
+ self.fail(err)
+
+
+ def getTraffic(self):
+ try:
+ api_r = self.load("http://premium.to/api/straffic.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ traffic = sum(map(int, api_r.split(';')))
+ except Exception:
+ traffic = 0
+ return traffic
diff --git a/pyload/plugins/hoster/PremiumizeMe.py b/pyload/plugins/hoster/PremiumizeMe.py
new file mode 100644
index 000000000..ff56adf79
--- /dev/null
+++ b/pyload/plugins/hoster/PremiumizeMe.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class PremiumizeMe(Hoster):
+ __name__ = "PremiumizeMe"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
+
+ __description__ = """Premiumize.me hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def process(self, pyfile):
+ # Check account
+ if not self.account or not self.account.canUse():
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "premiumize.me")
+ self.fail(_("No valid premiumize.me account provided"))
+
+ # 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.download(data['result']['location'], disposition=True)
+ 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/plugins/hoster/PromptfileCom.py b/pyload/plugins/hoster/PromptfileCom.py
new file mode 100644
index 000000000..027f57505
--- /dev/null
+++ b/pyload/plugins/hoster/PromptfileCom.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class PromptfileCom(SimpleHoster):
+ __name__ = "PromptfileCom"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'https?://(?:www\.)?promptfile\.com/'
+
+ __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_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>'
+
+
+ def handleFree(self):
+ # 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(self.pyfile.url, decode=True, post={'chash': chash})
+
+ # STAGE 2: get the direct link
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ self.download(m.group(1), disposition=True)
+
+
+getInfo = create_getInfo(PromptfileCom)
diff --git a/pyload/plugins/hoster/PrzeklejPl.py b/pyload/plugins/hoster/PrzeklejPl.py
new file mode 100644
index 000000000..5e6056adb
--- /dev/null
+++ b/pyload/plugins/hoster/PrzeklejPl.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class PrzeklejPl(DeadHoster):
+ __name__ = "PrzeklejPl"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?przeklej\.pl/plik/.+'
+
+ __description__ = """Przeklej.pl hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(PrzeklejPl)
diff --git a/pyload/plugins/hoster/QuickshareCz.py b/pyload/plugins/hoster/QuickshareCz.py
new file mode 100644
index 000000000..21dc7aa11
--- /dev/null
+++ b/pyload/plugins/hoster/QuickshareCz.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class QuickshareCz(SimpleHoster):
+ __name__ = "QuickshareCz"
+ __type__ = "hoster"
+ __version__ = "0.55"
+
+ __pattern__ = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.*'
+
+ __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()
+ else:
+ self.handleFree()
+
+ check = self.checkDownload({"err": re.compile(r"\AChyba!")}, max_size=100)
+ if check == "err":
+ self.fail(_("File not m or plugin defect"))
+
+
+ def handleFree(self):
+ # 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(FOLLOWLOCATION, 0)
+ self.load(download_url, post=data)
+ self.header = self.req.http.header
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ m = re.search(r'Location\s*:\s*(.+)', self.header, re.I)
+ if m is None:
+ self.fail(_("File not found"))
+ download_url = m.group(1)
+ self.logDebug("FREE URL2:" + download_url)
+
+ # check errors
+ m = re.search(r'/chyba/(\d+)', download_url)
+ 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))
+
+ # download file
+ self.download(download_url)
+
+
+ def handlePremium(self):
+ 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/pyload/plugins/hoster/RPNetBiz.py b/pyload/plugins/hoster/RPNetBiz.py
new file mode 100644
index 000000000..9b2ff8d8c
--- /dev/null
+++ b/pyload/plugins/hoster/RPNetBiz.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Hoster):
+ __name__ = "RPNetBiz"
+ __type__ = "hoster"
+ __version__ = "0.10"
+
+ __description__ = """RPNet.biz hoster plugin"""
+ __license__ = "GPLv3"
+
+ __pattern__ = r'https?://.*rpnet\.biz'
+ __authors__ = [("Dman", "dmanugm@gmail.com")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ link_status = {'generated': pyfile.url}
+ elif not self.account:
+ # Check account
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
+ self.fail(_("No rpnet account provided"))
+ else:
+ (user, data) = self.account.selectAccount()
+
+ self.logDebug("Original URL: %s" % pyfile.url)
+ # 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.download(link_status['generated'], disposition=True)
+ 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/plugins/hoster/RapidfileshareNet.py b/pyload/plugins/hoster/RapidfileshareNet.py
new file mode 100644
index 000000000..849ac2b68
--- /dev/null
+++ b/pyload/plugins/hoster/RapidfileshareNet.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class RapidfileshareNet(XFSHoster):
+ __name__ = "RapidfileshareNet"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?rapidfileshare\.net/\w{12}'
+
+ __description__ = """Rapidfileshare.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "rapidfileshare.net"
+
+ 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.<'
+
+
+ def handlePremium(self):
+ self.fail(_("Premium download not implemented"))
+
+
+getInfo = create_getInfo(RapidfileshareNet)
diff --git a/pyload/plugins/hoster/RapidgatorNet.py b/pyload/plugins/hoster/RapidgatorNet.py
new file mode 100644
index 000000000..7ba629c4d
--- /dev/null
+++ b/pyload/plugins/hoster/RapidgatorNet.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import AdsCaptcha, ReCaptcha, SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RapidgatorNet(SimpleHoster):
+ __name__ = "RapidgatorNet"
+ __type__ = "hoster"
+ __version__ = "0.26"
+
+ __pattern__ = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
+
+ __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_ERROR_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
+ DOWNLOAD_LIMIT_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)\s*(\d+)\s*(hour|min)'
+ LINK_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.getAccountData(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):
+ #self.logDebug("ACCOUNT_DATA", self.account.getAccountData(self.user))
+ self.api_data = self.api_response('info')
+ self.api_data['md5'] = self.api_data['hash']
+ self.pyfile.name = self.api_data['filename']
+ self.pyfile.size = self.api_data['size']
+ url = self.api_response('download')['url']
+ self.download(url)
+
+
+ def handleFree(self):
+ self.checkFree()
+
+ jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
+ self.logDebug(jsvars)
+
+ self.req.http.lastURL = self.pyfile.url
+ self.req.http.c.setopt(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(int(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 = self.pyfile.url
+ self.req.http.c.setopt(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_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ self.logDebug(link)
+ self.download(link, disposition=True)
+ break
+ else:
+ captcha, captcha_key = self.getCaptcha()
+ challenge, response = captcha.challenge(captcha_key)
+
+ 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 getCaptcha(self):
+ m = re.search(self.ADSCAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = AdsCaptcha(self)
+ else:
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = ReCaptcha(self)
+ else:
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = SolveMedia(self)
+ else:
+ self.error(_("Captcha"))
+
+ return captcha, captcha_key
+
+
+ def checkFree(self):
+ m = re.search(self.PREMIUM_ONLY_ERROR_PATTERN, self.html)
+ if m:
+ self.fail(_("Premium account needed for download"))
+ else:
+ m = re.search(self.WAIT_PATTERN, self.html)
+
+ if m:
+ wait_time = int(m.group(1)) * {"hour": 60, "min": 1}[m.group(2)]
+ else:
+ m = re.search(self.DOWNLOAD_LIMIT_ERROR_PATTERN, self.html)
+ if m is None:
+ return
+ elif m.group(1) == "daily":
+ self.logWarning(_("You have reached your daily downloads limit for today"))
+ wait_time = secondsToMidnight(gmt=2)
+ else:
+ wait_time = 1 * 60 * 60
+
+ self.logDebug("Waiting %d minutes" % wait_time / 60)
+ self.wait(wait_time, True)
+ self.retry()
+
+
+ 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/pyload/plugins/hoster/RapiduNet.py b/pyload/plugins/hoster/RapiduNet.py
new file mode 100644
index 000000000..3b66d3c3f
--- /dev/null
+++ b/pyload/plugins/hoster/RapiduNet.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+from time import time, altzone
+
+from pyload.utils import json_loads
+from pyload.plugins.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RapiduNet(SimpleHoster):
+ __name__ = "RapiduNet"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})'
+
+ __description__ = """Rapidu.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("prOq", None)]
+
+
+ COOKIES = [("rapidu.net", "rapidu_lang", "en")]
+
+ FILE_INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>'
+ OFFLINE_PATTERN = r'404 - File not found'
+
+ ERROR_PATTERN = r'<div class="error">'
+
+ RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.limitDL = 0 if self.premium else 2
+
+
+ def handleFree(self):
+ self.req.http.lastURL = self.pyfile.url
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php?a=getLoadTimeToDownload", {'_go': None})
+
+ if str(jsvars['timeToDownload']) is "stop":
+ t = (24 * 60 * 60) - (int(time()) % (24 *60 * 60)) + 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()))
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(10):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php?a=getCheckCaptcha",
+ {'_go' : None,
+ 'captcha1': challenge,
+ 'captcha2': response,
+ 'fileId' : self.info['ID']})
+ if jsvars['message'] == 'success':
+ self.download(jsvars['url'])
+ break
+
+
+ def getJsonResponse(self, url, post_data):
+ res = self.load(url, post=post_data, decode=True)
+ if not res.startswith('{'):
+ self.retry()
+
+ self.logDebug(url, res)
+
+ return json_loads(res)
+
+
+getInfo = create_getInfo(RapiduNet)
diff --git a/pyload/plugins/hoster/RarefileNet.py b/pyload/plugins/hoster/RarefileNet.py
new file mode 100644
index 000000000..ce54b4dc7
--- /dev/null
+++ b/pyload/plugins/hoster/RarefileNet.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class RarefileNet(XFSHoster):
+ __name__ = "RarefileNet"
+ __type__ = "hoster"
+ __version__ = "0.08"
+
+ __pattern__ = r'http://(?:www\.)?rarefile\.net/\w{12}'
+
+ __description__ = """Rarefile.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "rarefile.net"
+
+ NAME_PATTERN = r'<font color="red">(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Size : (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ LINK_PATTERN = r'<a href="(?P<link>[^"]+)">(?P=link)</a>'
+
+
+getInfo = create_getInfo(RarefileNet)
diff --git a/pyload/plugins/hoster/RealdebridCom.py b/pyload/plugins/hoster/RealdebridCom.py
new file mode 100644
index 000000000..cc4731e3f
--- /dev/null
+++ b/pyload/plugins/hoster/RealdebridCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import quote, unquote
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class RealdebridCom(Hoster):
+ __name__ = "RealdebridCom"
+ __type__ = "hoster"
+ __version__ = "0.53"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?real-debrid\..*'
+
+ __description__ = """Real-Debrid.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if not name or name.endswith(".."): #: incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+
+ def setup(self):
+ self.chunkLimit = 3
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Real-debrid")
+ self.fail(_("No Real-debrid account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ password = self.getPassword().splitlines()
+ if not password:
+ password = ""
+ else:
+ password = password[0]
+
+ data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php",
+ get={'lang' : "en",
+ 'link' : quote(pyfile.url, ""),
+ 'password': password,
+ 'time' : int(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 is not None and pyfile.name.endswith('.tmp') and data['file_name']:
+ pyfile.name = data['file_name']
+ pyfile.size = parseFileSize(data['file_size'])
+ new_url = data['generated_links'][0][-1]
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload(
+ {"error": "<title>An error occured while processing your request</title>"})
+
+ if check == "error":
+ #usual this download can safely be retried
+ self.retry(wait_time=60, reason=_("An error occured while generating link"))
diff --git a/pyload/plugins/hoster/RedtubeCom.py b/pyload/plugins/hoster/RedtubeCom.py
new file mode 100644
index 000000000..ed143d987
--- /dev/null
+++ b/pyload/plugins/hoster/RedtubeCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/RehostTo.py b/pyload/plugins/hoster/RehostTo.py
new file mode 100644
index 000000000..99d44b1aa
--- /dev/null
+++ b/pyload/plugins/hoster/RehostTo.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from urllib import quote, unquote
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class RehostTo(Hoster):
+ __name__ = "RehostTo"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'https?://.*rehost\.to\..*'
+
+ __description__ = """Rehost.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def getFilename(self, url):
+ return unquote(url.rsplit("/", 1)[1])
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rehost.to")
+ self.fail(_("No rehost.to account provided"))
+
+ data = self.account.getAccountInfo(self.user)
+ long_ses = data['long_ses']
+
+ self.logDebug("Rehost.to: Old URL: %s" % pyfile.url)
+
+ #raise timeout to 2min
+ self.req.setOption("timeout", 120)
+
+ self.download("http://rehost.to/process_download.php",
+ get={'user': "cookie", 'pass': long_ses, 'dl': quote(pyfile.url, "")},
+ disposition=True)
diff --git a/pyload/plugins/hoster/RemixshareCom.py b/pyload/plugins/hoster/RemixshareCom.py
new file mode 100644
index 000000000..b3aaee1e8
--- /dev/null
+++ b/pyload/plugins/hoster/RemixshareCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://remixshare.com/download/p946u
+#
+# 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.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RemixshareCom(SimpleHoster):
+ __name__ = "RemixshareCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://remixshare\.com/(download|dl)/\w+'
+
+ __description__ = """Remixshare.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>[\w^_]+)\)<'
+ OFFLINE_PATTERN = r'<h1>Ooops!<'
+
+ LINK_PATTERN = r'(http://remixshare\.com/downloadfinal/.+?)"'
+ TOKEN_PATTERN = r'var acc = (\d+)'
+ WAIT_PATTERN = r'var XYZ = r"(\d+)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ b = re.search(self.LINK_PATTERN, self.html)
+ if not b:
+ self.error(_("Cannot parse download url"))
+ c = re.search(self.TOKEN_PATTERN, self.html)
+ if not c:
+ self.error(_("Cannot parse file token"))
+ dl_url = b.group(1) + c.group(1)
+
+ #Check if we have to wait
+ seconds = re.search(self.WAIT_PATTERN, self.html)
+ if seconds:
+ self.logDebug("Wait " + seconds.group(1))
+ self.wait(int(seconds.group(1)))
+
+ # Finally start downloading...
+ self.download(dl_url, disposition=True)
+
+
+getInfo = create_getInfo(RemixshareCom)
diff --git a/pyload/plugins/hoster/RgHostNet.py b/pyload/plugins/hoster/RgHostNet.py
new file mode 100644
index 000000000..353e62696
--- /dev/null
+++ b/pyload/plugins/hoster/RgHostNet.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RgHostNet(SimpleHoster):
+ __name__ = "RgHostNet"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?rghost\.net/\d+(?:r=\d+)?'
+
+ __description__ = """RgHost.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("z00nx", "z00nx0@gmail.com")]
+
+
+ INFO_PATTERN = r'<h1>\s+(<a[^>]+>)?(?P<N>[^<]+)(</a>)?\s+<small[^>]+>\s+\((?P<S>[^)]+)\)\s+</small>\s+</h1>'
+ OFFLINE_PATTERN = r'File is deleted|this page is not found'
+
+ LINK_FREE_PATTERN = r'<a\s+href="([^"]+)"\s+class="btn\s+large\s+download"[^>]+>Download</a>'
+
+
+getInfo = create_getInfo(RgHostNet)
diff --git a/pyload/plugins/hoster/RyushareCom.py b/pyload/plugins/hoster/RyushareCom.py
new file mode 100644
index 000000000..8b08c9f51
--- /dev/null
+++ b/pyload/plugins/hoster/RyushareCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://ryushare.com/cl0jy8ric2js/random.bin
+
+import re
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+from pyload.plugins.internal.captcha import SolveMedia
+
+
+class RyushareCom(XFSHoster):
+ __name__ = "RyushareCom"
+ __type__ = "hoster"
+ __version__ = "0.20"
+
+ __pattern__ = r'http://(?:www\.)?ryushare\.com/\w+'
+
+ __description__ = """Ryushare.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("quareevo", "quareevo@arcor.de")]
+
+
+ HOSTER_DOMAIN = "ryushare.com"
+
+ SIZE_PATTERN = r'You have requested <font color="red">[^<]+</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ WAIT_PATTERN = r'You have to wait ((?P<hour>\d+) hour[s]?, )?((?P<min>\d+) minute[s], )?(?P<sec>\d+) second[s]'
+ LINK_PATTERN = r'<a href="([^"]+)">Click here to download<'
+
+
+ def getDownloadLink(self):
+ retry = False
+ self.html = self.load(self.pyfile.url)
+ action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
+ if "method_premium" in inputs:
+ del inputs['method_premium']
+
+ self.html = self.load(self.pyfile.url, post=inputs)
+ action, inputs = self.parseHtmlForm('F1')
+
+ self.setWait(65)
+ # Wait 1 hour
+ if "You have reached the download-limit" in self.html:
+ self.setWait(1 * 60 * 60, True)
+ retry = True
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait = m.groupdict(0)
+ waittime = int(wait['hour']) * 60 * 60 + int(wait['min']) * 60 + int(wait['sec'])
+ self.setWait(waittime, True)
+ retry = True
+
+ self.wait()
+ if retry:
+ self.retry()
+
+ for _i in xrange(5):
+ solvemedia = SolveMedia(self)
+ challenge, response = solvemedia.challenge()
+
+ inputs['adcopy_challenge'] = challenge
+ inputs['adcopy_response'] = response
+
+ self.html = self.load(self.pyfile.url, post=inputs)
+ if "WRONG CAPTCHA" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("You have entered 5 invalid captcha codes"))
+
+ if "Click here to download" in self.html:
+ return re.search(r'<a href="([^"]+)">Click here to download</a>', self.html).group(1)
+
+
+getInfo = create_getInfo(RyushareCom)
diff --git a/pyload/plugins/hoster/SafesharingEu.py b/pyload/plugins/hoster/SafesharingEu.py
new file mode 100644
index 000000000..730bfc692
--- /dev/null
+++ b/pyload/plugins/hoster/SafesharingEu.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "safesharing.eu"
+
+ WAIT_PATTERN = r'You have to wait (\d+) minutes'
+
+ ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)'
+
+
+getInfo = create_getInfo(SafesharingEu)
diff --git a/pyload/plugins/hoster/SecureUploadEu.py b/pyload/plugins/hoster/SecureUploadEu.py
new file mode 100644
index 000000000..6939e4f6d
--- /dev/null
+++ b/pyload/plugins/hoster/SecureUploadEu.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "secureupload.eu"
+
+ INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
+
+
+getInfo = create_getInfo(SecureUploadEu)
diff --git a/pyload/plugins/hoster/SendmywayCom.py b/pyload/plugins/hoster/SendmywayCom.py
new file mode 100644
index 000000000..cb82fb19d
--- /dev/null
+++ b/pyload/plugins/hoster/SendmywayCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class SendmywayCom(XFSHoster):
+ __name__ = "SendmywayCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?sendmyway\.com/\w{12}'
+
+ __description__ = """SendMyWay hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "sendmyway.com"
+
+ NAME_PATTERN = r'<p class="file-name" ><.*?>\s*(?P<N>.+)'
+ SIZE_PATTERN = r'<small>\((?P<S>\d+) bytes\)</small>'
+
+
+getInfo = create_getInfo(SendmywayCom)
diff --git a/pyload/plugins/hoster/SendspaceCom.py b/pyload/plugins/hoster/SendspaceCom.py
new file mode 100644
index 000000000..2915d47c0
--- /dev/null
+++ b/pyload/plugins/hoster/SendspaceCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class SendspaceCom(SimpleHoster):
+ __name__ = "SendspaceCom"
+ __type__ = "hoster"
+ __version__ = "0.14"
+
+ __pattern__ = r'http://(?:www\.)?sendspace\.com/file/.*'
+
+ __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_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):
+ params = {}
+ for _i in xrange(3):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.correctCaptcha()
+ download_url = 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(self.pyfile.url, post=params)
+ else:
+ self.fail(_("Download link not found"))
+
+ self.download(download_url)
+
+
+getInfo = create_getInfo(SendspaceCom)
diff --git a/pyload/plugins/hoster/Share4webCom.py b/pyload/plugins/hoster/Share4webCom.py
new file mode 100644
index 000000000..9449ec959
--- /dev/null
+++ b/pyload/plugins/hoster/Share4webCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.UnibytesCom import UnibytesCom
+from pyload.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(UnibytesCom)
diff --git a/pyload/plugins/hoster/Share76Com.py b/pyload/plugins/hoster/Share76Com.py
new file mode 100644
index 000000000..fb6fc041f
--- /dev/null
+++ b/pyload/plugins/hoster/Share76Com.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Share76.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = []
+
+
+getInfo = create_getInfo(Share76Com)
diff --git a/pyload/plugins/hoster/ShareFilesCo.py b/pyload/plugins/hoster/ShareFilesCo.py
new file mode 100644
index 000000000..db51c2024
--- /dev/null
+++ b/pyload/plugins/hoster/ShareFilesCo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Sharefiles.co hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(ShareFilesCo)
diff --git a/pyload/plugins/hoster/SharebeesCom.py b/pyload/plugins/hoster/SharebeesCom.py
new file mode 100644
index 000000000..07f0e8bfd
--- /dev/null
+++ b/pyload/plugins/hoster/SharebeesCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """ShareBees hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(SharebeesCom)
diff --git a/pyload/plugins/hoster/ShareonlineBiz.py b/pyload/plugins/hoster/ShareonlineBiz.py
new file mode 100644
index 000000000..df89f7bed
--- /dev/null
+++ b/pyload/plugins/hoster/ShareonlineBiz.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class ShareonlineBiz(SimpleHoster):
+ __name__ = "ShareonlineBiz"
+ __type__ = "hoster"
+ __version__ = "0.44"
+
+ __pattern__ = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)'
+
+ __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>")]
+
+ RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX"
+
+ ERROR_INFO_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = {'name': urlparse(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):
+ challenge, response = 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() * 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):
+ self.html = self.load(self.pyfile.url, cookies=True) #: refer, stuff
+
+ self.wait(3)
+
+ self.html = self.load("%s/free/" % self.pyfile.url, post={"dl_free": "1", "choice": "free"}, decode=True)
+
+ self.checkErrors()
+
+ res = self.handleCaptcha()
+
+ download_url = res.decode("base64")
+
+ if not download_url.startswith("http://"):
+ self.error(_("Wrong download url"))
+
+ self.wait()
+
+ self.download(download_url)
+
+
+ def checkFile(self):
+ # check download
+ check = self.checkDownload({
+ 'empty' : re.compile(r"^$"),
+ 'cookie': re.compile(r'<div id="dl_failure"'),
+ 'fail' : re.compile(r"<title>Share-Online")
+ })
+
+ if check == "empty":
+ self.fail(_("Empty file"))
+
+ elif check == "cookie":
+ self.invalidCaptcha()
+ self.retry(5, 60, _("Cookie failure"))
+
+ elif check == "fail":
+ self.invalidCaptcha()
+ self.retry(5, 5 * 60, _("Download failed"))
+
+
+ def handlePremium(self): #: should be working better loading (account) api internally
+ self.account.getAccountInfo(self.user, True)
+
+ html = self.load("http://api.share-online.biz/account.php",
+ {"username": self.user, "password": self.account.accounts[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:
+ self.pyfile.name = dlinfo['name']
+ self.pyfile.size = int(dlinfo['size'])
+
+ dlLink = dlinfo['url']
+
+ if dlLink == "server_under_maintenance":
+ self.tempOffline()
+ else:
+ self.multiDL = True
+ self.download(dlLink)
+
+
+ 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_INFO_PATTERN, self.html).group(1))
+ except:
+ 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/pyload/plugins/hoster/ShareplaceCom.py b/pyload/plugins/hoster/ShareplaceCom.py
new file mode 100644
index 000000000..6e11de05c
--- /dev/null
+++ b/pyload/plugins/hoster/ShareplaceCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class ShareplaceCom(Hoster):
+ __name__ = "ShareplaceCom"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __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 = 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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/SharingmatrixCom.py b/pyload/plugins/hoster/SharingmatrixCom.py
new file mode 100644
index 000000000..7459c12b6
--- /dev/null
+++ b/pyload/plugins/hoster/SharingmatrixCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Sharingmatrix.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", None)]
+
+
+getInfo = create_getInfo(SharingmatrixCom)
diff --git a/pyload/plugins/hoster/ShragleCom.py b/pyload/plugins/hoster/ShragleCom.py
new file mode 100644
index 000000000..792457bbc
--- /dev/null
+++ b/pyload/plugins/hoster/ShragleCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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>.*?)/'
+
+ __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/pyload/plugins/hoster/SimplyPremiumCom.py b/pyload/plugins/hoster/SimplyPremiumCom.py
new file mode 100644
index 000000000..b0ea9f90f
--- /dev/null
+++ b/pyload/plugins/hoster/SimplyPremiumCom.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from datetime import datetime, timedelta
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+
+
+class SimplyPremiumCom(Hoster):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://.*(simply-premium)\.com'
+
+ __description__ = """Simply-Premium.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = False
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Simply-Premium.com")
+ self.fail(_("No Simply-Premium.com account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ for i in xrange(5):
+ page = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': pyfile.url})
+ self.logDebug("JSON data: " + page)
+ if page != '':
+ break
+ else:
+ self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
+ self.retry(5, 60, "Unable to get API data")
+
+ if '<valid>0</valid>' in page or (
+ "You are not allowed to download from this host" in page and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+ elif "NOTFOUND" in page:
+ self.offline()
+ elif "downloadlimit" in page:
+ self.logWarning(_("Reached maximum connctions"))
+ self.retry(5, 60, "Reached maximum connctions")
+ elif "trafficlimit" in page:
+ self.logWarning(_("Reached daily limit for this host"))
+ self.retry(wait_time=secondsToMidnight(gmt=2), "Daily limit for this host reached")
+ elif "hostererror" in page:
+ self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
+ self.retry(5, 60, "Hoster is temporarily unavailable")
+ #page = json_loads(page)
+ #new_url = page.keys()[0]
+ #self.api_data = page[new_url]
+
+ try:
+ self.pyfile.name = re.search(r'<name>([^<]+)</name>', page).group(1)
+ except AttributeError:
+ self.pyfile.name = ""
+
+ try:
+ self.pyfile.size = re.search(r'<size>(\d+)</size>', page).group(1)
+ except AttributeError:
+ self.pyfile.size = 0
+
+ try:
+ new_url = re.search(r'<download>([^<]+)</download>', page).group(1)
+ except AttributeError:
+ new_url = 'http://www.simply-premium.com/premium.php?link=' + pyfile.url
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ self.download(new_url, disposition=True)
diff --git a/pyload/plugins/hoster/SimplydebridCom.py b/pyload/plugins/hoster/SimplydebridCom.py
new file mode 100644
index 000000000..f9063d737
--- /dev/null
+++ b/pyload/plugins/hoster/SimplydebridCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class SimplydebridCom(Hoster):
+ __name__ = "SimplydebridCom"
+ __type__ = "hoster"
+ __version__ = "0.10"
+
+ __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php/*'
+
+ __description__ = """Simply-debrid.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "simply-debrid.com")
+ self.fail(_("No simply-debrid.com account provided"))
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ #fix the links for simply-debrid.com!
+ new_url = pyfile.url
+ new_url = new_url.replace("clz.to", "cloudzer.net/file")
+ new_url = new_url.replace("http://share-online", "http://www.share-online")
+ new_url = new_url.replace("ul.to", "uploaded.net/file")
+ new_url = new_url.replace("uploaded.com", "uploaded.net")
+ new_url = new_url.replace("filerio.com", "filerio.in")
+ new_url = new_url.replace("lumfile.com", "lumfile.se")
+ if('fileparadox' in new_url):
+ new_url = new_url.replace("http://", "https://")
+
+ if re.match(self.__pattern__, new_url):
+ new_url = new_url
+
+ self.logDebug("New URL: %s" % new_url)
+
+ if not re.match(self.__pattern__, new_url):
+ page = self.load("http://simply-debrid.com/api.php", get={'dl': new_url}) # +'&u='+self.user+'&p='+self.account.getAccountData(self.user)['password'])
+ if 'tiger Link' in page or 'Invalid Link' in page or ('API' in page and 'ERROR' in page):
+ self.fail(_("Unable to unrestrict link"))
+ new_url = page
+
+ self.setWait(5)
+ self.wait()
+ self.logDebug("Unrestricted URL: " + new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"bad1": "No address associated with hostname", "bad2": "<html"})
+
+ if check == "bad1" or check == "bad2":
+ self.retry(24, 3 * 60, "Bad file downloaded")
diff --git a/pyload/plugins/hoster/SockshareCom.py b/pyload/plugins/hoster/SockshareCom.py
new file mode 100644
index 000000000..d28041893
--- /dev/null
+++ b/pyload/plugins/hoster/SockshareCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)'
+
+ __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/pyload/plugins/hoster/SoundcloudCom.py b/pyload/plugins/hoster/SoundcloudCom.py
new file mode 100644
index 000000000..cc6ef83f3
--- /dev/null
+++ b/pyload/plugins/hoster/SoundcloudCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class SoundcloudCom(Hoster):
+ __name__ = "SoundcloudCom"
+ __type__ = "hoster"
+ __version__ = "0.10"
+
+ __pattern__ = r'https?://(?:www\.)?soundcloud\.com/(?P<UID>.*?)/(?P<SID>.*)'
+
+ __description__ = """SoundCloud.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Peekayy", "peekayy.dev@gmail.com")]
+
+
+ def process(self, pyfile):
+ # default UserAgent of HTTPRequest fails for this hoster so we use this one
+ self.req.http.c.setopt(pycurl.USERAGENT, 'Mozilla/5.0')
+ page = self.load(pyfile.url)
+ m = re.search(r'<div class="haudio.*?large.*?" data-sc-track="(?P<ID>\d*)"', page)
+ songId = clientId = ""
+ if m:
+ songId = m.group("ID")
+ if len(songId) <= 0:
+ self.logError(_("Could not find song id"))
+ self.offline()
+ else:
+ m = re.search(r'"clientID":"(?P<CID>.*?)"', page)
+ if m:
+ clientId = m.group("CID")
+
+ if len(clientId) <= 0:
+ clientId = "b45b1aa10f1ac2941910a7f0d10f8e28"
+
+ m = re.search(r'<em itemprop="name">\s(?P<TITLE>.*?)\s</em>', page)
+ if m:
+ pyfile.name = m.group("TITLE") + ".mp3"
+ else:
+ pyfile.name = re.match(self.__pattern__, pyfile.url).group("SID") + ".mp3"
+
+ # url to retrieve the actual song url
+ page = self.load("https://api.sndcdn.com/i1/tracks/%s/streams" % songId, get={"client_id": clientId})
+ # getting streams
+ # for now we choose the first stream found in all cases
+ # it could be improved if relevant for this hoster
+ streams = [
+ (result.group("QUALITY"), result.group("URL"))
+ for result in re.finditer(r'"(?P<QUALITY>.*?)":"(?P<URL>.*?)"', page)
+ ]
+ self.logDebug("Found Streams", streams)
+ self.logDebug("Downloading", streams[0][0], streams[0][1])
+ self.download(streams[0][1])
diff --git a/pyload/plugins/hoster/SpeedLoadOrg.py b/pyload/plugins/hoster/SpeedLoadOrg.py
new file mode 100644
index 000000000..35bd84803
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedLoadOrg.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)'
+
+ __description__ = """Speedload.org hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/pyload/plugins/hoster/SpeedfileCz.py b/pyload/plugins/hoster/SpeedfileCz.py
new file mode 100644
index 000000000..549a146d4
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedfileCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class SpeedfileCz(DeadHoster):
+ __name__ = "SpeedFileCz"
+ __type__ = "hoster"
+ __version__ = "0.32"
+
+ __pattern__ = r'http://(?:www\.)?speedfile\.cz/.*'
+
+ __description__ = """Speedfile.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(SpeedfileCz)
diff --git a/pyload/plugins/hoster/SpeedyshareCom.py b/pyload/plugins/hoster/SpeedyshareCom.py
new file mode 100644
index 000000000..5128e42e7
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedyshareCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class SpeedyshareCom(SimpleHoster):
+ __name__ = "SpeedyshareCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+'
+
+ __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_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):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ dl_link = urljoin("http://www.speedyshare.com", m.group(1))
+ self.download(dl_link, disposition=True)
+
+ check = self.checkDownload({'html': re.compile("html")})
+ if check == "html":
+ self.error(_("Downloaded file is an html page"))
+
+
+getInfo = create_getInfo(SpeedyshareCom)
diff --git a/pyload/plugins/hoster/StorageTo.py b/pyload/plugins/hoster/StorageTo.py
new file mode 100644
index 000000000..5ba0c145b
--- /dev/null
+++ b/pyload/plugins/hoster/StorageTo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class StorageTo(DeadHoster):
+ __name__ = "StorageTo"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?storage\.to/get/.+'
+
+ __description__ = """Storage.to hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("mkaay", "mkaay@mkaay.de")]
+
+
+getInfo = create_getInfo(StorageTo)
diff --git a/pyload/plugins/hoster/StreamCz.py b/pyload/plugins/hoster/StreamCz.py
new file mode 100644
index 000000000..e999e4c25
--- /dev/null
+++ b/pyload/plugins/hoster/StreamCz.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.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/plugins/hoster/StreamcloudEu.py b/pyload/plugins/hoster/StreamcloudEu.py
new file mode 100644
index 000000000..5ae617095
--- /dev/null
+++ b/pyload/plugins/hoster/StreamcloudEu.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class StreamcloudEu(XFSHoster):
+ __name__ = "StreamcloudEu"
+ __type__ = "hoster"
+ __version__ = "0.09"
+
+ __pattern__ = r'http://(?:www\.)?streamcloud\.eu/\w{12}'
+
+ __description__ = """Streamcloud.eu hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("seoester", "seoester@googlemail.com")]
+
+
+ HOSTER_DOMAIN = "streamcloud.eu"
+
+ 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/pyload/plugins/hoster/TurbobitNet.py b/pyload/plugins/hoster/TurbobitNet.py
new file mode 100644
index 000000000..adb9d21a7
--- /dev/null
+++ b/pyload/plugins/hoster/TurbobitNet.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+import time
+
+from Crypto.Cipher import ARC4
+from binascii import hexlify, unhexlify
+from pycurl import HTTPHEADER
+from urllib import quote
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
+
+
+class TurbobitNet(SimpleHoster):
+ __name__ = "TurbobitNet"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)'
+
+ __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_PATTERN = r'(?P<url>/download/redirect/[^"\']+)'
+ LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<'
+
+ CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"'
+
+
+ def handleFree(self):
+ self.url = "http://turbobit.net/download/free/%s" % self.info['pattern']['ID']
+ self.html = self.load(self.url, ref=True, decode=True)
+
+ rtUpdate = self.getRtUpdate()
+
+ self.solveCaptcha()
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.url = self.getDownloadUrl(rtUpdate)
+
+ self.wait()
+ self.html = self.load(self.url)
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
+ self.downloadFile()
+
+
+ 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_challenge_field'], inputs['recaptcha_response_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, 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")
+ self.retry()
+
+
+ def decrypt(self, data):
+ cipher = ARC4.new(hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
+ return unhexlify(cipher.encrypt(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)
+
+
+ def handlePremium(self):
+ self.logDebug("Premium download as user %s" % self.user)
+ self.downloadFile()
+
+
+ def downloadFile(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+ self.url = "http://turbobit.net" + m.group('url')
+ self.download(self.url)
+
+
+getInfo = create_getInfo(TurbobitNet)
diff --git a/pyload/plugins/hoster/TurbouploadCom.py b/pyload/plugins/hoster/TurbouploadCom.py
new file mode 100644
index 000000000..80ad6ef1f
--- /dev/null
+++ b/pyload/plugins/hoster/TurbouploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class TurbouploadCom(DeadHoster):
+ __name__ = "TurbouploadCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?turboupload\.com/(\w+).*'
+
+ __description__ = """Turboupload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(TurbouploadCom)
diff --git a/pyload/plugins/hoster/TusfilesNet.py b/pyload/plugins/hoster/TusfilesNet.py
new file mode 100644
index 000000000..3b4d7f92c
--- /dev/null
+++ b/pyload/plugins/hoster/TusfilesNet.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class TusfilesNet(XFSHoster):
+ __name__ = "TusfilesNet"
+ __type__ = "hoster"
+ __version__ = "0.07"
+
+ __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")]
+
+
+ HOSTER_DOMAIN = "tusfiles.net"
+
+ INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\['
+ OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!'
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def handlePremium(self):
+ return self.handleFree()
+
+
+getInfo = create_getInfo(TusfilesNet)
diff --git a/pyload/plugins/hoster/TwoSharedCom.py b/pyload/plugins/hoster/TwoSharedCom.py
new file mode 100644
index 000000000..ee50c8712
--- /dev/null
+++ b/pyload/plugins/hoster/TwoSharedCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class TwoSharedCom(SimpleHoster):
+ __name__ = "TwoSharedCom"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.*'
+
+ __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_PATTERN = r'window.location =\'(.+?)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link"))
+
+ link = m.group(1)
+ self.download(link)
+
+
+getInfo = create_getInfo(TwoSharedCom)
diff --git a/pyload/plugins/hoster/UlozTo.py b/pyload/plugins/hoster/UlozTo.py
new file mode 100644
index 000000000..b331dd4f1
--- /dev/null
+++ b/pyload/plugins/hoster/UlozTo.py
@@ -0,0 +1,164 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.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.00"
+
+ __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<id>\w+/[^/?]*)'
+
+ __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>'
+
+ SIZE_REPLACEMENTS = [('([\d.]+)\s([kMG])B', convertDecimalPrefix)]
+ URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "www.ulozto.net")]
+
+ ADULT_PATTERN = r'<form action="(?P<link>[^\"]*)" method="post" id="frm-askAgeForm">'
+ PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
+ VIPLINK_PATTERN = r'<a href="[^"]*\?disclaimer=1" class="linkVip">'
+ FREE_URL_PATTERN = r'<div class="freeDownloadForm"><form action="([^"]+)"'
+ PREMIUM_URL_PATTERN = r'<div class="downloadForm"><form action="([^"]+)"'
+ TOKEN_PATTERN = r'<input type="hidden" name="_token_" id="[^\"]*" value="(?P<token>.+?)"'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ pyfile.url = re.sub(r"(?<=http://)([^/]+)", "www.ulozto.net", pyfile.url)
+ self.html = self.load(pyfile.url, decode=True, cookies=True)
+
+ 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"))
+ token = m.group(1)
+
+ self.html = self.load(pyfile.url, get={"do": "askAgeForm-submit"},
+ post={"agree": "Confirm", "_token_": token}, cookies=True)
+
+ passwords = self.getPassword().splitlines()
+ while self.PASSWD_PATTERN in self.html:
+ if passwords:
+ password = passwords.pop(0)
+ self.logInfo(_("Password protected link, trying ") + password)
+ self.html = self.load(pyfile.url, get={"do": "passwordProtectedForm-submit"},
+ post={"password": password, "password_send": 'Send'}, cookies=True)
+ else:
+ self.fail(_("No or incorrect password"))
+
+ if re.search(self.VIPLINK_PATTERN, self.html):
+ self.html = self.load(pyfile.url, get={"disclaimer": "1"})
+
+ self.getFileInfo()
+
+ if self.premium and self.checkTrafficLeft():
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ self.doCheckDownload()
+
+
+ def handleFree(self):
+ 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.multiDL = True
+ self.download("http://www.ulozto.net" + action, post=inputs, cookies=True, disposition=True)
+
+
+ def handlePremium(self):
+ self.download(self.pyfile.url + "?do=directDownload", disposition=True)
+ #parsed_url = self.findDownloadURL(premium=True)
+ #self.download(parsed_url, post={"download": "Download"})
+
+
+ def findDownloadURL(self, premium=False):
+ msg = _("%s link" % ("Premium" if premium else "Free"))
+ m = re.search(self.PREMIUM_URL_PATTERN if premium else self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.error(msg)
+ parsed_url = "http://www.ulozto.net" + m.group(1)
+ self.logDebug("%s: %s" % (msg, parsed_url))
+ return parsed_url
+
+
+ def doCheckDownload(self):
+ 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.delStorage("captcha_id")
+ #self.delStorage("captcha_text")
+ 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"))
+
+
+getInfo = create_getInfo(UlozTo)
diff --git a/pyload/plugins/hoster/UloziskoSk.py b/pyload/plugins/hoster/UloziskoSk.py
new file mode 100644
index 000000000..e34f74a3f
--- /dev/null
+++ b/pyload/plugins/hoster/UloziskoSk.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UloziskoSk(SimpleHoster):
+ __name__ = "UloziskoSk"
+ __type__ = "hoster"
+ __version__ = "0.24"
+
+ __pattern__ = r'http://(?:www\.)?ulozisko\.sk/.*'
+
+ __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_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:
+ url = "http://ulozisko.sk" + m.group(1)
+ self.download(url)
+ else:
+ self.handleFree()
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_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": self.pyfile.name,
+ "but": "++++STIAHNI+S%DABOR++++"
+ })
+
+
+getInfo = create_getInfo(UloziskoSk)
diff --git a/pyload/plugins/hoster/UnibytesCom.py b/pyload/plugins/hoster/UnibytesCom.py
new file mode 100644
index 000000000..6ed3805b1
--- /dev/null
+++ b/pyload/plugins/hoster/UnibytesCom.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pycurl import FOLLOWLOCATION
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UnibytesCom(SimpleHoster):
+ __name__ = "UnibytesCom"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B'
+
+ __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_PATTERN = r'<a href="([^"]+)">Download</a>'
+
+
+ def handleFree(self):
+ domain = "http://www.%s/" % self.HOSTER_DOMAIN
+ action, post_data = self.parseHtmlForm('id="startForm"')
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+
+ for _i in xrange(8):
+ self.logDebug(action, post_data)
+ self.html = self.load(urljoin(domain, action), post=post_data)
+
+ m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
+ if m:
+ url = 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_PATTERN, self.html)
+ if m:
+ url = 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(int(m.group(1)) if m else 60, False)
+ elif last_step in ("captcha", "last"):
+ post_data['captcha'] = self.decryptCaptcha(urljoin(domain, "/captcha.jpg"))
+ else:
+ self.fail(_("No valid captcha code entered"))
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+ self.download(url)
+
+
+getInfo = create_getInfo(UnibytesCom)
diff --git a/pyload/plugins/hoster/UnrestrictLi.py b/pyload/plugins/hoster/UnrestrictLi.py
new file mode 100644
index 000000000..998c097fd
--- /dev/null
+++ b/pyload/plugins/hoster/UnrestrictLi.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from datetime import datetime, timedelta
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.Hoster import Hoster
+
+
+def secondsToMidnight(gmt=0):
+ now = datetime.utcnow() + timedelta(hours=gmt)
+ if now.hour is 0 and now.minute < 10:
+ midnight = now
+ else:
+ midnight = now + timedelta(days=1)
+ midnight = midnight.replace(hour=0, minute=10, second=0, microsecond=0)
+ return int((midnight - now).total_seconds())
+
+
+class UnrestrictLi(Hoster):
+ __name__ = "UnrestrictLi"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?(unrestrict|unr)\.li'
+
+ __description__ = """Unrestrict.li hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = True
+
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Unrestrict.li")
+ self.fail(_("No Unrestrict.li account provided"))
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ for _i in xrange(5):
+ page = self.load('https://unrestrict.li/unrestrict.php',
+ post={'link': pyfile.url, 'domain': 'long'})
+ self.logDebug("JSON data: " + page)
+ if page != '':
+ 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 page or ("You are not allowed to "
+ "download from this host" in page and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+ elif "File offline" in page:
+ self.offline()
+ elif "You are not allowed to download from this host" in page:
+ self.fail(_("You are not allowed to download from this host"))
+ elif "You have reached your daily limit for this host" in page:
+ 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 page:
+ self.logInfo(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
+ self.retry(5, 60, "Hoster is temporarily unavailable")
+ page = json_loads(page)
+ new_url = page.keys()[0]
+ self.api_data = page[new_url]
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ if hasattr(self, 'api_data'):
+ self.setNameSize()
+
+ self.download(new_url, disposition=True)
+
+ 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/plugins/hoster/UpleaCom.py b/pyload/plugins/hoster/UpleaCom.py
new file mode 100644
index 000000000..ca639a954
--- /dev/null
+++ b/pyload/plugins/hoster/UpleaCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class UpleaCom(XFSHoster):
+ __name__ = "UpleaCom"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'https?://(?:www\.)?uplea\.com/dl/\w{15}'
+
+ __description__ = """Uplea.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Redleon", None)]
+
+
+ HOSTER_DOMAIN = "uplea.com"
+
+ NAME_PATTERN = r'class="agmd size18">(?P<N>.+?)<'
+ SIZE_PATTERN = r'size14">(?P<S>[\d.,]+) (?P<U>[\w^_])</span>'
+
+ OFFLINE_PATTERN = r'>You followed an invalid or expired link'
+
+ LINK_PATTERN = r'"(http?://\w+\.uplea\.com/anonym/.*?)"'
+ 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):
+ m = re.search(self.STEP_PATTERN, self.html)
+ if m is None:
+ self.error("STEP_PATTERN not found")
+
+ self.html = self.load(urljoin("http://uplea.com/", m.group(1)))
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(int(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.wait(15)
+ self.download(m.group(1), disposition=True)
+
+
+getInfo = create_getInfo(UpleaCom)
diff --git a/pyload/plugins/hoster/UploadStationCom.py b/pyload/plugins/hoster/UploadStationCom.py
new file mode 100644
index 000000000..ad6779364
--- /dev/null
+++ b/pyload/plugins/hoster/UploadStationCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)'
+
+ __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/pyload/plugins/hoster/UploadableCh.py b/pyload/plugins/hoster/UploadableCh.py
new file mode 100644
index 000000000..0fafcb6e8
--- /dev/null
+++ b/pyload/plugins/hoster/UploadableCh.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import sleep
+
+from pyload.plugins.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UploadableCh(SimpleHoster):
+ __name__ = "UploadableCh"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)'
+
+ __description__ = """Uploadable.ch hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ FILE_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'data-time="(\d+)" data-format'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.uploadable.ch/file/\g<ID>')]
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ # Click the "free user" button and wait
+ a = self.load(self.pyfile.url, cookies=True, post={'downloadLink': "wait"}, decode=True)
+ self.logDebug(a)
+
+ m = re.search(self.WAIT_PATTERN, a)
+ if m is not None:
+ self.wait(int(m.group(1))) #: Expected output: {"waitTime":30}
+ else:
+ self.error("WAIT_PATTERN")
+
+ # Make the recaptcha appear and show it the pyload interface
+ b = self.load(self.pyfile.url, cookies=True, post={'checkDownload': "check"}, decode=True)
+ self.logDebug(b) #: Expected output: {"success":"showCaptcha"}
+
+ recaptcha = ReCaptcha(self)
+
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ # Submit the captcha solution
+ self.load("http://www.uploadable.ch/checkReCaptcha.php",
+ cookies=True,
+ post={'recaptcha_challenge_field' : challenge,
+ 'recaptcha_response_field' : response,
+ 'recaptcha_shortencode_field': self.info['ID']},
+ decode=True)
+
+ self.wait(3)
+
+ # Get ready for downloading
+ self.load(self.pyfile.url, cookies=True, post={'downloadLink': "show"}, decode=True)
+
+ self.wait(3)
+
+ # Download the file
+ self.download(self.pyfile.url, cookies=True, post={'download': "normal"}, disposition=True)
+
+
+ def checkFile(self):
+ check = self.checkDownload({'wait_or_reconnect': re.compile("Please wait for"),
+ 'is_html' : re.compile("<head>")})
+
+ if check == "wait_or_reconnect":
+ self.logInfo("Downloadlimit reached, please wait or reconnect")
+ self.wait(60 * 60, True)
+ self.retry()
+
+ elif check == "is_html":
+ self.error("Downloaded file is an html file")
+
+
+getInfo = create_getInfo(UploadableCh)
diff --git a/pyload/plugins/hoster/UploadboxCom.py b/pyload/plugins/hoster/UploadboxCom.py
new file mode 100644
index 000000000..89a525c67
--- /dev/null
+++ b/pyload/plugins/hoster/UploadboxCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class UploadboxCom(DeadHoster):
+ __name__ = "Uploadbox"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?uploadbox\.com/files/.+'
+
+ __description__ = """UploadBox.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(UploadboxCom)
diff --git a/pyload/plugins/hoster/UploadedTo.py b/pyload/plugins/hoster/UploadedTo.py
new file mode 100644
index 000000000..e8dea32c2
--- /dev/null
+++ b/pyload/plugins/hoster/UploadedTo.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://ul.to/044yug9o
+# http://ul.to/gzfhd0xs
+
+import re
+
+from time import sleep
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.utils import html_unescape, parseFileSize
+
+
+key = "bGhGMkllZXByd2VEZnU5Y2NXbHhYVlZ5cEE1bkEzRUw=".decode('base64')
+
+
+def getID(url):
+ """ returns id from file url"""
+ m = re.match(UploadedTo.__pattern__, url)
+ return m.group('ID')
+
+
+def getAPIData(urls):
+ post = {"apikey": key}
+
+ idMap = {}
+
+ for i, url in enumerate(urls):
+ id = getID(url)
+ post['id_%s' % i] = id
+ idMap[id] = url
+
+ for _i in xrange(5):
+ api = unicode(getURL("http://uploaded.net/api/filemultiple", post=post, decode=False), 'iso-8859-1')
+ if api != "can't find request":
+ break
+ else:
+ sleep(3)
+
+ result = {}
+
+ if api:
+ for line in api.splitlines():
+ data = line.split(",", 4)
+ if data[1] in idMap:
+ result[data[1]] = (data[0], data[2], data[4], data[3], idMap[data[1]])
+
+ return result
+
+
+def parseFileInfo(self, url='', html=''):
+ if not html and hasattr(self, "html"):
+ html = self.html
+
+ name = url
+ size = 0
+ fileid = None
+
+ if re.search(self.OFFLINE_PATTERN, html):
+ # File offline
+ status = 1
+ else:
+ m = re.search(self.INFO_PATTERN, html)
+ if m:
+ name, fileid = html_unescape(m.group('N')), m.group('ID')
+ size = parseFileSize(m.group('S'))
+ status = 2
+ else:
+ status = 3
+
+ return name, size, status, fileid
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 80):
+ result = []
+
+ api = getAPIData(chunk)
+
+ for data in api.itervalues():
+ if data[0] == "online":
+ result.append((html_unescape(data[2]), data[1], 2, data[4]))
+
+ elif data[0] == "offline":
+ result.append((data[4], 0, 1, data[4]))
+
+ yield result
+
+
+class UploadedTo(Hoster):
+ __name__ = "UploadedTo"
+ __type__ = "hoster"
+ __version__ = "0.75"
+
+ __pattern__ = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
+
+ __description__ = """Uploaded.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("netpok", "netpok@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = r'<a href="file/(?P<ID>\w+)" id="filename">(?P<N>[^<]+)</a> &nbsp;\s*<small[^>]*>(?P<S>[^<]+)</small>'
+ OFFLINE_PATTERN = r'<small class="cL">Error: 404</small>'
+ DL_LIMIT_PATTERN = r'You have reached the max. number of possible free downloads for this hour'
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.chunkLimit = 1 # critical problems with more chunks
+
+ self.fileID = getID(self.pyfile.url)
+ self.pyfile.url = "http://uploaded.net/file/%s" % self.fileID
+
+
+ def process(self, pyfile):
+ self.load("http://uploaded.net/language/en", just_header=True)
+
+ api = getAPIData([pyfile.url])
+
+ # TODO: fallback to parse from site, because api sometimes delivers wrong status codes
+
+ if not api:
+ self.logWarning(_("No response for API call"))
+
+ self.html = unicode(self.load(pyfile.url, decode=False), 'iso-8859-1')
+ name, size, status, self.fileID = parseFileInfo(self)
+ self.logDebug(name, size, status, self.fileID)
+ if status == 1:
+ self.offline()
+ elif status == 2:
+ pyfile.name, pyfile.size = name, size
+ else:
+ self.error(_("file info"))
+
+ elif api == 'Access denied':
+ self.fail(_("API key invalid"))
+
+ else:
+ if self.fileID not in api:
+ self.offline()
+
+ self.data = api[self.fileID]
+ if self.data[0] != "online":
+ self.offline()
+
+ pyfile.name = html_unescape(self.data[2])
+
+ # pyfile.name = self.get_file_name()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+
+ def handlePremium(self):
+ info = self.account.getAccountInfo(self.user, True)
+ self.logDebug("%(name)s: Use Premium Account (%(left)sGB left)" % {"name": self.__name__,
+ "left": info['trafficleft'] / 1024 / 1024})
+ if int(self.data[1]) / 1024 > info['trafficleft']:
+ self.logInfo(_("Not enough traffic left"))
+ self.account.empty(self.user)
+ self.resetAccount()
+ self.fail(_("Traffic exceeded"))
+
+ header = self.load("http://uploaded.net/file/%s" % self.fileID, just_header=True)
+ if 'location' in header:
+ #Direct download
+ self.logDebug("Direct download link detected")
+ self.download(header['location'])
+ else:
+ #Indirect download
+ self.html = self.load("http://uploaded.net/file/%s" % self.fileID)
+ m = re.search(r'<div class="tfree".*\s*<form method="post" action="(.*?)"', self.html)
+ if m is None:
+ self.fail(_("Download URL not m. Try to enable direct downloads"))
+ url = m.group(1)
+ self.download(url, post={})
+
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ if 'var free_enabled = false;' in self.html:
+ self.logError(_("Free-download capacities exhausted"))
+ self.retry(24, 5 * 60)
+
+ m = re.search(r"Current waiting period: <span>(\d+)</span> seconds", self.html)
+ if m is None:
+ self.fail(_("File not downloadable for free users"))
+ self.setWait(int(m.group(1)))
+
+ self.html = self.load("http://uploaded.net/js/download.js", decode=True)
+
+ url = "http://uploaded.net/io/ticket/captcha/%s" % self.fileID
+ downloadURL = ""
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ challenge, response = recaptcha.challenge()
+ options = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": response}
+ self.wait()
+
+ result = self.load(url, post=options)
+ self.logDebug("Result: %s" % result)
+
+ if "limit-size" in result:
+ self.fail(_("File too big for free download"))
+ elif "limit-slot" in result: # Temporary restriction so just wait a bit
+ self.setWait(30 * 60, True)
+ self.wait()
+ self.retry()
+ elif "limit-parallel" in result:
+ self.fail(_("Cannot download in parallel"))
+ elif "limit-dl" in result or self.DL_LIMIT_PATTERN in result: # limit-dl
+ self.setWait(3 * 60 * 60, True)
+ self.wait()
+ self.retry()
+ elif '"err":"captcha"' in result:
+ self.invalidCaptcha()
+ elif "type:'download'" in result:
+ self.correctCaptcha()
+ downloadURL = re.search("url:'([^']+)", result).group(1)
+ break
+ else:
+ self.error(_("Unknown error: %s") % result)
+
+ if not downloadURL:
+ self.fail(_("No Download url retrieved/all captcha attempts failed"))
+
+ self.download(downloadURL, disposition=True)
+ check = self.checkDownload({"limit-dl": self.DL_LIMIT_PATTERN})
+ if check == "limit-dl":
+ self.setWait(3 * 60 * 60, True)
+ self.wait()
+ self.retry()
diff --git a/pyload/plugins/hoster/UploadhereCom.py b/pyload/plugins/hoster/UploadhereCom.py
new file mode 100644
index 000000000..ba02ea07c
--- /dev/null
+++ b/pyload/plugins/hoster/UploadhereCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """Uploadhere.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(UploadhereCom)
diff --git a/pyload/plugins/hoster/UploadheroCom.py b/pyload/plugins/hoster/UploadheroCom.py
new file mode 100644
index 000000000..857cf066d
--- /dev/null
+++ b/pyload/plugins/hoster/UploadheroCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://uploadhero.co/dl/wQBRAVSM
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UploadheroCom(SimpleHoster):
+ __name__ = "UploadheroCom"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
+
+ __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>.*?)</div>'
+ SIZE_PATTERN = r'Taille du fichier : </span><strong>(?P<S>.*?)</strong>'
+ OFFLINE_PATTERN = r'<p class="titre_dl_2">|<div class="raison"><strong>Le lien du fichier ci-dessus n\'existe plus.'
+
+ 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+)"'
+ FREE_URL_PATTERN = r'var magicomfg = \'<a href="(http://[^<>"]*?)"|"(http://storage\d+\.uploadhero\.co/\?d=\w+/[^<>"/]+)"'
+ PREMIUM_URL_PATTERN = r'<a href="([^"]+)" id="downloadnow"'
+
+
+ def handleFree(self):
+ self.checkErrors()
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("CAPTCHA_PATTERN not found"))
+ captcha_url = "http://uploadhero.co" + m.group(1)
+
+ for _i in xrange(5):
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(self.pyfile.url, get={"code": captcha})
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m:
+ self.correctCaptcha()
+ download_url = m.group(1) or m.group(2)
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("No valid captcha code entered"))
+
+ self.download(download_url)
+
+
+ def handlePremium(self):
+ self.logDebug("%s: Use Premium Account" % self.__name__)
+ link = re.search(self.PREMIUM_URL_PATTERN, self.html).group(1)
+ self.download(link)
+
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.html = self.load("http://uploadhero.co%s" % 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()
+
+ self.info.pop('error', None)
+
+
+getInfo = create_getInfo(UploadheroCom)
diff --git a/pyload/plugins/hoster/UploadingCom.py b/pyload/plugins/hoster/UploadingCom.py
new file mode 100644
index 000000000..bc409e1cb
--- /dev/null
+++ b/pyload/plugins/hoster/UploadingCom.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
+
+
+class UploadingCom(SimpleHoster):
+ __name__ = "UploadingCom"
+ __type__ = "hoster"
+ __version__ = "0.39"
+
+ __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()
+ else:
+ self.handleFree()
+
+
+ def handlePremium(self):
+ 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:
+ url = url.group(1).replace("\\/", "/")
+ self.download(url)
+
+ raise Exception("Plugin defect")
+
+
+ def handleFree(self):
+ m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
+ if m:
+ self.pyfile.error = m.group(1)
+ self.logWarning(self.pyfile.error)
+ self.retry(6, (6 * 60 if m.group(2) else 15) * 60, self.pyfile.error)
+
+ ajax_url = "http://uploading.com/files/get/?ajax"
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = self.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.download(url)
+
+ check = self.checkDownload({"html": re.compile("\A<!DOCTYPE html PUBLIC")})
+ if check == "html":
+ self.logWarning(_("Redirected to a HTML page, wait 10 minutes and retry"))
+ self.wait(10 * 60, True)
+
+
+getInfo = create_getInfo(UploadingCom)
diff --git a/pyload/plugins/hoster/UploadkingCom.py b/pyload/plugins/hoster/UploadkingCom.py
new file mode 100644
index 000000000..e9c536819
--- /dev/null
+++ b/pyload/plugins/hoster/UploadkingCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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}'
+
+ __description__ = """UploadKing.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+getInfo = create_getInfo(UploadkingCom)
diff --git a/pyload/plugins/hoster/UpstoreNet.py b/pyload/plugins/hoster/UpstoreNet.py
new file mode 100644
index 000000000..f7083f820
--- /dev/null
+++ b/pyload/plugins/hoster/UpstoreNet.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.captcha import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UpstoreNet(SimpleHoster):
+ __name__ = "UpstoreNet"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?upstore\.net/'
+
+ __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_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
+
+
+ def handleFree(self):
+ # 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(self.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
+ challenge, response = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(self.pyfile.url, post=post_data, decode=True)
+
+ # STAGE 3: get direct link
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ if m is None:
+ self.error(_("Download link not found"))
+
+ direct = m.group(1)
+ self.download(direct, disposition=True)
+
+
+getInfo = create_getInfo(UpstoreNet)
diff --git a/pyload/plugins/hoster/UptoboxCom.py b/pyload/plugins/hoster/UptoboxCom.py
new file mode 100644
index 000000000..ad14307c2
--- /dev/null
+++ b/pyload/plugins/hoster/UptoboxCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class UptoboxCom(XFSHoster):
+ __name__ = "UptoboxCom"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'https?://(?:www\.)?uptobox\.com/\w{12}'
+
+ __description__ = """Uptobox.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "uptobox.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)'
+
+ 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/pyload/plugins/hoster/VeehdCom.py b/pyload/plugins/hoster/VeehdCom.py
new file mode 100644
index 000000000..326d0a18e
--- /dev/null
+++ b/pyload/plugins/hoster/VeehdCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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/plugins/hoster/VeohCom.py b/pyload/plugins/hoster/VeohCom.py
new file mode 100644
index 000000000..b2f8c69ed
--- /dev/null
+++ b/pyload/plugins/hoster/VeohCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class VeohCom(SimpleHoster):
+ __name__ = "VeohCom"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
+ __config__ = [("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):
+ 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:
+ self.pyfile.name += ".mp4"
+ link = m.group(1).replace("\\", "")
+ self.download(link)
+ return
+ else:
+ self.logInfo(_("No %s quality video found") % q.upper())
+ else:
+ self.fail(_("No video found!"))
+
+
+getInfo = create_getInfo(VeohCom)
diff --git a/pyload/plugins/hoster/VidPlayNet.py b/pyload/plugins/hoster/VidPlayNet.py
new file mode 100644
index 000000000..3a5198973
--- /dev/null
+++ b/pyload/plugins/hoster/VidPlayNet.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://vidplay.net/38lkev0h3jv0
+
+from pyload.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")]
+
+
+ HOSTER_DOMAIN = "vidplay.net"
+
+ NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
+
+
+getInfo = create_getInfo(VidPlayNet)
diff --git a/pyload/plugins/hoster/VimeoCom.py b/pyload/plugins/hoster/VimeoCom.py
new file mode 100644
index 000000000..8b4d5bafb
--- /dev/null
+++ b/pyload/plugins/hoster/VimeoCom.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class VimeoCom(SimpleHoster):
+ __name__ = "VimeoCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
+ __config__ = [("quality", "Lowest;Mobile;SD;HD;Highest", "Quality", "Highest"),
+ ("original", "bool", "Try to download the original file first", 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):
+ password = self.getPassword()
+
+ if self.js and 'class="btn iconify_down_b"' in self.html:
+ html = self.js.eval(self.load(self.pyfile.url, get={'action': "download", 'password': password}, decode=True))
+ pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
+ else:
+ id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ html = self.load("https://player.vimeo.com/video/" + 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.download(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.download(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/pyload/plugins/hoster/Vipleech4uCom.py b/pyload/plugins/hoster/Vipleech4uCom.py
new file mode 100644
index 000000000..c3627b914
--- /dev/null
+++ b/pyload/plugins/hoster/Vipleech4uCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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'
+
+ __description__ = """Vipleech4u.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+getInfo = create_getInfo(Vipleech4uCom)
diff --git a/pyload/plugins/hoster/WarserverCz.py b/pyload/plugins/hoster/WarserverCz.py
new file mode 100644
index 000000000..cb6efeba6
--- /dev/null
+++ b/pyload/plugins/hoster/WarserverCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+'
+
+ __description__ = """Warserver.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+getInfo = create_getInfo(WarserverCz)
diff --git a/pyload/plugins/hoster/WebshareCz.py b/pyload/plugins/hoster/WebshareCz.py
new file mode 100644
index 000000000..0a6063062
--- /dev/null
+++ b/pyload/plugins/hoster/WebshareCz.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+def getInfo(urls):
+ for url in urls:
+ fid = re.search(WebshareCz.__pattern__, url).group('ID')
+ api_data = getURL("https://webshare.cz/api/file_info/", post={'ident': fid})
+
+ if 'File not found' in api_data:
+ file_info = (url, 0, 1, url)
+ else:
+ name = re.search('<name>(.+)</name>', api_data).group(1)
+ size = re.search('<size>(.+)</size>', api_data).group(1)
+ file_info = (name, size, 2, url)
+
+ yield file_info
+
+
+class WebshareCz(SimpleHoster):
+ __name__ = "WebshareCz"
+ __type__ = "hoster"
+ __version__ = "0.14"
+
+ __pattern__ = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)'
+
+ __description__ = """WebShare.cz hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def handleFree(self):
+ api_data = self.load('https://webshare.cz/api/file_link/', post={'ident': self.fid})
+
+ 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.download(m.group(1), disposition=True)
+
+
+ def getFileInfo(self):
+ self.logDebug("URL: %s" % self.pyfile.url)
+
+ self.fid = re.match(self.__pattern__, self.pyfile.url).group('ID')
+
+ self.load(self.pyfile.url)
+ api_data = self.load('https://webshare.cz/api/file_info/', post={'ident': self.fid})
+
+ if 'File not found' in api_data:
+ self.offline()
+ else:
+ self.pyfile.name = re.search('<name>(.+)</name>', api_data).group(1)
+ self.pyfile.size = re.search('<size>(.+)</size>', api_data).group(1)
+
+ self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size))
diff --git a/pyload/plugins/hoster/WrzucTo.py b/pyload/plugins/hoster/WrzucTo.py
new file mode 100644
index 000000000..dbd09f138
--- /dev/null
+++ b/pyload/plugins/hoster/WrzucTo.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class WrzucTo(SimpleHoster):
+ __name__ = "WrzucTo"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))'
+
+ __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):
+ data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
+ if len(data) != 2:
+ self.error(_("No file ID"))
+
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = self.pyfile.url
+ self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
+
+ self.req.http.lastURL = self.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"))
+
+ download_url = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
+ self.download(download_url)
+
+
+getInfo = create_getInfo(WrzucTo)
diff --git a/pyload/plugins/hoster/WuploadCom.py b/pyload/plugins/hoster/WuploadCom.py
new file mode 100644
index 000000000..d2c202849
--- /dev/null
+++ b/pyload/plugins/hoster/WuploadCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.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+)(/.*)?'
+
+ __description__ = """Wupload.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("jeix", "jeix@hasnomail.de"),
+ ("Paul King", None)]
+
+
+getInfo = create_getInfo(WuploadCom)
diff --git a/pyload/plugins/hoster/X7To.py b/pyload/plugins/hoster/X7To.py
new file mode 100644
index 000000000..72e7d08db
--- /dev/null
+++ b/pyload/plugins/hoster/X7To.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class X7To(DeadHoster):
+ __name__ = "X7To"
+ __type__ = "hoster"
+ __version__ = "0.41"
+
+ __pattern__ = r'http://(?:www\.)?x7\.to/'
+
+ __description__ = """X7.to hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("ernieb", "ernieb")]
+
+
+getInfo = create_getInfo(X7To)
diff --git a/pyload/plugins/hoster/XFileSharingPro.py b/pyload/plugins/hoster/XFileSharingPro.py
new file mode 100644
index 000000000..f21ae34a4
--- /dev/null
+++ b/pyload/plugins/hoster/XFileSharingPro.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.XFSHoster import XFSHoster, create_getInfo
+
+
+class XFileSharingPro(XFSHoster):
+ __name__ = "XFileSharingPro"
+ __type__ = "hoster"
+ __version__ = "0.43"
+
+ __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(1).lower()
+ self.HOSTER_NAME = "".join([str.capitalize() for str in self.HOSTER_DOMAIN.split('.')])
+
+ 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/pyload/plugins/hoster/XHamsterCom.py b/pyload/plugins/hoster/XHamsterCom.py
new file mode 100644
index 000000000..503022ec9
--- /dev/null
+++ b/pyload/plugins/hoster/XHamsterCom.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.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 = 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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/XVideosCom.py b/pyload/plugins/hoster/XVideosCom.py
new file mode 100644
index 000000000..530858bbf
--- /dev/null
+++ b/pyload/plugins/hoster/XVideosCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.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/plugins/hoster/Xdcc.py b/pyload/plugins/hoster/Xdcc.py
new file mode 100644
index 000000000..de750ee58
--- /dev/null
+++ b/pyload/plugins/hoster/Xdcc.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+
+import re
+import socket
+import struct
+import sys
+import time
+
+from os import makedirs
+from os.path import exists, join
+from select import select
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import safe_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))
+ time.sleep(3)
+ 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 = safe_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/plugins/hoster/YibaishiwuCom.py b/pyload/plugins/hoster/YibaishiwuCom.py
new file mode 100644
index 000000000..82ba9e8dc
--- /dev/null
+++ b/pyload/plugins/hoster/YibaishiwuCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class YibaishiwuCom(SimpleHoster):
+ __name__ = "YibaishiwuCom"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)'
+
+ __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_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_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:
+ url = mr['url'].replace("\\", "")
+ self.logDebug("Trying URL: " + url)
+ self.download(url)
+ break
+ except Exception:
+ continue
+ else:
+ self.fail(_("No working link found"))
+
+
+getInfo = create_getInfo(YibaishiwuCom)
diff --git a/pyload/plugins/hoster/YoupornCom.py b/pyload/plugins/hoster/YoupornCom.py
new file mode 100644
index 000000000..b214d820c
--- /dev/null
+++ b/pyload/plugins/hoster/YoupornCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/YourfilesTo.py b/pyload/plugins/hoster/YourfilesTo.py
new file mode 100644
index 000000000..7cb94a2df
--- /dev/null
+++ b/pyload/plugins/hoster/YourfilesTo.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class YourfilesTo(Hoster):
+ __name__ = "YourfilesTo"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __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 = 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) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/YoutubeCom.py b/pyload/plugins/hoster/YoutubeCom.py
new file mode 100644
index 000000000..7570ddc1d
--- /dev/null
+++ b/pyload/plugins/hoster/YoutubeCom.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+
+from urllib import unquote
+
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.plugins.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"""
+
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+ fpath, fname = os.path.split(program)
+
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+
+ return None
+
+
+class YoutubeCom(Hoster):
+ __name__ = "YoutubeCom"
+ __type__ = "hoster"
+ __version__ = "0.40"
+
+ __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 (5-102, 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 desired_fmt and desired_fmt not in self.formats:
+ self.logWarning(_("FMT %d unknown, using default") % desired_fmt)
+ desired_fmt = 0
+ if not desired_fmt:
+ desired_fmt = quality.get(self.getConfig("quality"), 18)
+
+ #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']), 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/plugins/hoster/ZDF.py b/pyload/plugins/hoster/ZDF.py
new file mode 100644
index 000000000..9917955ec
--- /dev/null
+++ b/pyload/plugins/hoster/ZDF.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from xml.etree.ElementTree import fromstring
+
+from pyload.plugins.internal.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/plugins/hoster/ZShareNet.py b/pyload/plugins/hoster/ZShareNet.py
new file mode 100644
index 000000000..663377102
--- /dev/null
+++ b/pyload/plugins/hoster/ZShareNet.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class ZShareNet(DeadHoster):
+ __name__ = "ZShareNet"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
+
+ __description__ = """ZShare.net hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("espes", None),
+ ("Cptn Sandwich", None)]
+
+
+getInfo = create_getInfo(ZShareNet)
diff --git a/pyload/plugins/hoster/ZeveraCom.py b/pyload/plugins/hoster/ZeveraCom.py
new file mode 100644
index 000000000..d298d42f0
--- /dev/null
+++ b/pyload/plugins/hoster/ZeveraCom.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class ZeveraCom(Hoster):
+ __name__ = "ZeveraCom"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?zevera\.com/.*'
+
+ __description__ = """Zevera.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "zevera.com")
+ self.fail(_("No zevera.com account provided"))
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ if self.account.getAPIData(self.req, cmd="checklink", olink=pyfile.url) != "Alive":
+ self.fail(_("Offline or not downloadable - contact Zevera support"))
+
+ header = self.account.getAPIData(self.req, just_header=True, cmd="generatedownloaddirect", olink=pyfile.url)
+ if not "location" in header:
+ self.fail(_("Unable to initialize download"))
+
+ self.download(header['location'], disposition=True)
+
+ check = self.checkDownload({"error": 'action="ErrorDownload.aspx'})
+ if check == "error":
+ self.fail(_("Error response received - contact Zevera support"))
diff --git a/pyload/plugins/hoster/ZippyshareCom.py b/pyload/plugins/hoster/ZippyshareCom.py
new file mode 100644
index 000000000..5850a6a6a
--- /dev/null
+++ b/pyload/plugins/hoster/ZippyshareCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from os.path import join
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class ZippyshareCom(SimpleHoster):
+ __name__ = "ZippyshareCom"
+ __type__ = "hoster"
+ __version__ = "0.62"
+
+ __pattern__ = r'(?P<HOST>http://www\d{0,2}\.zippyshare\.com)/v(?:/|iew\.jsp.*key=)(?P<KEY>\d+)'
+
+ __description__ = """Zippyshare.com hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'("\d{6,}/"[ ]*\+.+?"/|<title>Zippyshare.com - )(?P<N>.+?)("|</title>)'
+ SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>File does not exist on this server<'
+
+ COOKIES = [("zippyshare.com", "ziplocale", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+ def handleFree(self):
+ url = self.get_link()
+ self.download(url)
+
+
+ def get_checksum(self):
+ try:
+ m = re.search(r'\+[ ]*\((\d+)[ ]*\%[ ]*(\d+)[ ]*\+[ ]*(\d+)[ ]*\%[ ]*(\d+)\)[ ]*\+', self.html)
+ if m:
+ a1, a2, c1, c2 = map(int, m.groups())
+ else:
+ a1, a2 = map(int, re.search(r'\(\'downloadB\'\).omg = (\d+)%(\d+)', self.html).groups())
+ c1, c2 = map(int, re.search(r'\(\'downloadB\'\).omg\) \* \((\d+)%(\d+)', self.html).groups())
+
+ b = (a1 % a2) * (c1 % c2)
+ except Exception:
+ self.error(_("Unable to calculate checksum"))
+ else:
+ return b + 18
+
+
+ def get_link(self):
+ checksum = self.get_checksum()
+ p_url = join("d", self.info['pattern']['KEY'], str(checksum), self.pyfile.name)
+ dl_link = urljoin(self.info['pattern']['HOST'], p_url)
+ return dl_link
+
+
+getInfo = create_getInfo(ZippyshareCom)
diff --git a/pyload/plugins/hoster/__init__.py b/pyload/plugins/hoster/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/hoster/__init__.py
diff --git a/pyload/plugins/internal/AbstractExtractor.py b/pyload/plugins/internal/AbstractExtractor.py
new file mode 100644
index 000000000..5e09c6755
--- /dev/null
+++ b/pyload/plugins/internal/AbstractExtractor.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+class ArchiveError(Exception):
+ pass
+
+
+class CRCError(Exception):
+ pass
+
+
+class WrongPassword(Exception):
+ pass
+
+
+class AbtractExtractor(object):
+ __name__ = "AbtractExtractor"
+ __version__ = "0.10"
+
+ __description__ = """Abtract extractor plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ @staticmethod
+ def checkDeps():
+ """ Check if system statisfy dependencies
+ :return: boolean
+ """
+ return True
+
+
+ @staticmethod
+ def getTargets(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
+ """
+ raise NotImplementedError
+
+
+ def __init__(self, m, file, out, fullpath, overwrite, excludefiles, renice):
+ """Initialize extractor for specific file
+
+ :param m: ExtractArchive Addon plugin
+ :param file: Absolute filepath
+ :param out: Absolute path to destination directory
+ :param fullpath: extract to fullpath
+ :param overwrite: Overwrite existing archives
+ :param renice: Renice value
+ """
+ self.m = m
+ self.file = file
+ self.out = out
+ self.fullpath = fullpath
+ self.overwrite = overwrite
+ self.excludefiles = excludefiles
+ self.renice = renice
+ self.files = [] #: Store extracted files here
+
+
+ def init(self):
+ """ Initialize additional data structures """
+ pass
+
+
+ def checkArchive(self):
+ """Check if password if needed. Raise ArchiveError if integrity is
+ questionable.
+
+ :return: boolean
+ :raises ArchiveError
+ """
+ return False
+
+
+ def checkPassword(self, password):
+ """ Check if the given password is/might be correct.
+ If it can not be decided at this point return true.
+
+ :param password:
+ :return: boolean
+ """
+ return True
+
+
+ def extract(self, progress, 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 WrongPassword
+ :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
+ """
+ raise NotImplementedError
+
+
+ def getExtractedFiles(self):
+ """Populate self.files at some point while extracting"""
+ return self.files
diff --git a/pyload/plugins/internal/Account.py b/pyload/plugins/internal/Account.py
new file mode 100644
index 000000000..b5125d61c
--- /dev/null
+++ b/pyload/plugins/internal/Account.py
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+
+from random import choice
+from time import time
+from traceback import print_exc
+from threading import RLock
+
+from pyload.plugins.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 kb, -1 for unlimited
+ "maxtraffic": None,
+ "premium": None,
+ "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 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 False if self.selectAccount() == (None, None) else True
+
+
+ def parseTraffic(self, string): #returns kbyte
+ return parseFileSize(string)
+
+
+ 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/plugins/internal/Addon.py b/pyload/plugins/internal/Addon.py
new file mode 100644
index 000000000..b126b97d6
--- /dev/null
+++ b/pyload/plugins/internal/Addon.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+
+from traceback import print_exc
+
+from pyload.plugins.Plugin import Base
+
+
+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(f):
+
+ def run(*args,**kwargs):
+ addonManager.startThread(f, *args, **kwargs)
+ return run
+
+
+class Addon(Base):
+ """
+ Base class for addon plugins.
+ """
+ __name__ = "Addon"
+ __type__ = "addon"
+ __version__ = "0.03"
+
+ __config__ = [] #: [("name", "type", "desc", "default")]
+
+ __description__ = """Base addon/hook 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 = {}
+
+ # 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 = -1 #: disabled
+
+ #: `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:
+ for f in self.event_list:
+ self.manager.addEvent(f, getattr(self,f))
+
+ self.event_list = None
+
+ self.setup()
+
+ self.initPeriodical()
+
+
+ def initPeriodical(self, delay=0, threaded=False):
+ self.cb = self.core.scheduler.addJob(delay, self._periodical, args=[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)
+
+
+ def __repr__(self):
+ return "<Addon %s>" % self.__name__
+
+
+ def setup(self):
+ """ more init stuff if needed """
+ pass
+
+
+ def unload(self):
+ """ called when addon was deactivated """
+ pass
+
+
+ def isActivated(self):
+ """ checks if addon is activated"""
+ return self.core.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
diff --git a/pyload/plugins/internal/BasePlugin.py b/pyload/plugins/internal/BasePlugin.py
new file mode 100644
index 000000000..a9d81d079
--- /dev/null
+++ b/pyload/plugins/internal/BasePlugin.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+from urlparse import urljoin, urlparse
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugins.internal.SimpleHoster import create_getInfo
+from pyload.plugins.internal.Hoster import Hoster
+
+
+class BasePlugin(Hoster):
+ __name__ = "BasePlugin"
+ __type__ = "hoster"
+ __version__ = "0.25"
+
+ __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
+ return {'name': urlparse(unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': unquote(url) or ""}
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ 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:
+ self.downloadFile(pyfile)
+
+ 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(pyfile.url).netloc
+
+ if server in servers:
+ self.logDebug("Logging on to %s" % server)
+ self.req.addAuth(account.accounts[server]['password'])
+ else:
+ for pwd in self.getPassword().splitlines():
+ if ":" in pwd:
+ self.req.addAuth(pwd.strip())
+ break
+ 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
+
+ if self.checkDownload({'empty': re.compile(r"^$")}) is "empty": #@TODO: Move to hoster in 0.4.10
+ self.fail(_("Empty file"))
+
+
+ def downloadFile(self, pyfile):
+ url = pyfile.url
+
+ for i in xrange(1, 7): #@TODO: retrieve the pycurl.MAXREDIRS value set by req
+ header = self.load(url, ref=True, cookies=True, just_header=True, decode=True)
+
+ if 'location' not in header or not header['location']:
+ if 'code' in header and header['code'] not in (200, 201, 203, 206):
+ self.logDebug("Received HTTP status code: %d" % header['code'])
+ self.fail(_("File not found"))
+ else:
+ break
+
+ location = header['location']
+
+ self.logDebug("Redirect #%d to: %s" % (i, location))
+
+ if urlparse(location).scheme:
+ url = location
+ else:
+ p = urlparse(url)
+ base = "%s://%s" % (p.scheme, p.netloc)
+ url = urljoin(base, location)
+ else:
+ self.fail(_("Too many redirects"))
+
+ self.download(unquote(url), disposition=True)
diff --git a/pyload/plugins/internal/Captcha.py b/pyload/plugins/internal/Captcha.py
new file mode 100644
index 000000000..7197c390e
--- /dev/null
+++ b/pyload/plugins/internal/Captcha.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Plugin import Plugin
+
+
+class Captcha(Plugin):
+ __name__ = "Captcha"
+ __type__ = "captcha"
+ __version__ = "0.14"
+
+ __description__ = """Base captcha service plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ KEY_PATTERN = None
+
+ key = None #: last key detected
+
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("%s html not found") % self.__name__
+ self.plugin.error(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_PATTERN, html)
+ if m:
+ self.key = m.group("KEY")
+ self.plugin.logDebug("%s key: %s" % (self.__name__, self.key))
+ return self.key
+ else:
+ self.plugin.logDebug("%s key not found" % self.__name__)
+ return None
+
+
+ def challenge(self, key=None):
+ raise NotImplementedError
+
+
+ def result(self, server, challenge):
+ raise NotImplementedError
diff --git a/pyload/plugins/internal/Container.py b/pyload/plugins/internal/Container.py
new file mode 100644
index 000000000..3320217ae
--- /dev/null
+++ b/pyload/plugins/internal/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.plugins.internal.Crypter import Crypter
+from pyload.utils import safe_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 = safe_join(self.core.config['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(safe_join(pypath, self.pyfile.url)):
+ self.pyfile.url = safe_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/plugins/internal/Crypter.py b/pyload/plugins/internal/Crypter.py
new file mode 100644
index 000000000..8580def61
--- /dev/null
+++ b/pyload/plugins/internal/Crypter.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+from urlparse import urlparse
+
+from pyload.plugins.Plugin import Plugin
+from pyload.utils import decode, html_unescape, 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['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 extracted"), "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['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(html_unescape(name)).path.split("/")[-1]
+
+ setFolder(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/plugins/internal/DeadCrypter.py b/pyload/plugins/internal/DeadCrypter.py
new file mode 100644
index 000000000..81b68e00a
--- /dev/null
+++ b/pyload/plugins/internal/DeadCrypter.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.plugins.internal.Crypter import Crypter as _Crypter
+from pyload.plugins.internal.SimpleCrypter import create_getInfo
+
+
+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 getInfo(cls, url="", html=""):
+ return {'name': urlparse(unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 1, 'url': url}
+
+
+ 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/pyload/plugins/internal/DeadHoster.py b/pyload/plugins/internal/DeadHoster.py
new file mode 100644
index 000000000..f066883c6
--- /dev/null
+++ b/pyload/plugins/internal/DeadHoster.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.plugins.internal.Hoster import Hoster as _Hoster
+from pyload.plugins.internal.SimpleHoster import create_getInfo
+
+
+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 getInfo(cls, url="", html=""):
+ return {'name': urlparse(unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 1, 'url': url}
+
+
+ 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/pyload/plugins/internal/Hoster.py b/pyload/plugins/internal/Hoster.py
new file mode 100644
index 000000000..ea225262e
--- /dev/null
+++ b/pyload/plugins/internal/Hoster.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.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/plugins/internal/MultiHoster.py b/pyload/plugins/internal/MultiHoster.py
new file mode 100644
index 000000000..4d466d350
--- /dev/null
+++ b/pyload/plugins/internal/MultiHoster.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.Addon import Addon
+from pyload.utils import remove_chars
+
+
+class MultiHoster(Addon):
+ __name__ = "MultiHoster"
+ __type__ = "addon"
+ __version__ = "0.20"
+
+ __description__ = """Base multi-hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ interval = 12 * 60 * 60 #: reload hosters every 12h
+
+ HOSTER_REPLACEMENTS = [("1fichier.com", "onefichier.com"), ("2shared.com", "twoshared.com"),
+ ("4shared.com", "fourshared.com"), ("cloudnator.com", "shragle.com"),
+ ("easy-share.com", "crocko.com"), ("freakshare.net", "freakshare.com"),
+ ("hellshare.com", "hellshare.cz"), ("ifile.it", "filecloud.io"),
+ ("putlocker.com", "firedrive.com"), ("share-rapid.cz", "multishare.cz"),
+ ("sharerapid.cz", "multishare.cz"), ("ul.to", "uploaded.to"),
+ ("uploaded.net", "uploaded.to")]
+ HOSTER_EXCLUDED = []
+
+
+ def setup(self):
+ self.hosters = []
+ self.supported = []
+ self.new_supported = []
+
+
+ def getConfig(self, option, default=''):
+ """getConfig with default value - subclass may not implements all config options"""
+ try:
+ # Fixed loop due to getConf deprecation in 0.4.10
+ return super(MultiHoster, self).getConfig(option)
+ except KeyError:
+ return default
+
+
+ def getHosterCached(self):
+ if not self.hosters:
+ try:
+ hosterSet = self.toHosterSet(self.getHoster()) - set(self.HOSTER_EXCLUDED)
+ except Exception, e:
+ self.logError(e)
+ return []
+
+ try:
+ configMode = self.getConfig('hosterListMode', 'all')
+ if configMode in ("listed", "unlisted"):
+ configSet = self.toHosterSet(self.getConfig('hosterList', '').replace('|', ',').replace(';', ',').split(','))
+
+ if configMode == "listed":
+ hosterSet &= configSet
+ else:
+ hosterSet -= configSet
+
+ except Exception, e:
+ self.logError(e)
+
+ self.hosters = list(hosterSet)
+
+ return self.hosters
+
+
+ def toHosterSet(self, hosters):
+ hosters = set((str(x).strip().lower() for x in hosters))
+
+ for rep in self.HOSTER_REPLACEMENTS:
+ if rep[0] in hosters:
+ hosters.remove(rep[0])
+ hosters.add(rep[1])
+
+ hosters.discard('')
+ return hosters
+
+
+ def getHoster(self):
+ """Load list of supported hoster
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+
+ def coreReady(self):
+ if self.cb:
+ self.core.scheduler.removeJob(self.cb)
+
+ self.setConfig("activated", True) #: config not in sync after plugin reload
+
+ cfg_interval = self.getConfig("interval", None) #: reload interval in hours
+ if cfg_interval is not None:
+ self.interval = cfg_interval * 60 * 60
+
+ if self.interval:
+ self._periodical()
+ else:
+ self.periodical()
+
+
+ def initPeriodical(self):
+ pass
+
+
+ def periodical(self):
+ """reload hoster list periodically"""
+ self.logInfo(_("Reloading supported hoster list"))
+
+ old_supported = self.supported
+ self.supported = []
+ self.new_supported = []
+ self.hosters = []
+
+ self.overridePlugins()
+
+ old_supported = [hoster for hoster in old_supported if hoster not in self.supported]
+ if old_supported:
+ self.logDebug("UNLOAD", ", ".join(old_supported))
+ for hoster in old_supported:
+ self.unloadHoster(hoster)
+
+
+ def overridePlugins(self):
+ pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.keys())
+ accountList = [name.lower() for name, data in self.core.accountManager.accounts.iteritems() if data]
+ excludedList = []
+
+ for hoster in self.getHosterCached():
+ name = remove_chars(hoster.lower(), "-.")
+
+ if name in accountList:
+ excludedList.append(hoster)
+ else:
+ if name in pluginMap:
+ self.supported.append(pluginMap[name])
+ else:
+ self.new_supported.append(hoster)
+
+ if not self.supported and not self.new_supported:
+ self.logError(_("No Hoster loaded"))
+ return
+
+ module = self.core.pluginManager.getPlugin(self.__type__, self.__name__)
+ klass = getattr(module, self.__name__)
+
+ # inject plugin plugin
+ self.logDebug("Overwritten Hosters", ", ".join(sorted(self.supported)))
+ for hoster in self.supported:
+ dict = self.core.pluginManager.hosterPlugins[hoster]
+ dict['new_module'] = module
+ dict['new_name'] = self.__name__
+
+ if excludedList:
+ self.logInfo(_("The following hosters were not overwritten - account exists"), ", ".join(sorted(excludedList)))
+
+ if self.new_supported:
+ self.logDebug("New Hosters", ", ".join(sorted(self.new_supported)))
+
+ # create new regexp
+ regexp = r'.*(%s).*' % "|".join([x.replace(".", "\.") for x in self.new_supported])
+ if hasattr(klass, "__pattern__") and isinstance(klass.__pattern__, basestring) and '://' in klass.__pattern__:
+ regexp = r'%s|%s' % (klass.__pattern__, regexp)
+
+ self.logDebug("Regexp", regexp)
+
+ dict = self.core.pluginManager.hosterPlugins[self.__name__]
+ dict['pattern'] = regexp
+ dict['re'] = re.compile(regexp)
+
+
+ def unloadHoster(self, hoster):
+ dict = self.core.pluginManager.hosterPlugins[hoster]
+ if "module" in dict:
+ del dict['module']
+
+ if "new_module" in dict:
+ del dict['new_module']
+ del dict['new_name']
+
+
+ def unload(self):
+ """Remove override for all hosters. Scheduler job is removed by AddonManager"""
+ for hoster in self.supported:
+ self.unloadHoster(hoster)
+
+ # reset pattern
+ klass = getattr(self.core.pluginManager.getPlugin(self.__type__, self.__name__), self.__name__)
+ dict = self.core.pluginManager.hosterPlugins[self.__name__]
+ dict['pattern'] = getattr(klass, "__pattern__", r'^unmatchable$')
+ dict['re'] = re.compile(dict['pattern'])
+
+
+ def downloadFailed(self, pyfile):
+ """remove plugin override if download fails but not if file is offline/temp.offline"""
+ if pyfile.hasStatus("failed") and self.getConfig("unloadFailing", True):
+ hdict = self.core.pluginManager.hosterPlugins[pyfile.pluginname]
+ if "new_name" in hdict and hdict['new_name'] == self.__name__:
+ self.logDebug("Unload MultiHoster", pyfile.pluginname, hdict)
+ self.unloadHoster(pyfile.pluginname)
+ pyfile.setStatus("queued")
diff --git a/pyload/plugins/internal/OCR.py b/pyload/plugins/internal/OCR.py
new file mode 100644
index 000000000..8e60737b6
--- /dev/null
+++ b/pyload/plugins/internal/OCR.py
@@ -0,0 +1,315 @@
+# -*- 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 subprocess
+
+from os.path import abspath, join
+
+
+class OCR(object):
+ __name__ = "OCR"
+ __type__ = "ocr"
+ __version__ = "0.10"
+
+ __description__ = """Base OCR 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(join("tmp", "tmpTif_%s.tif" % self.__name__), "wb")
+ tmpTif.close()
+
+ #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
+ tmpTxt = open(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 = [join(pypath, "tesseract", "tesseract.exe")]
+ else:
+ tessparams = ['tesseract']
+
+ tessparams.extend([abspath(tmpTif.name), abspath(tmpTxt.name).replace(".txt", "")] )
+
+ if subset and (digits or lowercase or uppercase):
+ #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
+ with open(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(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/plugins/internal/SimpleCrypter.py b/pyload/plugins/internal/SimpleCrypter.py
new file mode 100644
index 000000000..4287c4d74
--- /dev/null
+++ b/pyload/plugins/internal/SimpleCrypter.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urlparse
+
+from pyload.plugins.internal.Crypter import Crypter
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns, set_cookies
+from pyload.utils import fixup
+
+
+class SimpleCrypter(Crypter, SimpleHoster):
+ __name__ = "SimpleCrypter"
+ __type__ = "crypter"
+ __version__ = "0.32"
+
+ __pattern__ = r'^unmatchable$'
+ __config__ = [("use_subfolder", "bool", "Save package to subfolder", True), #: Overrides core.config['general']['folder_per_package']
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description__ = """Simple decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("stickell", "l.stickell@yahoo.it"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: group(1) must be a download link or a regex to catch more links
+ example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ NAME_PATTERN: (optional) folder name or webpage title
+ example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>'
+
+ OFFLINE_PATTERN: (optional) Checks if the file is yet available online
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the file is temporarily offline
+ 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.info = {}
+ self.links = []
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ if self.LOGIN_PREMIUM and not self.premium:
+ self.fail(_("Required premium 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()
+
+ if self.html is None:
+ self.fail(_("No html retrieved"))
+
+ self.checkInfo()
+
+ self.links = self.getLinks()
+
+ if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
+ self.handleMultiPages()
+
+ self.logDebug("Package has %d links" % len(self.links))
+
+ if self.links:
+ self.packages = [(self.info['name'], self.links, self.info['folder'])]
+
+
+ def checkStatus(self):
+ status = self.info['status']
+
+ if status is 1:
+ self.offline()
+
+ elif status is 6:
+ self.tempOffline()
+
+
+ def checkNameSize(self):
+ name = self.info['name']
+ url = self.info['url']
+
+ if name and name != url:
+ self.pyfile.name = name
+ else:
+ self.pyfile.name = name = self.info['name'] = urlparse(name).path.split('/')[-1]
+
+ folder = self.info['folder'] = name
+
+ self.logDebug("File name: %s" % name,
+ "File folder: %s" % folder)
+
+
+ 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.
+ """
+ return re.findall(self.LINK_PATTERN, self.html)
+
+
+ def handleMultiPages(self):
+ try:
+ m = re.search(self.PAGES_PATTERN, self.html)
+ pages = int(m.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/plugins/internal/SimpleHoster.py b/pyload/plugins/internal/SimpleHoster.py
new file mode 100644
index 000000000..ce1ddd2b6
--- /dev/null
+++ b/pyload/plugins/internal/SimpleHoster.py
@@ -0,0 +1,532 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+from urllib import unquote
+from urlparse import urljoin, urlparse
+
+from pyload.datatype.PyFile import statusMap as _statusMap
+from pyload.network.CookieJar import CookieJar
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Hoster import Hoster
+from pyload.utils import fixup, parseFileSize
+
+
+#@TODO: Adapt and move to PyFile in 0.4.10
+statusMap = dict((v, k) for k, v in _statusMap.iteritems())
+
+
+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=""):
+ info = plugin.getInfo(url, html)
+ return info['name'], info['size'], info['status'], info['url']
+
+
+#@TODO: Remove in 0.4.10
+#@NOTE: Every plugin must have own parseInfo classmethod to work with 0.4.10
+def create_getInfo(plugin):
+ return lambda urls: [(info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfo(urls)]
+
+
+def timestamp():
+ return int(time() * 1000)
+
+
+#@TODO: Move to hoster class in 0.4.10
+def _isDirectLink(self, url, resumable=True):
+ header = self.load(url, ref=True, just_header=True, decode=True)
+
+ if not 'location' in header or not header['location']:
+ return ""
+
+ location = header['location']
+
+ resumable = False #@NOTE: Testing...
+
+ if resumable: #: sometimes http code may be wrong...
+ if 'location' in self.load(location, ref=True, cookies=True, just_header=True, decode=True):
+ return ""
+ else:
+ if not 'code' in header or header['code'] != 302:
+ return ""
+
+ if urlparse(location).scheme:
+ link = location
+ else:
+ p = urlparse(url)
+ base = "%s://%s" % (p.scheme, p.netloc)
+ link = urljoin(base, location)
+
+ return link
+
+
+class SimpleHoster(Hoster):
+ __name__ = "SimpleHoster"
+ __type__ = "hoster"
+ __version__ = "0.71"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """Simple hoster plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("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 file is yet available online
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Check if the file is temporarily offline
+ 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)]
+ FORCE_CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account
+ CHECK_DIRECT_LINK = None #: Set to True to check for direct link, set to None to do it only if self.account is True
+ MULTI_HOSTER = False #: Set to True to leech other hoster link (according its multihoster hook if available)
+ CONTENT_DISPOSITION = False #: Set to True to replace file name with content-disposition value from http header
+
+
+ @classmethod
+ def parseInfo(cls, urls):
+ for url in urls:
+ url = replace_patterns(url, cls.URL_REPLACEMENTS)
+ yield cls.getInfo(url)
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = {'name': urlparse(unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3, 'url': url}
+
+ if not html:
+ try:
+ if not url:
+ info['error'] = "missing url"
+ info['status'] = 1
+ raise
+
+ 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
+ raise
+
+ if e.code is 503:
+ info['status'] = 6
+ raise
+ except:
+ return info
+
+ online = False
+
+ 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:
+ try:
+ info['pattern'] = re.match(cls.__pattern__, url).groupdict() #: pattern groups will be saved here, please save api stuff to info['api']
+ except Exception:
+ pass
+
+ for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"):
+ try:
+ attr = getattr(cls, pattern)
+ dict = re.search(attr, html).groupdict()
+
+ if all(True for k in dict if k not in info['pattern']):
+ info['pattern'].update(dict)
+
+ except AttributeError:
+ continue
+
+ else:
+ online = True
+
+ if online:
+ info['status'] = 2
+
+ if 'N' in info['pattern']:
+ info['name'] = replace_patterns(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 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']
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ self.info = {}
+ 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
+
+ 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.logInfo("Multi hoster detected")
+
+ if self.account:
+ self.multihost = True
+ return
+ else:
+ self.fail(_("Only registered or premium users can use url leech feature"))
+
+ if self.CHECK_DIRECT_LINK is None:
+ self.directDL = bool(self.account)
+
+ 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):
+ self.prepare()
+
+ if self.multihost:
+ self.logDebug("Looking for leeched download link...")
+ self.handleMulti()
+
+ elif self.directDL:
+ self.logDebug("Looking for direct download link...")
+ self.handleDirect()
+
+ if not self.link:
+ self.preload()
+
+ if self.html is None:
+ self.fail(_("No html retrieved"))
+
+ self.checkErrors()
+
+ premium_only = 'error' in self.info and self.info['error'] == "premium-only"
+
+ self._updateInfo(self.getInfo(pyfile.url, self.html))
+
+ self.checkNameSize()
+
+ #: Usually premium only pages doesn't show any file information
+ if not premium_only:
+ self.checkStatus()
+
+ if self.premium and (not self.FORCE_CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as premium download")
+ self.handlePremium()
+
+ elif premium_only:
+ self.fail(_("Link require a premium account to be handled"))
+
+ else:
+ self.logDebug("Handled as free download")
+ self.handleFree()
+
+ self.downloadLink(self.link)
+ self.checkFile()
+
+
+ def downloadLink(self, link):
+ if not link:
+ return
+
+ self.download(link, disposition=self.CONTENT_DISPOSITION)
+
+
+ def checkFile(self):
+ if self.checkDownload({'empty': re.compile(r"^$")}) is "empty": #@TODO: Move to hoster in 0.4.10
+ self.fail(_("Empty file"))
+
+
+ def checkErrors(self):
+ if hasattr(self, 'ERROR_PATTERN'):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = m.group(1)
+ self.error(errmsg)
+
+ if hasattr(self, 'PREMIUM_ONLY_PATTERN'):
+ m = re.search(self.PREMIUM_ONLY_PATTERN, self.html)
+ if m:
+ self.info['error'] = "premium-only"
+ return
+
+ if 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, re.I)])
+ self.wait(wait_time, False)
+ return
+
+ self.info.pop('error', None)
+
+
+ def checkStatus(self):
+ status = self.info['status']
+
+ if status is 1:
+ self.offline()
+
+ elif status is 6:
+ self.tempOffline()
+
+ elif status is not 2:
+ self.logInfo(_("File status: %s") % statusMap[status],
+ _("File info: %s") % self.info)
+ self.error(_("No file info retrieved"))
+
+
+ def checkNameSize(self):
+ name = self.info['name']
+ size = self.info['size']
+ url = self.info['url']
+
+ if name and name != url:
+ self.pyfile.name = name
+ else:
+ self.pyfile.name = name = self.info['name'] = urlparse(name).path.split('/')[-1]
+
+ if size > 0:
+ self.pyfile.size = size
+ else:
+ size = "Unknown"
+
+ self.logDebug("File name: %s" % name,
+ "File size: %s" % size)
+
+
+ def checkInfo(self):
+ self.checkErrors()
+
+ self._updateInfo(self.getInfo(self.pyfile.url, self.html or ""))
+
+ self.checkNameSize()
+ self.checkStatus()
+
+
+ #: Deprecated
+ def getFileInfo(self):
+ self.info = {}
+ self.checkInfo()
+ return self.info
+
+
+ def _updateInfo(self, info):
+ self.logDebug(_("File info (before update): %s") % self.info)
+ self.info.update(info)
+ self.logDebug(_("File info (after update): %s") % self.info)
+
+
+ def handleDirect(self):
+ link = _isDirectLink(self, self.pyfile.url, self.resumeDownload)
+
+ if link:
+ self.logInfo(_("Direct download link detected"))
+
+ self.link = link
+
+ self._updateInfo(self.getInfo(self.pyfile.url))
+ self.checkNameSize()
+ else:
+ self.logDebug(_("Direct download link not found"))
+
+
+ def handleMulti(self): #: Multi-hoster handler
+ pass
+
+
+ def handleFree(self):
+ if not hasattr(self, 'LINK_FREE_PATTERN'):
+ self.fail(_("Free download not implemented"))
+
+ try:
+ 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)
+
+ except Exception, e:
+ self.fail(e)
+
+
+ def handlePremium(self):
+ if not hasattr(self, 'LINK_PREMIUM_PATTERN'):
+ self.fail(_("Premium download not implemented"))
+
+ try:
+ m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ if m is None:
+ self.error(_("Premium download link not found"))
+
+ self.link = m.group(1)
+
+ except Exception, e:
+ self.fail(e)
+
+
+ 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.setWait(wait_time, True)
+ self.wait()
+ 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):
+ 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 error(self, reason="", type="parse"):
+ return super(SimpleHoster, self).error(self, reason, type)
diff --git a/pyload/plugins/internal/UnRar.py b/pyload/plugins/internal/UnRar.py
new file mode 100644
index 000000000..90216222b
--- /dev/null
+++ b/pyload/plugins/internal/UnRar.py
@@ -0,0 +1,221 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+
+from glob import glob
+from os.path import basename, join
+from string import digits
+from subprocess import Popen, PIPE
+
+from pyload.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError
+from pyload.utils import safe_join, decode
+
+
+def renice(pid, value):
+ if os.name != "nt" and value:
+ try:
+ Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1)
+ except Exception:
+ print "Renice failed"
+
+
+class UnRar(AbtractExtractor):
+ __name__ = "UnRar"
+ __version__ = "0.19"
+
+ __description__ = """Rar extractor plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ CMD = "unrar"
+
+ # there are some more uncovered rar formats
+ re_version = re.compile(r'UNRAR ([\w .]+?) freeware')
+ re_splitfile = re.compile(r'(.*)\.part(\d+)\.rar$', re.I)
+ re_partfiles = re.compile(r'.*\.(rar|r\d+)', re.I)
+ re_filelist = re.compile(r'(.+)\s+(\d+)\s+(\d+)\s+')
+ re_filelist5 = re.compile(r'(.+)\s+(\d+)\s+\d\d-\d\d-\d\d\s+\d\d:\d\d\s+(.+)')
+ re_wrongpwd = re.compile(r'(Corrupt file or wrong password|password incorrect)', re.I)
+
+
+ @staticmethod
+ def checkDeps():
+ if os.name == "nt":
+ UnRar.CMD = join(pypath, "UnRAR.exe")
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+ else:
+ try:
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+ except OSError:
+
+ # fallback to rar
+ UnRar.CMD = "rar"
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+
+ return True
+
+
+ @staticmethod
+ def getTargets(files_ids):
+ result = []
+
+ for file, id in files_ids:
+ if not file.endswith(".rar"):
+ continue
+
+ match = UnRar.re_splitfile.findall(file)
+ if match:
+ # only add first parts
+ if int(match[0][1]) == 1:
+ result.append((file, id))
+ else:
+ result.append((file, id))
+
+ return result
+
+
+ def init(self):
+ self.passwordProtected = False
+ self.headerProtected = False #: list files will not work without password
+ self.smallestFile = None #: small file to test passwords
+ self.password = "" #: save the correct password
+
+
+ def checkArchive(self):
+ p = self.call_unrar("l", "-v", self.file)
+ out, err = p.communicate()
+ if self.re_wrongpwd.search(err):
+ self.passwordProtected = True
+ self.headerProtected = True
+ return True
+
+ # output only used to check if passworded files are present
+ if self.re_version.search(out):
+ for attr, size, name in self.re_filelist5.findall(out):
+ if attr.startswith("*"):
+ self.passwordProtected = True
+ return True
+ else:
+ for name, size, packed in self.re_filelist.findall(out):
+ if name.startswith("*"):
+ self.passwordProtected = True
+ return True
+
+ self.listContent()
+ if not self.files:
+ raise ArchiveError("Empty Archive")
+
+ return False
+
+
+ def checkPassword(self, password):
+ # at this point we can only verify header protected files
+ if self.headerProtected:
+ p = self.call_unrar("l", "-v", self.file, password=password)
+ out, err = p.communicate()
+ if self.re_wrongpwd.search(err):
+ return False
+
+ return True
+
+
+ def extract(self, progress, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_unrar(command, self.file, self.out, password=password)
+ renice(p.pid, self.renice)
+
+ progress(0)
+ progressstring = ""
+ while True:
+ c = p.stdout.read(1)
+ # quit loop on eof
+ if not c:
+ break
+ # reading a percentage sign -> set progress and restart
+ if c == '%':
+ progress(int(progressstring))
+ progressstring = ""
+ # not reading a digit -> therefore restart
+ elif c not in digits:
+ progressstring = ""
+ # add digit to progressstring
+ else:
+ progressstring = progressstring + c
+ progress(100)
+
+ # retrieve stderr
+ err = p.stderr.read()
+
+ if "CRC failed" in err and not password and not self.passwordProtected:
+ raise CRCError
+ elif "CRC failed" in err:
+ raise WrongPassword
+ if err.strip(): #: raise error if anything is on stderr
+ raise ArchiveError(err.strip())
+ if p.returncode:
+ raise ArchiveError("Process terminated")
+
+ if not self.files:
+ self.password = password
+ self.listContent()
+
+
+ def getDeleteFiles(self):
+ if ".part" in basename(self.file):
+ return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.I))
+ # get files which matches .r* and filter unsuited files out
+ parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.I))
+ return filter(lambda x: self.re_partfiles.match(x), parts)
+
+
+ def listContent(self):
+ command = "vb" if self.fullpath else "lb"
+ p = self.call_unrar(command, "-v", self.file, password=self.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.m.logError(err.strip())
+
+ result = set()
+
+ for f in decode(out).splitlines():
+ f = f.strip()
+ result.add(safe_join(self.out, f))
+
+ self.files = result
+
+
+ def call_unrar(self, command, *xargs, **kwargs):
+ args = []
+ # overwrite flag
+ args.append("-o+") if self.overwrite else args.append("-o-")
+
+ if self.excludefiles:
+ for word in self.excludefiles.split(';'):
+ args.append("-x%s" % word)
+
+ # 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-")
+
+ # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
+ call = [self.CMD, command] + args + list(xargs)
+ self.m.logDebug(" ".join(call))
+
+ p = Popen(call, stdout=PIPE, stderr=PIPE)
+
+ return p
diff --git a/pyload/plugins/internal/UnZip.py b/pyload/plugins/internal/UnZip.py
new file mode 100644
index 000000000..17c321530
--- /dev/null
+++ b/pyload/plugins/internal/UnZip.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import zipfile
+
+from pyload.plugins.internal.AbstractExtractor import AbtractExtractor
+
+
+class UnZip(AbtractExtractor):
+ __name__ = "UnZip"
+ __version__ = "0.10"
+
+ __description__ = """Zip extractor plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ @staticmethod
+ def checkDeps():
+ return sys.version_info[:2] >= (2, 6)
+
+
+ @staticmethod
+ def getTargets(files_ids):
+ result = []
+
+ for file, id in files_ids:
+ if file.endswith(".zip"):
+ result.append((file, id))
+
+ return result
+
+
+ def extract(self, progress, password=None):
+ z = zipfile.ZipFile(self.file)
+ self.files = z.namelist()
+ z.extractall(self.out)
+
+
+ def getDeleteFiles(self):
+ return [self.file]
diff --git a/pyload/plugins/internal/UpdateManager.py b/pyload/plugins/internal/UpdateManager.py
new file mode 100644
index 000000000..622374136
--- /dev/null
+++ b/pyload/plugins/internal/UpdateManager.py
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+
+from operator import itemgetter
+from os import path, remove, stat
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.Addon import Expose, Addon, threaded
+from pyload.utils import safe_join
+
+
+class UpdateManager(Addon):
+ __name__ = "UpdateManager"
+ __type__ = "addon"
+ __version__ = "0.40"
+
+ __config__ = [("activated" , "bool" , "Activated" , True ),
+ ("mode" , "pyLoad + plugins;plugins only", "Check updates for" , "pyLoad + plugins"),
+ ("interval" , "int" , "Check interval in hours" , 8 ),
+ ("reloadplugins", "bool" , "Monitor plugins for code changes (debug mode only)", True ),
+ ("nodebugupdate", "bool" , "Don't check for updates in debug mode" , True )]
+
+ __description__ = """Check for updates"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ SERVER_URL = "http://updatemanager.pyload.org"
+ MIN_INTERVAL = 6 * 60 * 60 #: 6h minimum check interval (value is in seconds)
+
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == "interval":
+ interval = value * 60 * 60
+ if self.MIN_INTERVAL <= interval != self.interval:
+ self.core.scheduler.removeJob(self.cb)
+ self.interval = interval
+ self.initPeriodical()
+ else:
+ self.logDebug("Invalid interval value, kept current")
+
+ elif name == "reloadplugins":
+ if self.cb2:
+ self.core.scheduler.removeJob(self.cb2)
+ if value is True and self.core.debug:
+ self.periodical2()
+
+
+ def coreReady(self):
+ self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
+ x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins"))
+ self.core.scheduler.addJob(10, x, threaded=False)
+
+
+ def unload(self):
+ self.pluginConfigChanged(self.__name__, "reloadplugins", False)
+
+
+ def setup(self):
+ self.cb2 = None
+ self.interval = self.MIN_INTERVAL
+ self.updating = False
+ self.info = {'pyload': False, 'version': None, 'plugins': False}
+ self.mtimes = {} #: store modification time for each plugin
+
+
+ def periodical2(self):
+ if not self.updating:
+ self.autoreloadPlugins()
+
+ self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False)
+
+
+ @Expose
+ def autoreloadPlugins(self):
+ """ reload and reindex all modified plugins """
+ modules = filter(
+ lambda m: m and (m.__name__.startswith("pyload.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 path.isfile(f):
+ continue
+
+ mtime = 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 periodical(self):
+ if self.info['pyload'] or self.getConfig("nodebugupdate") and self.core.debug:
+ return
+
+ self.updateThread()
+
+
+ def server_request(self):
+ try:
+ return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
+ except Exception:
+ self.logWarning(_("Unable to contact server to get updates"))
+
+
+ @threaded
+ def updateThread(self):
+ self.updating = True
+
+ status = self.update(onlyplugin=self.getConfig("mode") == "plugins only")
+
+ if status == 2:
+ self.core.api.restart()
+ else:
+ self.updating = False
+
+
+ @Expose
+ def updatePlugins(self):
+ """ simple wrapper for calling plugin update quickly """
+ return self.update(onlyplugin=True)
+
+
+ @Expose
+ def update(self, onlyplugin=False):
+ """ check for updates """
+ data = self.server_request()
+
+ if not data:
+ exitcode = 0
+
+ elif data[0] == "None":
+ self.logInfo(_("No new pyLoad version available"))
+ updates = data[1:]
+ exitcode = self._updatePlugins(updates)
+
+ elif onlyplugin:
+ exitcode = 0
+
+ else:
+ newversion = data[0]
+ self.logInfo(_("*** New pyLoad Version %s available ***") % newversion)
+ self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
+ exitcode = 3
+ self.info['pyload'] = True
+ self.info['version'] = newversion
+
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available
+
+
+ def _updatePlugins(self, updates):
+ """ check for plugin updates """
+
+ if self.info['plugins']:
+ return False #: plugins were already updated
+
+ exitcode = 0
+ updated = []
+
+ vre = re.compile(r'__version__.*=.*("|\')([\d.]+)')
+ url = updates[0]
+ schema = updates[1].split('|')
+
+ if "BLACKLIST" in updates:
+ blacklist = updates[updates.index('BLACKLIST') + 1:]
+ updates = updates[2:updates.index('BLACKLIST')]
+ else:
+ blacklist = None
+ updates = updates[2:]
+
+ upgradable = sorted(map(lambda x: dict(zip(schema, x.split('|'))), updates),
+ key=itemgetter("type", "name"))
+
+ for plugin in upgradable:
+ 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 = vre.search(content)
+
+ if m and m.group(2) == version:
+ f = open(safe_join("userplugins", prefix, filename), "wb")
+ f.write(content)
+ f.close()
+ updated.append((prefix, name))
+ else:
+ raise Exception, _("Version mismatch")
+
+ except Exception, e:
+ self.logError(_("Error updating plugin %s") % filename, e)
+
+ if blacklist:
+ blacklisted = map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist)
+
+ # Always protect internal plugins from removing
+ for i, n, t in blacklisted.enumerate():
+ if t == "internal":
+ del blacklisted[i]
+
+ blacklisted = sorted(blacklisted)
+ removed = self.removePlugins(blacklisted)
+ for t, n in removed:
+ self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % {
+ 'type': t,
+ 'name': n,
+ })
+
+ if updated:
+ reloaded = self.core.pluginManager.reloadPlugins(updated)
+ if reloaded:
+ self.logInfo(_("Plugins updated and reloaded"))
+ exitcode = 1
+ else:
+ self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***"))
+ self.info['plugins'] = True
+ exitcode = 2
+ else:
+ self.logInfo(_("No plugin updates available"))
+
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required
+
+
+ @Expose
+ def removePlugins(self, type_plugins):
+ """ delete plugins from disk """
+
+ if not type_plugins:
+ return
+
+ self.logDebug("Requested deletion of plugins: %s" % type_plugins)
+
+ removed = []
+
+ for type, name in type_plugins:
+ err = False
+ file = name + ".py"
+
+ for root in ("userplugins", path.join(pypath, "pyload", "plugins")):
+
+ filename = safe_join(root, type, file)
+ try:
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting: %s" % path.basename(filename), e)
+ err = True
+
+ filename += "c"
+ if path.isfile(filename):
+ try:
+ if type == "addon":
+ self.manager.deactivateAddon(name)
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting: %s" % path.basename(filename), e)
+ err = True
+
+ if not err:
+ id = (type, name)
+ removed.append(id)
+
+ return removed #: return a list of the plugins successfully removed
diff --git a/pyload/plugins/internal/XFSAccount.py b/pyload/plugins/internal/XFSAccount.py
new file mode 100644
index 000000000..a330d2ff4
--- /dev/null
+++ b/pyload/plugins/internal/XFSAccount.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import gmtime, mktime, strptime
+from urlparse import urljoin
+
+from pyload.plugins.internal.Account import Account
+from pyload.plugins.internal.SimpleHoster import parseHtmlForm, set_cookies
+
+
+class XFSAccount(Account):
+ __name__ = "XFSAccount"
+ __type__ = "account"
+ __version__ = "0.32"
+
+ __description__ = """XFileSharing account plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+ HOSTER_URL = None
+
+ COOKIES = [(HOSTER_DOMAIN, "lang", "english")]
+
+ 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'>\s*(Incorrect Login or Password|Error<)'
+
+
+ def init(self):
+ # if not self.HOSTER_DOMAIN:
+ # self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if not self.HOSTER_URL:
+ self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ leechtraffic = None
+ premium = None
+
+ 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 = mktime(strptime(expiredate, "%d %B %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ self.logDebug("Valid until: %s" % validuntil)
+
+ if validuntil > mktime(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 isinstance(self.COOKIES, list):
+ set_cookies(req.cj, self.COOKIES)
+
+ url = urljoin(self.HOSTER_URL, "login.html")
+ html = req.load(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']})
+
+ html = req.load(self.HOSTER_URL, post=inputs, decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugins/internal/XFSCrypter.py b/pyload/plugins/internal/XFSCrypter.py
new file mode 100644
index 000000000..2de39f4bc
--- /dev/null
+++ b/pyload/plugins/internal/XFSCrypter.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class XFSCrypter(SimpleCrypter):
+ __name__ = "XFSCrypter"
+ __type__ = "crypter"
+ __version__ = "0.04"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """XFileSharing decrypter plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+ HOSTER_NAME = None
+
+ URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')]
+
+ COOKIES = [(HOSTER_DOMAIN, "lang", "english")]
+
+ LINK_PATTERN = r'<(?:td|TD).*?>\s*<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)'
diff --git a/pyload/plugins/internal/XFSHoster.py b/pyload/plugins/internal/XFSHoster.py
new file mode 100644
index 000000000..b215b8211
--- /dev/null
+++ b/pyload/plugins/internal/XFSHoster.py
@@ -0,0 +1,345 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+from time import sleep
+
+from pycurl import FOLLOWLOCATION, LOW_SPEED_TIME
+
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.captcha import ReCaptcha, SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from pyload.utils import html_unescape
+
+
+class XFSHoster(SimpleHoster):
+ __name__ = "XFSHoster"
+ __type__ = "hoster"
+ __version__ = "0.27"
+
+ __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
+ HOSTER_NAME = None
+
+ TEXT_ENCODING = False
+ COOKIES = [(HOSTER_DOMAIN, "lang", "english")]
+ CHECK_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="|<span class="name">)(?P<N>.+?)(\s*<|")'
+ SIZE_PATTERN = r'(>Size:</b></td><td>|>File:.*>|<span class="size">)(?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*)*(.+?)(?:["\']|<|\))'
+
+ LEECH_LINK_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
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ """ Initialize important variables """
+ if not self.HOSTER_DOMAIN:
+ self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if not self.HOSTER_NAME:
+ self.HOSTER_NAME = "".join([str.capitalize() for str in self.HOSTER_DOMAIN.split('.')])
+
+ 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
+ self.passwords = self.getPassword().splitlines()
+
+ super(XFSHoster, self).prepare()
+
+ if self.CHECK_DIRECT_LINK is None:
+ self.directDL = bool(self.premium)
+
+
+ def handleFree(self):
+ link = self.getDownloadLink()
+
+ if link:
+ if self.captcha:
+ self.correctCaptcha()
+
+ self.download(link, ref=True, cookies=True, disposition=True)
+
+ elif self.errmsg:
+ if 'captcha' in self.errmsg:
+ self.fail(_("No valid captcha code entered"))
+ else:
+ self.fail(self.errmsg)
+
+ else:
+ self.fail(_("Download link not found"))
+
+
+ def handlePremium(self):
+ return self.handleFree()
+
+
+ def getDownloadLink(self):
+ 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(FOLLOWLOCATION, 0)
+
+ self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
+
+ self.req.http.c.setopt(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.errmsg = None
+
+ return m.group(1)
+
+
+ def handleMulti(self):
+ #only tested with easybytez.com
+ self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN)
+
+ action, inputs = self.parseHtmlForm()
+
+ upload_id = "%012d" % int(random() * 10 ** 12)
+ action += upload_id + "&js_on=1&utype=prem&upload_type=url"
+
+ inputs['tos'] = '1'
+ inputs['url_mass'] = self.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.LEECH_LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LEECH_LINK_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']
+ else:
+ self.fail(_("Download link not found"))
+
+
+ def checkErrors(self):
+ m = re.search(self.PREMIUM_ONLY_PATTERN, self.html)
+ if m:
+ self.info['error'] = "premium-only"
+ return
+
+ 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}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec)', self.errmsg, re.I)])
+ self.wait(wait_time, True)
+
+ 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 'download files 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:
+ if self.passwords:
+ inputs['password'] = self.passwords.pop(0)
+ 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_challenge_field'], inputs['recaptcha_response_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_challenge'], inputs['adcopy_response'] = solvemedia.challenge(captcha_key)
+ return 4
+
+ return 0
diff --git a/pyload/plugins/internal/__init__.py b/pyload/plugins/internal/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/internal/__init__.py
diff --git a/pyload/plugins/ocr/GigasizeCom.py b/pyload/plugins/ocr/GigasizeCom.py
new file mode 100644
index 000000000..c8525d000
--- /dev/null
+++ b/pyload/plugins/ocr/GigasizeCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.OCR import OCR
+
+
+class GigasizeCom(OCR):
+ __name__ = "GigasizeCom"
+ __type__ = "ocr"
+ __version__ = "0.10"
+
+ __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/plugins/ocr/LinksaveIn.py b/pyload/plugins/ocr/LinksaveIn.py
new file mode 100644
index 000000000..d597efc36
--- /dev/null
+++ b/pyload/plugins/ocr/LinksaveIn.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from glob import glob
+from os import sep
+from os.path import abspath, dirname
+
+from pyload.plugins.internal.OCR import OCR
+
+
+class LinksaveIn(OCR):
+ __name__ = "LinksaveIn"
+ __type__ = "ocr"
+ __version__ = "0.10"
+
+ __description__ = """Linksave.in ocr plugin"""
+ __license__ = "GPLv3"
+ __authors__ = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+ self.data_dir = dirname(abspath(__file__)) + sep + "LinksaveIn" + 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(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/plugins/ocr/NetloadIn.py b/pyload/plugins/ocr/NetloadIn.py
new file mode 100644
index 000000000..2fce55d34
--- /dev/null
+++ b/pyload/plugins/ocr/NetloadIn.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.OCR import OCR
+
+
+class NetloadIn(OCR):
+ __name__ = "NetloadIn"
+ __type__ = "ocr"
+ __version__ = "0.10"
+
+ __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/plugins/ocr/ShareonlineBiz.py b/pyload/plugins/ocr/ShareonlineBiz.py
new file mode 100644
index 000000000..64753cf78
--- /dev/null
+++ b/pyload/plugins/ocr/ShareonlineBiz.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.OCR import OCR
+
+
+class ShareonlineBiz(OCR):
+ __name__ = "ShareonlineBiz"
+ __type__ = "ocr"
+ __version__ = "0.10"
+
+ __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/plugins/ocr/__init__.py b/pyload/plugins/ocr/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/ocr/__init__.py
diff --git a/pyload/remote/ClickAndLoadBackend.py b/pyload/remote/ClickAndLoadBackend.py
new file mode 100644
index 000000000..8c7906997
--- /dev/null
+++ b/pyload/remote/ClickAndLoadBackend.py
@@ -0,0 +1,156 @@
+# -*- 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.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/pyload/remote/SocketBackend.py b/pyload/remote/SocketBackend.py
new file mode 100644
index 000000000..c85e59f42
--- /dev/null
+++ b/pyload/remote/SocketBackend.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import SocketServer
+
+from pyload.manager.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/pyload/remote/ThriftBackend.py b/pyload/remote/ThriftBackend.py
new file mode 100644
index 000000000..7c76c4231
--- /dev/null
+++ b/pyload/remote/ThriftBackend.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from os.path import exists
+
+from pyload.manager.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/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..e69de29bb
--- /dev/null
+++ b/pyload/remote/socketbackend/__init__.py
diff --git a/pyload/remote/socketbackend/create_ttypes.py b/pyload/remote/socketbackend/create_ttypes.py
new file mode 100644
index 000000000..be1e2aaea
--- /dev/null
+++ b/pyload/remote/socketbackend/create_ttypes.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+import inspect
+import sys
+from os.path import abspath, dirname, join
+
+sys.path.append(join(pypath, "pyload", "lib"))
+sys.path.append(join(pypath, "pyload", "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(pypath, "pyload", "api", "types.py"), "wb")
+
+ 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..a8b87c82c
--- /dev/null
+++ b/pyload/remote/thriftbackend/Processor.py
@@ -0,0 +1,77 @@
+# -*- 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/pyload/remote/thriftbackend/Protocol.py b/pyload/remote/thriftbackend/Protocol.py
new file mode 100644
index 000000000..417b45834
--- /dev/null
+++ b/pyload/remote/thriftbackend/Protocol.py
@@ -0,0 +1,30 @@
+# -*- 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 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..f6edc8408
--- /dev/null
+++ b/pyload/remote/thriftbackend/Socket.py
@@ -0,0 +1,129 @@
+# -*- 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..d7b0d2919
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftClient.py
@@ -0,0 +1,87 @@
+# -*- 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(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..7243cc6f2
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftTest.py
@@ -0,0 +1,91 @@
+# -*- 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 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..7db4ba9d7
--- /dev/null
+++ b/pyload/remote/thriftbackend/Transport.py
@@ -0,0 +1,37 @@
+# -*- 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..e69de29bb
--- /dev/null
+++ b/pyload/remote/thriftbackend/__init__.py
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..e69de29bb
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/__init__.py
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..e8d2cf9af
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -0,0 +1,5533 @@
+#
+# 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..3bdd64cc1
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
@@ -0,0 +1,10 @@
+#
+# 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..67d23161d
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -0,0 +1,834 @@
+#
+# 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/JsEngine.py b/pyload/utils/JsEngine.py
new file mode 100644
index 000000000..c516dfc73
--- /dev/null
+++ b/pyload/utils/JsEngine.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+
+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): #: engine can be a jse name """string""" or an AbstractEngine """class"""
+
+ self.core = core
+ self.engine = None #: Default engine Instance
+
+ if not ENGINES:
+ self.core.log.critical("No JS Engine found!")
+ return
+
+ 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):
+ """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """
+ if 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:
+ JSE = None
+ elif issubclass(engine, AbstractEngine):
+ JSE = engine
+ else:
+ JSE = None
+ return JSE
+
+
+ def set(self, engine):
+ """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """
+ if isinstance(engine, basestring):
+ 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"""
+ if not engine:
+ JSE = self.engine
+ else:
+ JSE = self.get(engine)
+
+ if not JSE:
+ return None
+
+ 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):
+ self.setup()
+ self.available = self.find()
+
+ def setup(self):
+ pass
+
+ @classmethod
+ def find(cls):
+ """ Check if the engine is available """
+ try:
+ __import__(cls.NAME)
+ except ImportError:
+ try:
+ out, err = cls().eval("print(23+19)")
+ except Exception:
+ res = False
+ else:
+ res = out == "42"
+ else:
+ res = True
+ finally:
+ return res
+
+ def _eval(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(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
+ finally:
+ return res
+
+
+class CommonEngine(AbstractEngine):
+
+ NAME = "js"
+
+ def setup(self):
+ subprocess.Popen(["js", "-v"], bufsize=-1).communicate()
+
+ 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/utils/__init__.py b/pyload/utils/__init__.py
new file mode 100644
index 000000000..1c398224f
--- /dev/null
+++ b/pyload/utils/__init__.py
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+
+""" Store all useful functions here """
+
+import os
+import sys
+import time
+import re
+from os.path import join
+from string import maketrans
+from htmlentitydefs import name2codepoint
+
+# 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 = 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 safe_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 safe_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 save_path(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
+ else: return False
+
+
+def formatSize(size):
+ """formats size of bytes"""
+ size = int(size)
+ steps = 0
+ sizes = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB")
+ 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:
+ 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 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("."))))
diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py
new file mode 100644
index 000000000..d930157e1
--- /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 = ""
+
+ # fallback: package by hoster
+ if not name:
+ name = urlparse(file).hostname
+ 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..488f42d4a
--- /dev/null
+++ b/pyload/utils/printer.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+import colorama
+
+colorama.init(autoreset=True)
+
+def color(color, text):
+ return colorama.Fore.(c.upper())(text)
+
+for c in colorama.Fore:
+ eval("%(color) = lambda msg: color(%(color), 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..cab631cf4
--- /dev/null
+++ b/pyload/utils/pylgettext.py
@@ -0,0 +1,60 @@
+# -*- 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/pyload/webui/__init__.py b/pyload/webui/__init__.py
new file mode 100644
index 000000000..045c78e70
--- /dev/null
+++ b/pyload/webui/__init__.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+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 import InitHomeDir
+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 ServerThread
+from pyload.utils.JsEngine import JsEngine
+
+if not ServerThread.core:
+ if ServerThread.setup:
+ SETUP = ServerThread.setup
+ config = SETUP.config
+ JS = JsEngine(SETUP)
+ else:
+ raise Exception("Could not access pyLoad Core")
+else:
+ PYLOAD = ServerThread.core.api
+ config = ServerThread.core.config
+ JS = JsEngine(ServerThread.core)
+
+THEME = config.get('webinterface', 'theme')
+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 = FileSystemLoader(THEME_DIR)
+
+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..39d0fadd5
--- /dev/null
+++ b/pyload/webui/app/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from pyload.webui.app import api, cnl, json, pyload
diff --git a/pyload/webui/app/api.py b/pyload/webui/app/api.py
new file mode 100644
index 000000000..9726902fb
--- /dev/null
+++ b/pyload/webui/app/api.py
@@ -0,0 +1,101 @@
+# -*- 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 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
+ 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 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..e00e47f66
--- /dev/null
+++ b/pyload/webui/app/cnl.py
@@ -0,0 +1,167 @@
+# -*- 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 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', '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 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..096f72ab5
--- /dev/null
+++ b/pyload/webui/app/json.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+
+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 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)
+ destination = open(fpath, 'wb')
+ copyfileobj(f.file, destination)
+ destination.close()
+ 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", {"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/pyload.py b/pyload/webui/app/pyload.py
new file mode 100644
index 000000000..c5c6c80cd
--- /dev/null
+++ b/pyload/webui/app/pyload.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, SETUP, env
+
+from 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, safe_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, 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_static:re:.+\.js>')
+def server_js(theme, file):
+ response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
+
+ if "/render/" in file or ".render." in 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"
+
+ 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))
+
+
+@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(safe_join(root, item)):
+ folder = {
+ 'name': item,
+ 'path': item,
+ 'files': []
+ }
+ files = listdir(safe_join(root, item))
+ for file in sorted([fs_decode(x) for x in files]):
+ try:
+ if isfile(safe_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))
+
+ 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)
+
+ 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=""):
+ 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 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
+
+ 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 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 = 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 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},
+ [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():
+ return base([_("Run pyload.py -s to access the setup.")])
+
+
+@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/pyload/webui/app/utils.py b/pyload/webui/app/utils.py
new file mode 100644
index 000000000..fd0e14cfa
--- /dev/null
+++ b/pyload/webui/app/utils.py
@@ -0,0 +1,123 @@
+# -*- 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 = join(THEME, "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": 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/pyload/webui/filters.py b/pyload/webui/filters.py
new file mode 100644
index 000000000..527b18446
--- /dev/null
+++ b/pyload/webui/filters.py
@@ -0,0 +1,61 @@
+# -*- 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..5f56f81ee
--- /dev/null
+++ b/pyload/webui/middlewares.py
@@ -0,0 +1,132 @@
+# -*- 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)
diff --git a/pyload/webui/servers/lighttpd_default.conf b/pyload/webui/servers/lighttpd_default.conf
new file mode 100644
index 000000000..aa199b3f3
--- /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/bright/css/MooDialog.css b/pyload/webui/themes/bright/css/MooDialog.css
new file mode 100644
index 000000000..7d6e38ece
--- /dev/null
+++ b/pyload/webui/themes/bright/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(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/bright/css/bright.css b/pyload/webui/themes/bright/css/bright.css
new file mode 100644
index 000000000..fd73d0a19
--- /dev/null
+++ b/pyload/webui/themes/bright/css/bright.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/bright/css/log.css b/pyload/webui/themes/bright/css/log.css
new file mode 100644
index 000000000..c2f21b506
--- /dev/null
+++ b/pyload/webui/themes/bright/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/bright/css/pathchooser.css b/pyload/webui/themes/bright/css/pathchooser.css
new file mode 100644
index 000000000..204812aa1
--- /dev/null
+++ b/pyload/webui/themes/bright/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/bright/css/window.css b/pyload/webui/themes/bright/css/window.css
new file mode 100644
index 000000000..40c1aadca
--- /dev/null
+++ b/pyload/webui/themes/bright/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/bright/img/MooDialog/dialog-close.png b/pyload/webui/themes/bright/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/bright/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/MooDialog/dialog-error.png b/pyload/webui/themes/bright/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/bright/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/MooDialog/dialog-question.png b/pyload/webui/themes/bright/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/bright/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/MooDialog/dialog-warning.png b/pyload/webui/themes/bright/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/bright/img/MooDialog/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/arrow_refresh.png b/pyload/webui/themes/bright/img/arrow_refresh.png
new file mode 100644
index 000000000..b1b6fa4dc
--- /dev/null
+++ b/pyload/webui/themes/bright/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/arrow_right.png b/pyload/webui/themes/bright/img/arrow_right.png
new file mode 100644
index 000000000..68f379fc7
--- /dev/null
+++ b/pyload/webui/themes/bright/img/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/button.png b/pyload/webui/themes/bright/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/bright/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/cog.png b/pyload/webui/themes/bright/img/cog.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/bright/img/cog.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_add.png b/pyload/webui/themes/bright/img/control_add.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_add_blue.png b/pyload/webui/themes/bright/img/control_add_blue.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_cancel.png b/pyload/webui/themes/bright/img/control_cancel.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_cancel_blue.png b/pyload/webui/themes/bright/img/control_cancel_blue.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_pause.png b/pyload/webui/themes/bright/img/control_pause.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_pause_blue.png b/pyload/webui/themes/bright/img/control_pause_blue.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_play.png b/pyload/webui/themes/bright/img/control_play.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_play_blue.png b/pyload/webui/themes/bright/img/control_play_blue.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_stop.png b/pyload/webui/themes/bright/img/control_stop.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/control_stop_blue.png b/pyload/webui/themes/bright/img/control_stop_blue.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/bright/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/add_folder.png b/pyload/webui/themes/bright/img/default/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/ajax-loader.gif b/pyload/webui/themes/bright/img/default/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/big_button.gif b/pyload/webui/themes/bright/img/default/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/big_button_over.gif b/pyload/webui/themes/bright/img/default/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/body.png b/pyload/webui/themes/bright/img/default/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/body.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/closebtn.gif b/pyload/webui/themes/bright/img/default/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/drag_corner.gif b/pyload/webui/themes/bright/img/default/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/full.png b/pyload/webui/themes/bright/img/default/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/full.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/head-menu-recent.png b/pyload/webui/themes/bright/img/default/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/head_bg1.png b/pyload/webui/themes/bright/img/default/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/images.png b/pyload/webui/themes/bright/img/default/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/images.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/parseUri.png b/pyload/webui/themes/bright/img/default/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/pyload-logo.png b/pyload/webui/themes/bright/img/default/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/tab-background.png b/pyload/webui/themes/bright/img/default/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/default/tabs-border-bottom.png b/pyload/webui/themes/bright/img/default/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/bright/img/default/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/delete.png b/pyload/webui/themes/bright/img/delete.png
new file mode 100644
index 000000000..4539cff12
--- /dev/null
+++ b/pyload/webui/themes/bright/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/error.png b/pyload/webui/themes/bright/img/error.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/bright/img/error.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/folder.png b/pyload/webui/themes/bright/img/folder.png
new file mode 100644
index 000000000..0b067dd3c
--- /dev/null
+++ b/pyload/webui/themes/bright/img/folder.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-login.png b/pyload/webui/themes/bright/img/head-login.png
new file mode 100644
index 000000000..6b57515bc
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-collector.png b/pyload/webui/themes/bright/img/head-menu-collector.png
new file mode 100644
index 000000000..bbcbe6406
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-config.png b/pyload/webui/themes/bright/img/head-menu-config.png
new file mode 100644
index 000000000..93e8f83ac
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-development.png b/pyload/webui/themes/bright/img/head-menu-development.png
new file mode 100644
index 000000000..33d8b062f
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-download.png b/pyload/webui/themes/bright/img/head-menu-download.png
new file mode 100644
index 000000000..3691deebc
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-home.png b/pyload/webui/themes/bright/img/head-menu-home.png
new file mode 100644
index 000000000..b77bef5eb
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-index.png b/pyload/webui/themes/bright/img/head-menu-index.png
new file mode 100644
index 000000000..8bc6e9604
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-news.png b/pyload/webui/themes/bright/img/head-menu-news.png
new file mode 100644
index 000000000..44e79a9a9
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-queue.png b/pyload/webui/themes/bright/img/head-menu-queue.png
new file mode 100644
index 000000000..e4fa41ad8
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-menu-wiki.png b/pyload/webui/themes/bright/img/head-menu-wiki.png
new file mode 100644
index 000000000..61b0e54ea
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/head-search-noshadow.png b/pyload/webui/themes/bright/img/head-search-noshadow.png
new file mode 100644
index 000000000..16d39bd06
--- /dev/null
+++ b/pyload/webui/themes/bright/img/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/notice.png b/pyload/webui/themes/bright/img/notice.png
new file mode 100644
index 000000000..a52b067cb
--- /dev/null
+++ b/pyload/webui/themes/bright/img/notice.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/package_go.png b/pyload/webui/themes/bright/img/package_go.png
new file mode 100644
index 000000000..80b2c42ee
--- /dev/null
+++ b/pyload/webui/themes/bright/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/page-tools-backlinks.png b/pyload/webui/themes/bright/img/page-tools-backlinks.png
new file mode 100644
index 000000000..fb8f55b38
--- /dev/null
+++ b/pyload/webui/themes/bright/img/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/page-tools-edit.png b/pyload/webui/themes/bright/img/page-tools-edit.png
new file mode 100644
index 000000000..67177cf89
--- /dev/null
+++ b/pyload/webui/themes/bright/img/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/page-tools-revisions.png b/pyload/webui/themes/bright/img/page-tools-revisions.png
new file mode 100644
index 000000000..088fe0087
--- /dev/null
+++ b/pyload/webui/themes/bright/img/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/pencil.png b/pyload/webui/themes/bright/img/pencil.png
new file mode 100644
index 000000000..e39c93cd8
--- /dev/null
+++ b/pyload/webui/themes/bright/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/reconnect.png b/pyload/webui/themes/bright/img/reconnect.png
new file mode 100644
index 000000000..cd35c9325
--- /dev/null
+++ b/pyload/webui/themes/bright/img/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_None.png b/pyload/webui/themes/bright/img/status_None.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_downloading.png b/pyload/webui/themes/bright/img/status_downloading.png
new file mode 100644
index 000000000..db8ad8cd6
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_failed.png b/pyload/webui/themes/bright/img/status_failed.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_finished.png b/pyload/webui/themes/bright/img/status_finished.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_offline.png b/pyload/webui/themes/bright/img/status_offline.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_proc.png b/pyload/webui/themes/bright/img/status_proc.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_queue.png b/pyload/webui/themes/bright/img/status_queue.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/status_waiting.png b/pyload/webui/themes/bright/img/status_waiting.png
new file mode 100644
index 000000000..fd038175e
--- /dev/null
+++ b/pyload/webui/themes/bright/img/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/success.png b/pyload/webui/themes/bright/img/success.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/bright/img/success.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/user-actions-logout.png b/pyload/webui/themes/bright/img/user-actions-logout.png
new file mode 100644
index 000000000..d4ef360e8
--- /dev/null
+++ b/pyload/webui/themes/bright/img/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/user-actions-profile.png b/pyload/webui/themes/bright/img/user-actions-profile.png
new file mode 100644
index 000000000..9ec410b13
--- /dev/null
+++ b/pyload/webui/themes/bright/img/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/bright/img/user-info.png b/pyload/webui/themes/bright/img/user-info.png
new file mode 100644
index 000000000..345ed52e4
--- /dev/null
+++ b/pyload/webui/themes/bright/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/bright/js/render/admin.coffee b/pyload/webui/themes/bright/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/admin.min.js b/pyload/webui/themes/bright/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/base.coffee b/pyload/webui/themes/bright/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/base.min.js b/pyload/webui/themes/bright/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/filemanager.js b/pyload/webui/themes/bright/js/render/filemanager.js
new file mode 100644
index 000000000..ab9b8b492
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/package.js b/pyload/webui/themes/bright/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/settings.coffee b/pyload/webui/themes/bright/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/render/settings.min.js b/pyload/webui/themes/bright/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/bright/js/render/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/bright/js/static/MooDialog.js b/pyload/webui/themes/bright/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/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/bright/js/static/MooDialog.min.js b/pyload/webui/themes/bright/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,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")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/bright/js/static/MooDropMenu.js b/pyload/webui/themes/bright/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/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/bright/js/static/MooDropMenu.min.js b/pyload/webui/themes/bright/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/bright/js/static/mootools-core.js b/pyload/webui/themes/bright/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+...
+*/
+
+/*
+---
+
+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]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// 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: 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: 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: 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: 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: 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';
+ }
+
+ var 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.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;
+ };
+ 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: 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 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 == '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 == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ 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: 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: 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;
+ 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>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ 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];
+ };
+});
+
+// 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>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</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 this.className.clean().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('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+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){
+ 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: 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;
+
+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();
+ 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: '@'
+};
+
+
+
+
+
+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.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
+ 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 //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--;){
+ 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.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 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();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ 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.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: 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: 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: '',*/
+ 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;
+ 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 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.user && '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();
+ clearTimeout(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', 'GET', 'POST', 'PUT', 'DELETE'].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) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+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/bright/js/static/mootools-core.min.js b/pyload/webui/themes/bright/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){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("callee" in i){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;}if(!t.hasOwnProperty){return false;}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(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}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,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("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",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,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}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 parseInt(c,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({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).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(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 f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.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,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.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"||i=="keyup"){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(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};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=this.getAttribute(o,"class");
+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={"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.7";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=this.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;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}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={"$constructor":Element,"$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 f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);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(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},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(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});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:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(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,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};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));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}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 b.type!="propertychange"||b.event.propertyName=="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",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},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(n.condition||w&&w.condition){var l=q,m=n.condition||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,l.capture);}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(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}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.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});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(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},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 d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});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(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}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.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+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();}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 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.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+")");};})();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); \ No newline at end of file
diff --git a/pyload/webui/themes/bright/js/static/mootools-more.js b/pyload/webui/themes/bright/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+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.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+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;
+
+};
+
+
+/*
+---
+
+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: 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: 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('&');
+ }
+
+});
+
+
+/*
+---
+
+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);
+});
+
+})();
+
+
+/*
+---
+
+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));
+
+})();
+
+
+/*
+---
+
+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: 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: 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: 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,
+ 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.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ 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)
+ };
+ 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(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ 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(this.element.getOffsetParent());
+ 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 = event.page;
+
+ 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];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ 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);
+ 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 = {};
+
+ ['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,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ 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: 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;
+ }
+
+});
+
+
+/*
+---
+
+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: 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;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/bright/js/static/mootools-more.min.js b/pyload/webui/themes/bright/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - 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.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};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};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();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);(function(){var a=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:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.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.target.set("aria-busy","true");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.target.set("aria-busy","false");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);b.validate(a);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:"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 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="selectstart" in document?"selectstart":"mousedown";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)};
+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.setContainer(this.options.container);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;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},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;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}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:{},unDraggableTags:["button","input","a","textarea","select","option"]},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));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},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(this.getDroppableCoordinates(this.element));},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||(!this.options.handle&&this.options.unDraggableTags.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);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.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/pyload/webui/themes/bright/js/static/purr.js b/pyload/webui/themes/bright/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/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/bright/js/static/purr.min.js b/pyload/webui/themes/bright/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/purr.min.js
@@ -0,0 +1 @@
+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:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/bright/js/static/tinytab.js b/pyload/webui/themes/bright/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/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/bright/js/static/tinytab.min.js b/pyload/webui/themes/bright/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/bright/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/admin.html b/pyload/webui/themes/bright/tml/admin.html
new file mode 100644
index 000000000..99e9b003f
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/bright/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/bright/js/render/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/bright/tml/base.html b/pyload/webui/themes/bright/tml/base.html
new file mode 100644
index 000000000..274dbe82c
--- /dev/null
+++ b/pyload/webui/themes/bright/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="/bright/css/bright.min.css"/>
+<link rel="stylesheet" type="text/css" href="/bright/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/bright/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/bright/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/bright/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/bright/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/bright/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/bright/js/render/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="/bright/img/default/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="/bright/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="/bright/img/default/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="/bright/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/bright/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/bright/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/bright/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('filemanager') }}>
+ <a href="/filemanager/" title=""><img src="/bright/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/bright/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/bright/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="/bright/img/default/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 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 '/bright/tml/window.html' %}
+ {% include '/bright/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/bright/tml/captcha.html b/pyload/webui/themes/bright/tml/captcha.html
new file mode 100644
index 000000000..731d97d11
--- /dev/null
+++ b/pyload/webui/themes/bright/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> \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/downloads.html b/pyload/webui/themes/bright/tml/downloads.html
new file mode 100644
index 000000000..2c62f3d49
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/bright/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/filemanager.html b/pyload/webui/themes/bright/tml/filemanager.html
new file mode 100644
index 000000000..bda03affe
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/filemanager.html
@@ -0,0 +1,78 @@
+{% extends '/bright/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/bright/js/render/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="/bright/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/bright/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="/bright/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/bright/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/bright/img/default/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/bright/tml/folder.html b/pyload/webui/themes/bright/tml/folder.html
new file mode 100644
index 000000000..b311ed5e9
--- /dev/null
+++ b/pyload/webui/themes/bright/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="/bright/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/bright/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/bright/img/default/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li> \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/home.html b/pyload/webui/themes/bright/tml/home.html
new file mode 100644
index 000000000..151ca7229
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/home.html
@@ -0,0 +1,266 @@
+{% extends '/bright/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': '/bright/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="/bright/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/bright/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/bright/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/bright/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li>
+ <a href="/filemanager/" title=""><img src="/bright/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/bright/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/bright/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="/bright/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/pyload/webui/themes/bright/tml/info.html b/pyload/webui/themes/bright/tml/info.html
new file mode 100644
index 000000000..d08ba349c
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/bright/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/login.html b/pyload/webui/themes/bright/tml/login.html
new file mode 100644
index 000000000..cafa6a6f0
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/bright/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/bright/tml/logout.html b/pyload/webui/themes/bright/tml/logout.html
new file mode 100644
index 000000000..c886d86a1
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/bright/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/logs.html b/pyload/webui/themes/bright/tml/logs.html
new file mode 100644
index 000000000..5f1223e93
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/bright/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/bright/css/log.min.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/pyload/webui/themes/bright/tml/pathchooser.html b/pyload/webui/themes/bright/tml/pathchooser.html
new file mode 100644
index 000000000..9561972e7
--- /dev/null
+++ b/pyload/webui/themes/bright/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="/bright/css/pathchooser.min.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/pyload/webui/themes/bright/tml/queue.html b/pyload/webui/themes/bright/tml/queue.html
new file mode 100644
index 000000000..12da7f2f3
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/bright/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/bright/js/render/package.min.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="/bright/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="/bright/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/bright/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/bright/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/bright/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/settings.html b/pyload/webui/themes/bright/tml/settings.html
new file mode 100644
index 000000000..3c027904d
--- /dev/null
+++ b/pyload/webui/themes/bright/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/bright/tml/base.html' %}
+
+{% block title %}{{ _("Settings") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Settings") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/bright/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/bright/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/bright/js/render/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/bright/tml/settings_item.html b/pyload/webui/themes/bright/tml/settings_item.html
new file mode 100644
index 000000000..813383343
--- /dev/null
+++ b/pyload/webui/themes/bright/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 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/pyload/webui/themes/bright/tml/window.html b/pyload/webui/themes/bright/tml/window.html
new file mode 100644
index 000000000..29f8b1945
--- /dev/null
+++ b/pyload/webui/themes/bright/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="/bright/img/default/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/pyload/webui/themes/dark/css/MooDialog.css b/pyload/webui/themes/dark/css/MooDialog.css
new file mode 100644
index 000000000..1714e170f
--- /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(../img/MooDialog/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(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+ color:white;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/dark/css/dark.css b/pyload/webui/themes/dark/css/dark.css
new file mode 100644
index 000000000..f0bae8f24
--- /dev/null
+++ b/pyload/webui/themes/dark/css/dark.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/default/user-actions-logout.png) 0px 1px no-repeat;
+}
+
+a.info {
+ background:transparent url(../img/default/user-info.png) 0px 1px no-repeat;
+}
+
+a.admin {
+ background:transparent url(../img/default/user-actions-admin.png) 0px 1px no-repeat;
+}
+a.profile {
+ background:transparent url(../img/default/user-actions-profile.png) 0px 1px no-repeat;
+}
+a.create, a.edit {
+ background:transparent url(../img/default/page-tools-edit.png) 0px 1px no-repeat;
+}
+a.source, a.show {
+ background:transparent url(../img/default/page-tools-source.png) 0px 1px no-repeat;
+}
+a.revisions {
+ background:transparent url(../img/default/page-tools-revisions.png) 0px 1px no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background:transparent url(../img/default/page-tools-subscribe.png) 0px 1px no-repeat;
+}
+a.backlink {
+ background:transparent url(../img/default/page-tools-backlinks.png) 0px 1px no-repeat;
+}
+a.play {
+ background:transparent url(../img/default/control_play.png) 0px 1px no-repeat;
+}
+.time {
+ background:transparent url(../img/default/status_None.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+.reconnect {
+ background:transparent url(../img/default/reconnect.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+a.play:hover {
+ background:transparent url(../img/default/control_play_blue.png) 0px 1px no-repeat;
+}
+a.cancel {
+ background:transparent url(../img/default/control_cancel.png) 0px 1px no-repeat;
+}
+a.cancel:hover {
+ background:transparent url(../img/default/control_cancel_blue.png) 0px 1px no-repeat;
+}
+a.pause {
+ background:transparent url(../img/default/control_pause.png) 0px 1px no-repeat;
+}
+a.pause:hover {
+ background:transparent url(../img/default/control_pause_blue.png) 0px 1px no-repeat;
+ font-weight: bold;
+}
+a.stop {
+ background:transparent url(../img/default/control_stop.png) 0px 1px no-repeat;
+}
+a.stop:hover {
+ background:transparent url(../img/default/control_stop_blue.png) 0px 1px no-repeat;
+}
+a.add {
+ background:transparent url(../img/default/control_add.png) 0px 1px no-repeat;
+}
+a.add:hover {
+ background:transparent url(../img/default/control_add_blue.png) 0px 1px no-repeat;
+}
+a.cog {
+ background:transparent url(../img/default/cog.png) 0px 1px no-repeat;
+}
+#head-panel {
+ background:#525252 url(../img/default/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/default/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/default/error.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.success{
+ color:#5F5;
+ padding-left:30px;
+ background:url(../img/default/success.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.notice{
+ color:#99F;
+ padding-left:30px;
+ background:url(../img/default/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/pyload/webui/themes/dark/img/MooDialog/dialog-close.png b/pyload/webui/themes/dark/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/MooDialog/dialog-error.png b/pyload/webui/themes/dark/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/MooDialog/dialog-question.png b/pyload/webui/themes/dark/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/MooDialog/dialog-warning.png b/pyload/webui/themes/dark/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-warning.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/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/pyload/webui/themes/dark/img/default/add_folder.png b/pyload/webui/themes/dark/img/default/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/ajax-loader.gif b/pyload/webui/themes/dark/img/default/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/arrow_refresh.png b/pyload/webui/themes/dark/img/default/arrow_refresh.png
new file mode 100644
index 000000000..0de26566d
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/arrow_right.png b/pyload/webui/themes/dark/img/default/arrow_right.png
new file mode 100644
index 000000000..b1a181923
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/big_button.gif b/pyload/webui/themes/dark/img/default/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/big_button_over.gif b/pyload/webui/themes/dark/img/default/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/body.png b/pyload/webui/themes/dark/img/default/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/body.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/closebtn.gif b/pyload/webui/themes/dark/img/default/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/cog.png b/pyload/webui/themes/dark/img/default/cog.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/cog.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_add.png b/pyload/webui/themes/dark/img/default/control_add.png
new file mode 100644
index 000000000..d39886893
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_add_blue.png b/pyload/webui/themes/dark/img/default/control_add_blue.png
new file mode 100644
index 000000000..d11b7f41d
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_cancel.png b/pyload/webui/themes/dark/img/default/control_cancel.png
new file mode 100644
index 000000000..7b9bc3fba
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_cancel_blue.png b/pyload/webui/themes/dark/img/default/control_cancel_blue.png
new file mode 100644
index 000000000..0c5c96ce3
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_pause.png b/pyload/webui/themes/dark/img/default/control_pause.png
new file mode 100644
index 000000000..2d9ce9c4e
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_pause_blue.png b/pyload/webui/themes/dark/img/default/control_pause_blue.png
new file mode 100644
index 000000000..ec61099b0
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_play.png b/pyload/webui/themes/dark/img/default/control_play.png
new file mode 100644
index 000000000..0846555d0
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_play_blue.png b/pyload/webui/themes/dark/img/default/control_play_blue.png
new file mode 100644
index 000000000..f8c8ec683
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_stop.png b/pyload/webui/themes/dark/img/default/control_stop.png
new file mode 100644
index 000000000..893bb60e5
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/control_stop_blue.png b/pyload/webui/themes/dark/img/default/control_stop_blue.png
new file mode 100644
index 000000000..e6f75d232
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/delete.png b/pyload/webui/themes/dark/img/default/delete.png
new file mode 100644
index 000000000..08f249365
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/delete.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/drag_corner.gif b/pyload/webui/themes/dark/img/default/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/error.png b/pyload/webui/themes/dark/img/default/error.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/error.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/folder.png b/pyload/webui/themes/dark/img/default/folder.png
new file mode 100644
index 000000000..784e8fa48
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/folder.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/full.png b/pyload/webui/themes/dark/img/default/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/full.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-login.png b/pyload/webui/themes/dark/img/default/head-login.png
new file mode 100644
index 000000000..b59b7cbbf
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-collector.png b/pyload/webui/themes/dark/img/default/head-menu-collector.png
new file mode 100644
index 000000000..861be40bc
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-config.png b/pyload/webui/themes/dark/img/default/head-menu-config.png
new file mode 100644
index 000000000..bbf43d4f3
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-development.png b/pyload/webui/themes/dark/img/default/head-menu-development.png
new file mode 100644
index 000000000..fad150fe1
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-download.png b/pyload/webui/themes/dark/img/default/head-menu-download.png
new file mode 100644
index 000000000..98c5da9db
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-home.png b/pyload/webui/themes/dark/img/default/head-menu-home.png
new file mode 100644
index 000000000..9d62109aa
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-index.png b/pyload/webui/themes/dark/img/default/head-menu-index.png
new file mode 100644
index 000000000..44d631064
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-news.png b/pyload/webui/themes/dark/img/default/head-menu-news.png
new file mode 100644
index 000000000..43950ebc9
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-queue.png b/pyload/webui/themes/dark/img/default/head-menu-queue.png
new file mode 100644
index 000000000..be98793ce
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-recent.png b/pyload/webui/themes/dark/img/default/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-menu-wiki.png b/pyload/webui/themes/dark/img/default/head-menu-wiki.png
new file mode 100644
index 000000000..07cf0102d
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head-search-noshadow.png b/pyload/webui/themes/dark/img/default/head-search-noshadow.png
new file mode 100644
index 000000000..aafdae015
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/head_bg1.png b/pyload/webui/themes/dark/img/default/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/images.png b/pyload/webui/themes/dark/img/default/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/images.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/notice.png b/pyload/webui/themes/dark/img/default/notice.png
new file mode 100644
index 000000000..12cd1aef9
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/notice.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/package_go.png b/pyload/webui/themes/dark/img/default/package_go.png
new file mode 100644
index 000000000..aace63ad6
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/page-tools-backlinks.png b/pyload/webui/themes/dark/img/default/page-tools-backlinks.png
new file mode 100644
index 000000000..3eb6a9ce3
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/page-tools-edit.png b/pyload/webui/themes/dark/img/default/page-tools-edit.png
new file mode 100644
index 000000000..188e1c12b
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/page-tools-revisions.png b/pyload/webui/themes/dark/img/default/page-tools-revisions.png
new file mode 100644
index 000000000..5c3b8587f
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/parseUri.png b/pyload/webui/themes/dark/img/default/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/pencil.png b/pyload/webui/themes/dark/img/default/pencil.png
new file mode 100644
index 000000000..0bfecd50e
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/reconnect.png b/pyload/webui/themes/dark/img/default/reconnect.png
new file mode 100644
index 000000000..49b269145
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_None.png b/pyload/webui/themes/dark/img/default/status_None.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_downloading.png b/pyload/webui/themes/dark/img/default/status_downloading.png
new file mode 100644
index 000000000..fb4ebc850
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_failed.png b/pyload/webui/themes/dark/img/default/status_failed.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_finished.png b/pyload/webui/themes/dark/img/default/status_finished.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_offline.png b/pyload/webui/themes/dark/img/default/status_offline.png
new file mode 100644
index 000000000..0cfd58596
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_proc.png b/pyload/webui/themes/dark/img/default/status_proc.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_queue.png b/pyload/webui/themes/dark/img/default/status_queue.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/status_waiting.png b/pyload/webui/themes/dark/img/default/status_waiting.png
new file mode 100644
index 000000000..2842cc338
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/success.png b/pyload/webui/themes/dark/img/default/success.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/success.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/tabs-border-bottom.png b/pyload/webui/themes/dark/img/default/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/user-actions-logout.png b/pyload/webui/themes/dark/img/default/user-actions-logout.png
new file mode 100644
index 000000000..0010931e2
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/user-actions-profile.png b/pyload/webui/themes/dark/img/default/user-actions-profile.png
new file mode 100644
index 000000000..46573fff6
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/default/user-info.png b/pyload/webui/themes/dark/img/default/user-info.png
new file mode 100644
index 000000000..6e643100f
--- /dev/null
+++ b/pyload/webui/themes/dark/img/default/user-info.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/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/pyload/webui/themes/dark/js/render/admin.coffee b/pyload/webui/themes/dark/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/admin.min.js b/pyload/webui/themes/dark/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/base.coffee b/pyload/webui/themes/dark/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/base.min.js b/pyload/webui/themes/dark/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/package.js b/pyload/webui/themes/dark/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/settings.coffee b/pyload/webui/themes/dark/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/render/settings.min.js b/pyload/webui/themes/dark/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/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/js/static/MooDialog.js b/pyload/webui/themes/dark/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/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/js/static/MooDialog.min.js b/pyload/webui/themes/dark/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,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")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/MooDropMenu.js b/pyload/webui/themes/dark/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/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/js/static/MooDropMenu.min.js b/pyload/webui/themes/dark/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/mootools-core.js b/pyload/webui/themes/dark/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+...
+*/
+
+/*
+---
+
+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]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// 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: 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: 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: 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: 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: 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';
+ }
+
+ var 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.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;
+ };
+ 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: 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 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 == '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 == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ 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: 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: 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;
+ 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>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ 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];
+ };
+});
+
+// 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>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</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 this.className.clean().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('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+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){
+ 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: 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;
+
+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();
+ 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: '@'
+};
+
+
+
+
+
+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.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
+ 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 //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--;){
+ 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.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 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();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ 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.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: 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: 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: '',*/
+ 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;
+ 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 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.user && '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();
+ clearTimeout(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', 'GET', 'POST', 'PUT', 'DELETE'].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) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+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/js/static/mootools-core.min.js b/pyload/webui/themes/dark/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){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("callee" in i){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;}if(!t.hasOwnProperty){return false;}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(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}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,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("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",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,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}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 parseInt(c,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({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).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(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 f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.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,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.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"||i=="keyup"){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(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};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=this.getAttribute(o,"class");
+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={"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.7";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=this.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;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}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={"$constructor":Element,"$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 f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);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(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},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(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});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:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(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,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};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));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}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 b.type!="propertychange"||b.event.propertyName=="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",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},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(n.condition||w&&w.condition){var l=q,m=n.condition||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,l.capture);}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(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}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.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});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(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},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 d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});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(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}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.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+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();}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 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.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+")");};})();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); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/mootools-more.js b/pyload/webui/themes/dark/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+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.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+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;
+
+};
+
+
+/*
+---
+
+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: 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: 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('&');
+ }
+
+});
+
+
+/*
+---
+
+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);
+});
+
+})();
+
+
+/*
+---
+
+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));
+
+})();
+
+
+/*
+---
+
+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: 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: 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: 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,
+ 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.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ 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)
+ };
+ 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(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ 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(this.element.getOffsetParent());
+ 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 = event.page;
+
+ 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];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ 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);
+ 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 = {};
+
+ ['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,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ 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: 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;
+ }
+
+});
+
+
+/*
+---
+
+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: 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;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/dark/js/static/mootools-more.min.js b/pyload/webui/themes/dark/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - 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.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};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};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();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);(function(){var a=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:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.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.target.set("aria-busy","true");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.target.set("aria-busy","false");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);b.validate(a);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:"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 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="selectstart" in document?"selectstart":"mousedown";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)};
+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.setContainer(this.options.container);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;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},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;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}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:{},unDraggableTags:["button","input","a","textarea","select","option"]},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));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},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(this.getDroppableCoordinates(this.element));},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||(!this.options.handle&&this.options.unDraggableTags.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);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.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/pyload/webui/themes/dark/js/static/purr.js b/pyload/webui/themes/dark/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/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/js/static/purr.min.js b/pyload/webui/themes/dark/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/purr.min.js
@@ -0,0 +1 @@
+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:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/tinytab.js b/pyload/webui/themes/dark/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/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/js/static/tinytab.min.js b/pyload/webui/themes/dark/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(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..42118eda4
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/dark/js/render/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..bafcc319d
--- /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="/dark/css/dark.min.css"/>
+<link rel="stylesheet" type="text/css" href="/dark/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/dark/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/dark/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/dark/js/render/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="/dark/img/default/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="/dark/img/default/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="/dark/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="/dark/img/default/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/dark/img/default/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/dark/img/default/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/dark/img/default/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/dark/img/default/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/dark/img/default/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="/dark/img/default/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 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 '/dark/tml/window.html' %}
+ {% include '/dark/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..731d97d11
--- /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> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/downloads.html b/pyload/webui/themes/dark/tml/downloads.html
new file mode 100644
index 000000000..0c7fb9209
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/dark/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/folder.html b/pyload/webui/themes/dark/tml/folder.html
new file mode 100644
index 000000000..05176d51e
--- /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="/dark/img/default/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/dark/img/default/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/dark/img/default/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/home.html b/pyload/webui/themes/dark/tml/home.html
new file mode 100644
index 000000000..6c2260b83
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/dark/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': '/dark/img/default/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="/dark/img/default/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/dark/img/default/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/dark/img/default/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/dark/img/default/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/dark/img/default/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/dark/img/default/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="/dark/img/default/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/pyload/webui/themes/dark/tml/info.html b/pyload/webui/themes/dark/tml/info.html
new file mode 100644
index 000000000..7ff2b639b
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/info.html
@@ -0,0 +1,76 @@
+{% extends '/dark/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/login.html b/pyload/webui/themes/dark/tml/login.html
new file mode 100644
index 000000000..9f5e2cb2f
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/login.html
@@ -0,0 +1,37 @@
+{% extends '/dark/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..5320e07f5
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/dark/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/logs.html b/pyload/webui/themes/dark/tml/logs.html
new file mode 100644
index 000000000..e178c6c5c
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/dark/css/log.min.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/pyload/webui/themes/dark/tml/pathchooser.html b/pyload/webui/themes/dark/tml/pathchooser.html
new file mode 100644
index 000000000..2b94f1019
--- /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="/dark/css/pathchooser.min.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/pyload/webui/themes/dark/tml/queue.html b/pyload/webui/themes/dark/tml/queue.html
new file mode 100644
index 000000000..f68079106
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/dark/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/dark/js/render/package.min.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="/dark/img/default/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="/dark/img/default/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/settings.html b/pyload/webui/themes/dark/tml/settings.html
new file mode 100644
index 000000000..c9c0bed8a
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/dark/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/dark/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/dark/js/render/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 %} \ No newline at end of file
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..e417e564c
--- /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 section.iteritems() %}
+ {% 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> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/window.html b/pyload/webui/themes/dark/tml/window.html
new file mode 100644
index 000000000..0b4f5362b
--- /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="/dark/img/default/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> \ No newline at end of file
diff --git a/pyload/webui/themes/default/css/MooDialog.css b/pyload/webui/themes/default/css/MooDialog.css
new file mode 100644
index 000000000..1e2ae68aa
--- /dev/null
+++ b/pyload/webui/themes/default/css/MooDialog.css
@@ -0,0 +1,91 @@
+/* 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(../img/MooDialog/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(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/default/css/default.css b/pyload/webui/themes/default/css/default.css
new file mode 100644
index 000000000..f5078bfbb
--- /dev/null
+++ b/pyload/webui/themes/default/css/default.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/MooDialog/dialog-close.png b/pyload/webui/themes/default/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-error.png b/pyload/webui/themes/default/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-question.png b/pyload/webui/themes/default/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-warning.png b/pyload/webui/themes/default/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-warning.png
Binary files differ
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/pyload/webui/themes/default/img/button.png b/pyload/webui/themes/default/img/button.png
new file mode 100644
index 000000000..890160614
--- /dev/null
+++ 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/pyload/webui/themes/default/img/pyload-logo.png b/pyload/webui/themes/default/img/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ 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/pyload/webui/themes/default/img/tab-background.png b/pyload/webui/themes/default/img/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ 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/render/admin.coffee b/pyload/webui/themes/default/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/admin.min.js b/pyload/webui/themes/default/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/base.coffee b/pyload/webui/themes/default/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/base.min.js b/pyload/webui/themes/default/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/package.js b/pyload/webui/themes/default/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/settings.coffee b/pyload/webui/themes/default/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/render/settings.min.js b/pyload/webui/themes/default/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/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/js/static/MooDialog.js b/pyload/webui/themes/default/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/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/js/static/MooDialog.min.js b/pyload/webui/themes/default/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,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")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/MooDropMenu.js b/pyload/webui/themes/default/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/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/js/static/MooDropMenu.min.js b/pyload/webui/themes/default/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/mootools-core.js b/pyload/webui/themes/default/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+...
+*/
+
+/*
+---
+
+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]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// 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: 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: 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: 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: 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: 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';
+ }
+
+ var 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.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;
+ };
+ 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: 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 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 == '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 == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ 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: 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: 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;
+ 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>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ 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];
+ };
+});
+
+// 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>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</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 this.className.clean().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('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+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){
+ 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: 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;
+
+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();
+ 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: '@'
+};
+
+
+
+
+
+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.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
+ 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 //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--;){
+ 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.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 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();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ 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.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: 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: 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: '',*/
+ 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;
+ 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 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.user && '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();
+ clearTimeout(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', 'GET', 'POST', 'PUT', 'DELETE'].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) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+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/js/static/mootools-core.min.js b/pyload/webui/themes/default/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){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("callee" in i){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;}if(!t.hasOwnProperty){return false;}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(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}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,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("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",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,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}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 parseInt(c,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({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).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(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 f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.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,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.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"||i=="keyup"){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(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};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=this.getAttribute(o,"class");
+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={"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.7";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=this.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;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}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={"$constructor":Element,"$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 f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);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(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},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(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});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:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(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,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};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));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}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 b.type!="propertychange"||b.event.propertyName=="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",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},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(n.condition||w&&w.condition){var l=q,m=n.condition||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,l.capture);}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(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}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.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});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(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},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 d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});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(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}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.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+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();}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 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.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+")");};})();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); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/mootools-more.js b/pyload/webui/themes/default/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+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.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+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;
+
+};
+
+
+/*
+---
+
+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: 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: 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('&');
+ }
+
+});
+
+
+/*
+---
+
+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);
+});
+
+})();
+
+
+/*
+---
+
+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));
+
+})();
+
+
+/*
+---
+
+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: 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: 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: 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,
+ 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.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ 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)
+ };
+ 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(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ 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(this.element.getOffsetParent());
+ 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 = event.page;
+
+ 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];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ 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);
+ 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 = {};
+
+ ['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,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ 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: 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;
+ }
+
+});
+
+
+/*
+---
+
+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: 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;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/default/js/static/mootools-more.min.js b/pyload/webui/themes/default/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - 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.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};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};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();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);(function(){var a=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:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.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.target.set("aria-busy","true");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.target.set("aria-busy","false");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);b.validate(a);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:"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 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="selectstart" in document?"selectstart":"mousedown";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)};
+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.setContainer(this.options.container);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;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},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;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}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:{},unDraggableTags:["button","input","a","textarea","select","option"]},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));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},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(this.getDroppableCoordinates(this.element));},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||(!this.options.handle&&this.options.unDraggableTags.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);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.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/pyload/webui/themes/default/js/static/purr.js b/pyload/webui/themes/default/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/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/js/static/purr.min.js b/pyload/webui/themes/default/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/purr.min.js
@@ -0,0 +1 @@
+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:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/tinytab.js b/pyload/webui/themes/default/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/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/js/static/tinytab.min.js b/pyload/webui/themes/default/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(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..ba94f7a74
--- /dev/null
+++ b/pyload/webui/themes/default/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/default/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/default/js/render/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..a8f20504f
--- /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="/default/css/default.min.css"/>
+<link rel="stylesheet" type="text/css" href="/default/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/default/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/default/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/default/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/default/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/default/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/default/js/render/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="/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="/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="/default/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="/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/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="/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-2014 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/tml/window.html' %}
+ {% include '/default/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..ba0f77c18
--- /dev/null
+++ b/pyload/webui/themes/default/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/default/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..0a2305923
--- /dev/null
+++ b/pyload/webui/themes/default/tml/filemanager.html
@@ -0,0 +1,76 @@
+{% extends '/default/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="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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>
+
+{% 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..227a46ba0
--- /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="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/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..2fc7b03ff
--- /dev/null
+++ b/pyload/webui/themes/default/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/default/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': '/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="/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/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="/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 %}
diff --git a/pyload/webui/themes/default/tml/info.html b/pyload/webui/themes/default/tml/info.html
new file mode 100644
index 000000000..2deaa6dce
--- /dev/null
+++ b/pyload/webui/themes/default/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/default/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..089275219
--- /dev/null
+++ b/pyload/webui/themes/default/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/default/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..196676de5
--- /dev/null
+++ b/pyload/webui/themes/default/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/default/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..1706be8a6
--- /dev/null
+++ b/pyload/webui/themes/default/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/default/css/log.min.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..8ce9ab072
--- /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="/default/css/pathchooser.min.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..035ee1808
--- /dev/null
+++ b/pyload/webui/themes/default/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/default/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/default/js/render/package.min.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="/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="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/default/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/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 %}
diff --git a/pyload/webui/themes/default/tml/settings.html b/pyload/webui/themes/default/tml/settings.html
new file mode 100644
index 000000000..fddc6e35c
--- /dev/null
+++ b/pyload/webui/themes/default/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/default/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/default/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/default/js/render/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..6642d34b4
--- /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 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>
diff --git a/pyload/webui/themes/default/tml/window.html b/pyload/webui/themes/default/tml/window.html
new file mode 100644
index 000000000..e73eba2bd
--- /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="/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>
diff --git a/pyload/webui/themes/flat/css/MooDialog.css b/pyload/webui/themes/flat/css/MooDialog.css
new file mode 100644
index 000000000..3544bcac6
--- /dev/null
+++ b/pyload/webui/themes/flat/css/MooDialog.css
@@ -0,0 +1,84 @@
+/* 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: #CDCDCD;
+ color: black;
+}
+
+.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(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/flat/css/flat.css b/pyload/webui/themes/flat/css/flat.css
new file mode 100644
index 000000000..1a542962f
--- /dev/null
+++ b/pyload/webui/themes/flat/css/flat.css
@@ -0,0 +1,863 @@
+.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;
+ margin-left:-25px;
+ 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:#ECECEC;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:5px;
+}
+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:#DDDDDD;
+ 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:#EAEAEA;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+ border-color:#CCCCCC #CCCCCC transparent;
+ color:#555555;
+ padding:7px 15px 8px;
+ text-decoration:none;
+}
+#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;
+ color:#111111;
+ outline:none;
+ padding-bottom:7px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ background-color:#FFFFFF;
+ border-bottom-color:transparent;
+ color:#3566A5;
+ padding:7px 15px 8px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111111;
+}
+div#head-search-and-login {
+ color:white;
+ float:right;
+ margin:0 1em 0 0;
+ padding:7px 7px 5px 5px;
+ white-space:nowrap;
+}
+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..b469b4259
--- /dev/null
+++ b/pyload/webui/themes/flat/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/flat/img/MooDialog/dialog-close.png b/pyload/webui/themes/flat/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-error.png b/pyload/webui/themes/flat/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-question.png b/pyload/webui/themes/flat/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png b/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png
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/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/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/default/add_folder.png b/pyload/webui/themes/flat/img/default/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/ajax-loader.gif b/pyload/webui/themes/flat/img/default/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/big_button.gif b/pyload/webui/themes/flat/img/default/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/big_button_over.gif b/pyload/webui/themes/flat/img/default/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/body.png b/pyload/webui/themes/flat/img/default/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/body.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/closebtn.gif b/pyload/webui/themes/flat/img/default/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/drag_corner.gif b/pyload/webui/themes/flat/img/default/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/full.png b/pyload/webui/themes/flat/img/default/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/full.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/head-menu-recent.png b/pyload/webui/themes/flat/img/default/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/head_bg1.png b/pyload/webui/themes/flat/img/default/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/images.png b/pyload/webui/themes/flat/img/default/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/images.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/parseUri.png b/pyload/webui/themes/flat/img/default/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/pyload-logo.png b/pyload/webui/themes/flat/img/default/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/tab-background.png b/pyload/webui/themes/flat/img/default/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/tabs-border-bottom.png b/pyload/webui/themes/flat/img/default/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/tabs-border-bottom.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/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/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-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/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/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/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/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/render/admin.coffee b/pyload/webui/themes/flat/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/admin.min.js b/pyload/webui/themes/flat/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/base.coffee b/pyload/webui/themes/flat/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/base.min.js b/pyload/webui/themes/flat/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/package.js b/pyload/webui/themes/flat/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/settings.coffee b/pyload/webui/themes/flat/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/render/settings.min.js b/pyload/webui/themes/flat/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/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/js/static/MooDialog.js b/pyload/webui/themes/flat/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/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/js/static/MooDialog.min.js b/pyload/webui/themes/flat/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,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")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/MooDropMenu.js b/pyload/webui/themes/flat/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/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/js/static/MooDropMenu.min.js b/pyload/webui/themes/flat/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/mootools-core.js b/pyload/webui/themes/flat/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+...
+*/
+
+/*
+---
+
+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]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// 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: 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: 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: 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: 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: 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';
+ }
+
+ var 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.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;
+ };
+ 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: 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 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 == '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 == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ 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: 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: 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;
+ 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>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ 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];
+ };
+});
+
+// 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>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</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 this.className.clean().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('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+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){
+ 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: 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;
+
+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();
+ 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: '@'
+};
+
+
+
+
+
+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.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
+ 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 //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--;){
+ 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.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 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();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ 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.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: 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: 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: '',*/
+ 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;
+ 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 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.user && '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();
+ clearTimeout(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', 'GET', 'POST', 'PUT', 'DELETE'].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) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+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/js/static/mootools-core.min.js b/pyload/webui/themes/flat/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+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
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){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("callee" in i){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;}if(!t.hasOwnProperty){return false;}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(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}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,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("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",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,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}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 parseInt(c,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({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).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(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 f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.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,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.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"||i=="keyup"){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(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};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=this.getAttribute(o,"class");
+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={"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.7";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=this.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;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}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={"$constructor":Element,"$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 f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);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(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},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(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});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:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(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,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};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));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}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 b.type!="propertychange"||b.event.propertyName=="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",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},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(n.condition||w&&w.condition){var l=q,m=n.condition||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,l.capture);}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(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}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.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});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(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},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 d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});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(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}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.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+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();}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 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.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+")");};})();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); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/mootools-more.js b/pyload/webui/themes/flat/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+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.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+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;
+
+};
+
+
+/*
+---
+
+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: 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: 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('&');
+ }
+
+});
+
+
+/*
+---
+
+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);
+});
+
+})();
+
+
+/*
+---
+
+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));
+
+})();
+
+
+/*
+---
+
+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: 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: 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: 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,
+ 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.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ 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)
+ };
+ 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(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ 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(this.element.getOffsetParent());
+ 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 = event.page;
+
+ 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];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ 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);
+ 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 = {};
+
+ ['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,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ 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: 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;
+ }
+
+});
+
+
+/*
+---
+
+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: 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;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/flat/js/static/mootools-more.min.js b/pyload/webui/themes/flat/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - 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.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};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};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();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);(function(){var a=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:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.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.target.set("aria-busy","true");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.target.set("aria-busy","false");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);b.validate(a);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:"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 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="selectstart" in document?"selectstart":"mousedown";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)};
+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.setContainer(this.options.container);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;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},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;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}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:{},unDraggableTags:["button","input","a","textarea","select","option"]},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));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},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(this.getDroppableCoordinates(this.element));},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||(!this.options.handle&&this.options.unDraggableTags.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);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.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/pyload/webui/themes/flat/js/static/purr.js b/pyload/webui/themes/flat/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/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/js/static/purr.min.js b/pyload/webui/themes/flat/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/purr.min.js
@@ -0,0 +1 @@
+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:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/tinytab.js b/pyload/webui/themes/flat/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/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/js/static/tinytab.min.js b/pyload/webui/themes/flat/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(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..c7bdd7894
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/flat/js/render/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..015a64600
--- /dev/null
+++ b/pyload/webui/themes/flat/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="/flat/css/flat.min.css"/>
+<link rel="stylesheet" type="text/css" href="/flat/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/flat/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/flat/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/flat/js/render/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: 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="/flat/img/default/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="/flat/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="/flat/img/default/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="/flat/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/flat/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/flat/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/flat/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/flat/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/flat/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;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="/flat/img/default/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 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 '/flat/tml/window.html' %}
+ {% include '/flat/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..731d97d11
--- /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> \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/downloads.html b/pyload/webui/themes/flat/tml/downloads.html
new file mode 100644
index 000000000..445f8da37
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/flat/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/folder.html b/pyload/webui/themes/flat/tml/folder.html
new file mode 100644
index 000000000..89b84e7bb
--- /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="/flat/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/flat/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/flat/img/default/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li> \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/home.html b/pyload/webui/themes/flat/tml/home.html
new file mode 100644
index 000000000..ec06b9122
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/flat/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': '/flat/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="/flat/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/flat/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/flat/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/flat/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/flat/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/flat/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="/flat/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/pyload/webui/themes/flat/tml/info.html b/pyload/webui/themes/flat/tml/info.html
new file mode 100644
index 000000000..26b46736d
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/flat/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/login.html b/pyload/webui/themes/flat/tml/login.html
new file mode 100644
index 000000000..76365a79c
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/flat/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..370031b25
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/flat/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/logs.html b/pyload/webui/themes/flat/tml/logs.html
new file mode 100644
index 000000000..87e5a0f30
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/flat/css/log.min.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/pyload/webui/themes/flat/tml/pathchooser.html b/pyload/webui/themes/flat/tml/pathchooser.html
new file mode 100644
index 000000000..95a1a3be5
--- /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="/flat/css/pathchooser.min.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/pyload/webui/themes/flat/tml/queue.html b/pyload/webui/themes/flat/tml/queue.html
new file mode 100644
index 000000000..0d3022ddd
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/flat/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/flat/js/render/package.min.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="/flat/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="/flat/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/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 %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/settings.html b/pyload/webui/themes/flat/tml/settings.html
new file mode 100644
index 000000000..1bd0d1e17
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/flat/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/flat/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/flat/js/render/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 %} \ No newline at end of file
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..813383343
--- /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 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/pyload/webui/themes/flat/tml/window.html b/pyload/webui/themes/flat/tml/window.html
new file mode 100644
index 000000000..8f57843c6
--- /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="/flat/img/default/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