Full Code of pt-plugins/PT-Plugin-Plus for AI

master f5a21686b770 cached
478 files
2.2 MB
590.9k tokens
1581 symbols
1 requests
Download .txt
Showing preview only (2,354K chars total). Download the full file or copy to clipboard to get everything.
Repository: pt-plugins/PT-Plugin-Plus
Branch: master
Commit: f5a21686b770
Files: 478
Total size: 2.2 MB

Directory structure:
gitextract_s_ss06tw/

├── .eslintrc.json
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report-cn.md
│   │   ├── bug-report.md
│   │   ├── feature-request-cn.md
│   │   ├── feature-request.md
│   │   ├── new-tracker-request-cn.md
│   │   ├── new-tracker-request.md
│   │   └── suggestions-or-comments.md
│   ├── issue-close-app.yml
│   ├── pull_request_template.md
│   ├── stale.yml
│   └── workflows/
│       ├── build_action.yml
│       └── build_canary.yml
├── .gitignore
├── .nvmrc
├── LICENSE
├── README.md
├── babel.config.js
├── debug/
│   ├── config/
│   │   └── config.json
│   ├── data/
│   │   └── beforeSearching.json
│   ├── package.json
│   ├── src/
│   │   ├── App.ts
│   │   ├── BuildPlugin.ts
│   │   ├── SearchData.ts
│   │   ├── buildResource.ts
│   │   └── index.ts
│   ├── tsconfig.json
│   └── typings.d.ts
├── package.json
├── privacy-statement.md
├── public/
│   ├── _locales/
│   │   ├── en/
│   │   │   └── messages.json
│   │   └── zh_CN/
│   │       └── messages.json
│   ├── assets/
│   │   ├── base.css
│   │   └── options.css
│   ├── changelog.html
│   ├── index.html
│   ├── libs/
│   │   ├── Base64.js
│   │   ├── drag.js
│   │   ├── materialIcons/
│   │   │   ├── content_style.css
│   │   │   └── style.css
│   │   ├── notice/
│   │   │   ├── notice.js
│   │   │   └── noticejs.css
│   │   └── types.expand.js
│   ├── manifest.json
│   └── popup.html
├── resource/
│   ├── clients/
│   │   ├── README.md
│   │   ├── deluge/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── flood/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── qbittorrent/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── ruTorrent/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── synologyDownloadStation/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── transmission/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   └── utorrent/
│   │       ├── config.json
│   │       └── init.js
│   ├── i18n/
│   │   ├── README.md
│   │   ├── en.json
│   │   └── zh-CN.json
│   ├── libs/
│   │   └── album/
│   │       ├── album.js
│   │       └── style.css
│   ├── publicSites/
│   │   ├── douban.com/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── doulist.js
│   │   │   ├── explore.js
│   │   │   ├── subject.js
│   │   │   └── top250.js
│   │   ├── goodmovieslist.com/
│   │   │   ├── best-movies.js
│   │   │   └── config.json
│   │   ├── imdb.com/
│   │   │   ├── config.json
│   │   │   ├── subject.js
│   │   │   └── top.js
│   │   └── reseed.tongyifan.me/
│   │       ├── config.json
│   │       └── reseed.js
│   ├── schemas/
│   │   ├── Common/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── Discuz/
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── Gazelle/
│   │   │   ├── config.json
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── GazelleJSONAPI/
│   │   │   ├── config.json
│   │   │   └── getSearchResult.js
│   │   ├── NexusPHP/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   ├── parser/
│   │   │   │   └── downloadURL.js
│   │   │   └── torrents.js
│   │   ├── README.md
│   │   ├── TNode/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   └── UNIT3D/
│   │       ├── config.json
│   │       ├── details.js
│   │       ├── getSearchResult.js
│   │       ├── torrents.js
│   │       └── userTorrents.js
│   └── sites/
│       ├── 1ptba.com/
│       │   └── config.json
│       ├── 52pt.site/
│       │   └── config.json
│       ├── README.md
│       ├── aidoru-online.me/
│       │   └── config.json
│       ├── aither.cc/
│       │   └── config.json
│       ├── alpharatio.cc/
│       │   └── config.json
│       ├── animebytes.tv/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   └── userTorrents.js
│       ├── anthelion.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── asiancinema.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── audiences.me/
│       │   └── config.json
│       ├── azusa.wiki/
│       │   └── config.json
│       ├── baconbits.org/
│       │   └── config.json
│       ├── bemaniso.ws/
│       │   └── config.json
│       ├── beyond-hd.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── bibliotik.me/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── bitbr/
│       │   └── config.json
│       ├── bitpt.cn/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── blutopia.cc/
│       │   └── config.json
│       ├── broadcasthe.net/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── brokenstones.is/
│       │   └── config.json
│       ├── bt.neu6.edu.cn/
│       │   └── config.json
│       ├── bwtorrents.tv/
│       │   └── config.json
│       ├── byr.pt/
│       │   └── config.json
│       ├── carpt.net/
│       │   └── config.json
│       ├── ccfbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── chdbits.co/
│       │   └── config.json
│       ├── cinemageddon.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── club.hares.top/
│       │   └── config.json
│       ├── cnlang.org/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── concertos.live/
│       │   └── config.json
│       ├── cyanbug.net/
│       │   └── config.json
│       ├── dajiao.cyou/
│       │   └── config.json
│       ├── dicmusic.com/
│       │   └── config.json
│       ├── discfan.net/
│       │   └── config.json
│       ├── et8.org/
│       │   └── config.json
│       ├── extremlymtorrents.ws/
│       │   └── config.json
│       ├── femdomcult.org/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── filelist.io/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── fsm.name/
│       │   └── config.json
│       ├── gainbound.net/
│       │   └── config.json
│       ├── gay-torrents.org/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── gazellegames.net/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── gfxpeers.net/
│       │   └── config.json
│       ├── greatposterwall.com/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── hawke.uno/
│       │   └── config.json
│       ├── hd-space.org/
│       │   ├── config.json
│       │   └── details.js
│       ├── hd-torrents.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── hdatmos.club/
│       │   └── config.json
│       ├── hdbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── hdchina.org/
│       │   └── config.json
│       ├── hdcity.city/
│       │   └── config.json
│       ├── hddolby.com/
│       │   └── config.json
│       ├── hdf.world/
│       │   └── config.json
│       ├── hdfans.org/
│       │   └── config.json
│       ├── hdhome.org/
│       │   └── config.json
│       ├── hdmayi.com/
│       │   └── config.json
│       ├── hdpt.xyz/
│       │   └── config.json
│       ├── hdroute.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── hdsky.me/
│       │   └── config.json
│       ├── hdtime.org/
│       │   └── config.json
│       ├── hdvideo.one/
│       │   └── config.json
│       ├── hdzone.me/
│       │   └── config.json
│       ├── hhanclub.top/
│       │   └── config.json
│       ├── htpt.cc/
│       │   └── config.json
│       ├── hudbt.hust.edu.cn/
│       │   └── config.json
│       ├── ihdbits.me/
│       │   └── config.json
│       ├── iptorrents.com/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── joyhd.net/
│       │   └── config.json
│       ├── jpopsuki.eu/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── jptv.club/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── jptvts.us/
│       │   └── config.json
│       ├── kamept.com/
│       │   └── config.json
│       ├── karagarga.in/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── kp.m-team.cc/
│       │   ├── config.json
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── learnflakes.net/
│       │   └── config.json
│       ├── leaves.red/
│       │   └── config.json
│       ├── lztr.me/
│       │   └── config.json
│       ├── monikadesign.uk/
│       │   └── config.json
│       ├── nanyangpt.com/
│       │   └── config.json
│       ├── nebulance.io/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── nicept.net/
│       │   └── config.json
│       ├── npupt.com/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── oldtoons.world/
│       │   └── config.json
│       ├── open.cd/
│       │   └── config.json
│       ├── orpheus.network/
│       │   └── config.json
│       ├── ourbits.club/
│       │   └── config.json
│       ├── passthepopcorn.me/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── piggo.me/
│       │   └── config.json
│       ├── pt.0ff.cc/
│       │   └── config.json
│       ├── pt.2xfree.org/
│       │   └── config.json
│       ├── pt.btschool.club/
│       │   └── config.json
│       ├── pt.eastgame.org/
│       │   └── config.json
│       ├── pt.hd4fans.org/
│       │   └── config.json
│       ├── pt.hdbd.us/
│       │   └── config.json
│       ├── pt.hdpost.top/
│       │   └── config.json
│       ├── pt.hdupt.com/
│       │   └── config.json
│       ├── pt.keepfrds.com/
│       │   └── config.json
│       ├── pt.newworld.plus/
│       │   └── config.json
│       ├── pt.sjtu.edu.cn/
│       │   └── config.json
│       ├── pt.soulvoice.club/
│       │   └── config.json
│       ├── pt.xauat6.edu.cn/
│       │   └── config.json
│       ├── pt.zhixing.bjtu.edu.cn/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── ptchina.org/
│       │   └── config.json
│       ├── pterclub.com/
│       │   └── config.json
│       ├── pthome.net/
│       │   └── config.json
│       ├── ptsbao.club/
│       │   └── config.json
│       ├── pussytorrents.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── redacted.ch/
│       │   └── config.json
│       ├── resource.xidian.edu.cn/
│       │   └── config.json
│       ├── sdbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── shadowthein.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── speedapp.io/
│       │   └── config.json
│       ├── sportscult.org/
│       │   ├── config.json
│       │   └── details.js
│       ├── springsunday.net/
│       │   └── config.json
│       ├── sugoimusic.me/
│       │   └── config.json
│       ├── teamhd.org/
│       │   └── config.json
│       ├── thegeeks.click/
│       │   └── config.json
│       ├── tjupt.org/
│       │   └── config.json
│       ├── totheglory.im/
│       │   ├── bookmarks.js
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── parser/
│       │       └── downloadURL.js
│       ├── u2.dmhy.org/
│       │   └── config.json
│       ├── ubits.club/
│       │   └── config.json
│       ├── uhdbits.org/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   └── getUserSeedingTorrents.js
│       ├── ultrahd.net/
│       │   └── config.json
│       ├── wintersakura.net/
│       │   └── config.json
│       ├── world-in-hd.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── www.beitai.pt/
│       │   └── config.json
│       ├── www.cgpeers.com/
│       │   └── config.json
│       ├── www.cinematik.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── getUserSeedingTorrents.js
│       ├── www.empornium.sx/
│       │   └── config.json
│       ├── www.filept.com/
│       │   └── config.json
│       ├── www.gamegamept.com/
│       │   ├── config.json
│       │   └── torrents.js
│       ├── www.gaytor.rent/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── www.haidan.video/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── www.hdarea.co/
│       │   └── config.json
│       ├── www.hitpt.com/
│       │   └── config.json
│       ├── www.icc2022.com/
│       │   └── config.json
│       ├── www.morethantv.me/
│       │   └── config.json
│       ├── www.myanonamouse.net/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── www.okpt.net/
│       │   ├── config.json
│       │   └── torrents.js
│       ├── www.pttime.org/
│       │   └── config.json
│       ├── www.ptzone.xyz/
│       │   └── config.json
│       ├── www.skyey2.com/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── www.torrentday.com/
│       │   └── config.json
│       ├── www.torrentleech.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── www.torrentseeds.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── xingtan.one/
│       │   └── config.json
│       ├── zhuque.in/
│       │   └── config.json
│       └── zmpt.cc/
│           └── config.json
├── src/
│   ├── background/
│   │   ├── README.md
│   │   ├── collection.ts
│   │   ├── config.ts
│   │   ├── contextMenus.ts
│   │   ├── controller.ts
│   │   ├── downloadHistory.ts
│   │   ├── downloadQuene.ts
│   │   ├── i18n.ts
│   │   ├── index.ts
│   │   ├── infoParser.ts
│   │   ├── keepUploadTask.ts
│   │   ├── omnibox.ts
│   │   ├── pageParser.ts
│   │   ├── plugins/
│   │   │   ├── OWSS.ts
│   │   │   └── WebDAV.ts
│   │   ├── searchResultSnapshot.ts
│   │   ├── searcher.ts
│   │   ├── service.ts
│   │   ├── site.ts
│   │   ├── syncStorage.ts
│   │   ├── user.ts
│   │   └── userData.ts
│   ├── changelog/
│   │   ├── Index.vue
│   │   └── index.ts
│   ├── content/
│   │   ├── README.md
│   │   └── index.ts
│   ├── debugger/
│   │   ├── Index.vue
│   │   └── index.ts
│   ├── interface/
│   │   ├── common.ts
│   │   ├── enum.ts
│   │   └── types.expand.js
│   ├── options/
│   │   ├── App.vue
│   │   ├── assets/
│   │   │   └── contextMenu.scss
│   │   ├── components/
│   │   │   ├── ColorSelector.vue
│   │   │   ├── Content.vue
│   │   │   ├── DownloadTo.vue
│   │   │   ├── Footer.vue
│   │   │   ├── MovieInfoCard.vue
│   │   │   ├── Navigation.vue
│   │   │   ├── Permissions.vue
│   │   │   ├── SearchBox.vue
│   │   │   ├── Topbar.vue
│   │   │   ├── TorrentProgress.vue
│   │   │   └── WorkingStatus.vue
│   │   ├── i18n.ts
│   │   ├── main.ts
│   │   ├── plugins/
│   │   │   └── vuetify.ts
│   │   ├── router.ts
│   │   ├── shims-tsx.d.ts
│   │   ├── shims-vue.d.ts
│   │   ├── store.ts
│   │   ├── typings.d.ts
│   │   └── views/
│   │       ├── About.vue
│   │       ├── AutoSignWarning.vue
│   │       ├── Donate.vue
│   │       ├── History.vue
│   │       ├── Home.vue
│   │       ├── SystemLogs.vue
│   │       ├── Teams.vue
│   │       ├── TechnologyStack.vue
│   │       ├── UserDataTimeline.vue
│   │       ├── collection/
│   │       │   ├── AddToGroup.vue
│   │       │   ├── GroupCard.vue
│   │       │   └── Index.vue
│   │       ├── keepUpload/
│   │       │   └── KeepUploadTasks.vue
│   │       ├── search/
│   │       │   ├── Actions.vue
│   │       │   ├── AddToCollectionGroup.vue
│   │       │   ├── KeepUpload.vue
│   │       │   ├── SearchResultSnapshot.vue
│   │       │   ├── SearchTorrent.scss
│   │       │   ├── SearchTorrent.ts
│   │       │   └── SearchTorrent.vue
│   │       ├── settings/
│   │       │   ├── Backup/
│   │       │   │   ├── Index.vue
│   │       │   │   └── Server/
│   │       │   │       ├── Add.vue
│   │       │   │       ├── Edit.vue
│   │       │   │       ├── Editor.vue
│   │       │   │       └── List.vue
│   │       │   ├── Base/
│   │       │   │   └── Index.vue
│   │       │   ├── DownloadClients/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── DownloadPaths/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Index.vue
│   │       │   │   └── KeyDescription.vue
│   │       │   ├── Language/
│   │       │   │   └── Index.vue
│   │       │   ├── SearchSolution/
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── SitePlugins/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── SiteSearchEntry/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── Sites/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   ├── Index.vue
│   │       │   │   └── UserInfo.vue
│   │       │   └── SupportSchema.vue
│   │       └── statisticCharts/
│   │           └── SiteBase.vue
│   ├── popup/
│   │   └── index.ts
│   └── service/
│       ├── api.ts
│       ├── backupFileParser.ts
│       ├── clientController.ts
│       ├── downloader.ts
│       ├── extension.ts
│       ├── favicon.ts
│       ├── filters.ts
│       ├── localStorage.ts
│       ├── logger.ts
│       ├── movieInfoService.ts
│       ├── pathHandler.ts
│       └── public.ts
├── tsconfig.json
├── update/
│   └── index.xml
├── vue.config.js
└── webpack/
    ├── common.js
    ├── dev-background.js
    ├── dev-content.js
    ├── prod-background.js
    └── prod-content.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
"env": {
  "browser": true,
  "commonjs": true,
  "es6": true,
  "jquery": true
}
}

================================================
FILE: .github/ISSUE_TEMPLATE/bug-report-cn.md
================================================
---
name: Bug 反馈
about: 发起一个Bug反馈
title: ''
labels: bug
assignees: ''

---

**请确认你已经做过并了解如下步骤,在 `[]` 中填入 `x` 选中**
- [ ] 仔细看并搜索过[Wiki](https://github.com/pt-plugins/PT-Plugin-Plus/wiki)
- [ ] 请确认你的版本比当前[Pre-release](https://github.com/pt-plugins/PT-Plugin-Plus/tags)要新,我们建议使用crx版本作为标准
- [ ] 搜素过Issue,确认没有相关问题
- [ ] 不理解、询问问题或者使用上的问题请使用Discuissions

<!--
为了更快解决您的问题,请提供以下信息,谢谢
-->

- PT 助手版本:
- PT 助手安装方式:(市场安装,或zip包安装)
- 浏览器名称及版本:
- 浏览器是否安装了其他插件:
- 停用其他插件后是否正常工作:
- 问题描述:


- 相关截图:


- 重现步骤:

<!--
注意:
1、上传日志或配置信息前,请先删除个人信息!
2、请按以上格式填写,否则问题会被机器人直接关闭!
-->


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

<!--
In order to better solve your problem, please provide the following information, thank you
-->

- PT Plugin Plus version:
- PT Plugin Plus installation method: (market installation, or zip package installation)
- Browser name and version:
- Whether the browser has other plugins installed:
- Is it working properly after disabling other plugins:
- Problem Description:


- Related screenshots:


- Reproduce steps:


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request-cn.md
================================================
---
name: 功能请求
about: 发起一个新功能请求
title: ''
labels: enhancement
assignees: ''

---
<!-- 
注意:不要删除模板内容,删除或更改将会被机器人自动关闭 
-->
## 您的功能请求是否与问题有关? 请描述一下。
<!-- 简明扼要地描述问题所在。 -->


## 描述你想要的解决方案
<!-- 欢迎提供脑洞 -->


## 描述您考虑过的替代方案
<!-- 如果有参考链接,请在此附上 -->


## 其他附加信息
<!-- 您可以添加屏幕截图等信息 -->


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/ISSUE_TEMPLATE/new-tracker-request-cn.md
================================================
---
name: 新站点请求
about: 发起一个新站点支持请求
title: ''
labels: 'New Tracker'
assignees: ''

---

<!-- 
1、在发起请求前,请先确认是否在已支持的架构内;
2、如果在已支持的架构内,本主题将会被视为无效并关闭;
3、如果您是开发者,请按 https://github.com/pt-plugins/PT-Plugin-Plus/wiki/developer 文档适配并提交PR;
4、如果您无法发送邀请,请不要提交,因为开发人员无法对其适配;
5、如有考核,请确认是否可免考,因为开发人员无法保证完成考核;
6、如有多个站点需求,请分别发起;
-->

- 站点名称:
- 站点地址:
- 站点描述:


- 资源类型:
- 开放注册:是/否
- 是否连坐:是/否
- 站点规则:



================================================
FILE: .github/ISSUE_TEMPLATE/new-tracker-request.md
================================================
---
name: New Tracker Request
about: Initiate a new tracker support request
title: ''
labels: 'New Tracker'
assignees: ''

---

<!-- 
1. Before initiating a request, please confirm whether it is within the supported schema;
2. If within the supported schema, this topic will be considered invalid and closed;
3. If you are a developer, please follow the https://github.com/pt-plugins/PT-Plugin-Plus/wiki/developer document to adapt and submit the PR;
4. If you are unable to send the invitation, please do not submit it because the developer cannot adapt it;
5. If there is an assessment, please confirm whether the exam can be exempted, because the developer cannot guarantee the completion of the assessment;
6. A topic only initiates a tracker request;
-->

- Tracker Name: 
- Tracker Url: 
- Tracker Description: 


- Resource Type: 
- Open registration: Yes / No
- Tracker rules: 



================================================
FILE: .github/ISSUE_TEMPLATE/suggestions-or-comments.md
================================================
---
name: Suggestions or comments
about: 其他建议或意见
title: ''
labels: Suggestions
assignees: ''

---

- 

<!-- 
👆 👆 👆 请在上面描述您的建议或意见,并且不要删除这段内容。
👆 👆 👆 Please describe your suggestions or comments above and do not delete this content.
------------------------------------------
1、如果是功能或新站点请求,将会被关闭,请按对应请求模板发起;
2、如果是使用问题,请参考帮助文档:

https://github.com/pt-plugins/PT-Plugin-Plus/wiki
 -->

================================================
FILE: .github/issue-close-app.yml
================================================
# Comment that will be sent if an issue is judged to be closed
comment: "This issue is closed because it does not meet our issue template. Please read it.\n 您好,我是问题处理机器人,因为您没有按规定提交问题,所以被我自动关闭了,请按规定填写,谢谢!"
issueConfigs:
  - content:
      # bug report
      - "PT Plugin Plus version"
      - "PT Plugin Plus installation method"
      - "Problem Description"
  - content:
      # bug report cn
      - "PT 助手版本"
      - "PT 助手安装方式"
      - "问题描述"
  - content:
      # feature request
      - "Is your feature request related to a problem"
      - "Describe the solution you'd like"
      - "Describe alternatives you've considered"
  - content:
      # feature request cn
      - "您的功能请求是否与问题有关"
      - "描述你想要的解决方案"
      - "描述您考虑过的替代方案"
  - content:
      # 建议和意见
      - "请在上面描述您的建议或意见,并且不要删除这段内容"
      - "Please describe your suggestions or comments"
  - content:
      # 新站点请求
      - "站点名称"
      - "站点地址"
      - "站点描述"
      - "资源类型"
      - "开放注册"
      - "是否连坐"
      - "站点规则"
  - content:
      # 新站点请求
      - "Tracker Name"
      - "Tracker Url"
      - "Tracker Description"
      - "Resource Type"
      - "Open registration"
      - "Tracker rules"


================================================
FILE: .github/pull_request_template.md
================================================
<!-- 
感谢您提交 PR ,为了更好的进行版本迭代,请将目标分支选择为 `base:dev` ,我们会根据实际情况在后续版本中发布。

## 标题请尽量按以下格式进行描述

`<type>(<scope>): <subject>`

## type 说明

- feat: 添加新功能
- fix: 修补 bug
- docs: 文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor: 重构(即不是新增功能,也不是修改 bug 的代码变动)
- test: 增加测试
- chore: 构建过程或辅助工具的变动

## 参考文档:http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html

## 内容说明

请尽量详细描述本次 PR 的具体作用。

本内容仅供提交前查阅,提交时请务必删除这段内容。
本内容仅供提交前查阅,提交时请务必删除这段内容。
本内容仅供提交前查阅,提交时请务必删除这段内容。
 -->

================================================
FILE: .github/stale.yml
================================================
# Configuration for probot-stale - https://github.com/probot/stale

# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90

# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7

# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []

# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
  - pinned
  - security
  - enhancement
  - Development
  - bug

# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true

# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true

# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: true

# Label to use when marking as stale
staleLabel: stale

# Comment to post when marking as stale. Set to `false` to disable
markComment: |
  This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

  此问题已自动标记为陈旧,因为它近期没有活动。如果没有进一步的活动,它将被自动关闭。感谢您的贡献。

# Comment to post when removing the stale label.
# unmarkComment: >
#   Your comment here.

# Comment to post when closing a stale Issue or Pull Request.
closeComment: |
  This issue has been automatically closed due to no further feedback. If you need to continue processing, please submit a new issue. Thank you for your contributions.

  此问题因没有进一步反馈已被自动关闭,如需继续处理,请提交新的 issue ,感谢您的贡献。

# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues

# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
#   daysUntilStale: 30
#   markComment: >
#     This pull request has been automatically marked as stale because it has not had
#     recent activity. It will be closed if no further activity occurs. Thank you
#     for your contributions.

# issues:
#   exemptLabels:
#     - confirmed


================================================
FILE: .github/workflows/build_action.yml
================================================
name: Build Action Release

on:
  push:
    branches: [ dev ]
  pull_request:
    branches: [ dev ]
  workflow_call:
    inputs:
      ref:
        default: 'dev'
        type: string
      auto_update_file:
        default: 'canary.xml'
        type: string
      build_xpi:
        default: false
        type: boolean
    outputs:
      version:
        value: ${{ jobs.zip.outputs.version }}
      buildXPI:
        value: ${{ jobs.xpi.outputs.exist_xpi_file }}
    secrets:
      CRX_PRIVATE_KEY:
        required: true

jobs:
  build:
    name: Build dist
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          fetch-depth: 0

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: yarn

      - run: yarn

      - run: yarn build
        continue-on-error: true

      - name: Upload Built to action
        uses: actions/upload-artifact@v4
        with:
          name: build-dist-folder
          path: dist

  zip:
    name: Build Zip
    runs-on: ubuntu-latest
    needs: build
    outputs:
      version: ${{ steps.zip.outputs.extensionVersion }}
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-dist-folder
          path: dist

      - id: zip
        name: Build Zip
        uses: cardinalby/webext-buildtools-pack-extension-dir-action@v1
        with:
          extensionDir: 'dist'
          zipFilePath: 'artifact/extension.zip'

      - name: Upload Built Zip to action
        uses: actions/upload-artifact@v4
        with:
          name: dev-build-${{ steps.zip.outputs.extensionVersion }}-zip
          path: artifact/*

  crx:
    name: Build Crx (Chromium)
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Set name output
        id: auto_update_file
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != ''  }}
        run: |
          USER_INPUT=${{ inputs.auto_update_file }}
          echo "value=${USER_INPUT:-"stable.xml"}" >> $GITHUB_OUTPUT

      - uses: actions/download-artifact@v4
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != ''  }}
        with:
          name: build-dist-folder
          path: dist

      - name: Add update_url
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != ''  }}
        run: echo $(jq '. |= .+ {"update_url":"https://pt-plugins.github.io/PT-Plugin-Plus/update/${{ steps.auto_update_file.outputs.value }}"}' dist/manifest.json) > dist/manifest.json

      - id: zip
        name: Build Zip With update_url
        uses: cardinalby/webext-buildtools-pack-extension-dir-action@v1
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != ''  }}
        with:
          extensionDir: 'dist'
          zipFilePath: 'extension.zip'

      - name: Build Crx
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != ''  }}
        uses: cardinalby/webext-buildtools-chrome-crx-action@v2
        with:
          zipFilePath: 'extension.zip'
          crxFilePath: 'artifact/extension.crx'
          privateKey: ${{ secrets.CRX_PRIVATE_KEY }}
          updateXmlPath: artifact/${{ steps.auto_update_file.outputs.value }}
          updateXmlCodebaseUrl: https://github.com/pt-plugins/PT-Plugin-Plus/releases/download/v${{ steps.zip.outputs.extensionVersion }}/PT-Plugin-Plus-${{ steps.zip.outputs.extensionVersion }}.crx

      - name: Upload Built Crx to action
        env:
          CRX_PRIVATE_KEY: ${{ secrets.CRX_PRIVATE_KEY }}
        if: ${{ env.CRX_PRIVATE_KEY != '' }}
        uses: actions/upload-artifact@v4
        with:
          name: dev-build-${{ steps.zip.outputs.extensionVersion }}-crx
          path: artifact/*

  xpi:
    name: Build Xpi (Firefox)
    runs-on: ubuntu-latest
    needs: zip
    if: ${{ inputs.build_xpi }}
    outputs:
      exist_xpi_file: ${{ steps.addonsDeploy.outcome }}
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dev-build-${{ needs.zip.outputs.version }}-zip

      - uses: cardinalby/webext-buildtools-firefox-sign-xpi-action@1.0.6
        id: addonsDeploy
        if: ${{ env.FF_EXT_UUID != ''  }}
        env:
          FF_EXT_UUID: ${{ secrets.FF_EXT_UUID }}
        continue-on-error: true
        with:
          timeoutMs: 600000
          zipFilePath: extension.zip
          xpiFilePath: artifact/extension.signed.xpi
          extensionId: ${{ secrets.FF_EXT_UUID }}
          jwtIssuer: ${{ secrets.FF_JWT_ISSUER }}
          jwtSecret: ${{ secrets.FF_JWT_SECRET }}

      - name: Upload Built Xpi to action
        uses: actions/upload-artifact@v4
        if: ${{ steps.addonsDeploy.outcome == 'success' }}
        with:
          name: dev-build-${{ needs.zip.outputs.version }}-xpi
          path: artifact/*


================================================
FILE: .github/workflows/build_canary.yml
================================================
name: Build Canary Release

on:
  workflow_dispatch:
  schedule:
    - cron: '0 20 1,15 * *'

jobs:
  action:
    uses: ./.github/workflows/build_action.yml
    with:
      ref: 'dev'
      build_xpi: true
    secrets: inherit

  canary:
    runs-on: ubuntu-latest
    needs: action
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@master
        with:
          ref: 'dev'
          fetch-depth: 0

      - name: Checkout gh-pages
        uses: actions/checkout@master
        with:
          ref: 'gh-pages'
          path: 'pages'

      - name: Prepare Release
        run: mkdir build

      - name: Get And rename Zip build
        uses: actions/download-artifact@v4
        with:
          name: dev-build-${{ needs.action.outputs.version }}-zip

      - run: mv extension.zip build/PT-Plugin-Plus-${{ needs.action.outputs.version }}.zip

      - name: Get And remove Crx Build
        uses: actions/download-artifact@v4
        with:
          name:
            dev-build-${{ needs.action.outputs.version }}-crx

      - run: |
          mv canary.xml pages/update/canary.xml -f
          mv extension.crx build/PT-Plugin-Plus-${{ needs.action.outputs.version }}.crx

      - name: Get And move Xpi Build
        uses: actions/download-artifact@v4
        if: ${{ needs.action.outputs.buildXPI == 'success' }}
        with:
          name: dev-build-${{ needs.action.outputs.version }}-xpi

      - if: ${{ needs.action.outputs.buildXPI == 'success' }}
        run: | 
          mv extension.signed.xpi build/PT-Plugin-Plus-${{ needs.action.outputs.version }}.xpi
          echo $(jq '.addons[].updates += [{"version": "${{ needs.action.outputs.version }}", "update_link": "https://github.com/pt-plugins/PT-Plugin-Plus/releases/download/v${{ needs.action.outputs.version }}/PT-Plugin-Plus-${{ needs.action.outputs.version }}.xpi"}]' pages/update/firefox.json) > pages/update/firefox.json

      - name: Deploy update xml and json
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./pages
          commit_message: deploy ${{ github.ref }}
          force_orphan: true
          user_name: github-actions[bot]
          user_email: github-actions[bot]@users.noreply.github.com

      - uses: ncipollo/release-action@v1
        with:
          name: v${{ needs.action.outputs.version }}
          tag: v${{ needs.action.outputs.version }}
          commit: 'dev'
          generateReleaseNotes: true
          prerelease: true
          artifacts: 'build/*'


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
debug/dist

# Chrome 插件文件
*.crx
*.pem
*.zip

# 发布的二进制文件
/releases

================================================
FILE: .nvmrc
================================================
16


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 栽培者

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
> [!WARNING]
> PT-Plugin-Plus 项目已经进入停止维护期(具体说明见: https://github.com/pt-plugins/PT-Plugin-Plus/issues/2235 )
> 
> 我们推荐您使用全新的替代方案:  **[PT-depiler](https://github.com/pt-plugins/PT-depiler)** 。
> PT-depiler 是 PT-Plugin-Plus 的继任者,保留了绝大多数核心功能并进行了全面优化,包括更好的兼容性、更稳定的性能和更丰富的功能支持。建议所有用户迁移至新项目以获得持续的更新和支持。

> [!TIP]
> 如果你浏览器中使用的PTPP已经被禁用,你可以尝试以下方法 **重新恢复使用** 或 **导出数据以迁移至PT-depiler**:
> 
> ① [使用浏览器 flags 临时启用](https://github.com/pt-plugins/PT-Plugin-Plus/wiki#%E6%88%91%E5%B7%B2%E7%BB%8F%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E4%BA%86%E5%BA%94%E8%AF%A5%E6%80%8E%E4%B9%88%E5%8A%9E) (需要 chrome < 139)
> 
> ② [使用临时插件 PTPP Exporter](https://github.com/pt-plugins/PT-Plugin-Plus/discussions/2241) (仅适用于通过 crx 或者 zip 方法安装)
> 
> ③ [使用低版本 chrome 抢救数据](https://github.com/pt-plugins/PT-Plugin-Plus/discussions/2242)  (推荐!也可用于其他基于 chromium 但未移除 MV2 支持的浏览器)


<p align="center">
<img src="https://github.com/pt-plugins/PT-Plugin-Plus/raw/master/public/assets/icon-128.png"><br/>
<a href="https://github.com/pt-plugins/PT-Plugin-Plus/releases?include_prereleases/latest" title="GitHub Pre-releases"><img src="https://img.shields.io/github/release/pt-plugins/PT-Plugin-Plus.svg?include_prereleases&label=pre-release"></a>
<a href="https://github.com/pt-plugins/PT-Plugin-Plus/releases" title="GitHub All Releases"><img alt="Releases" src="https://img.shields.io/github/downloads/pt-plugins/PT-Plugin-Plus/total.svg?label=Downloads"></a>
<img src="https://img.shields.io/badge/Used-TypeScript%20Vue-blue.svg">
<a href="https://github.com/pt-plugins/PT-Plugin-Plus/LICENSE" title="GitHub license"><img src="https://img.shields.io/github/license/pt-plugins/PT-Plugin-Plus.svg?label=License" alt="GitHub license"/></a>
<a href="https://t.me/joinchat/NZ9NCxPKXyby8f35rn_QTw"><img src="https://img.shields.io/badge/Telegram-Chat-blue.svg?logo=telegram" alt="Telegram"/></a>
</p>

---

## 关于

PT 助手 Plus,是一款浏览器插件(Web Extensions),一个可以提升 PT 站点使用效率的工具。

适用于各 PT 站,可使下载种子等各项操作变化更简单、快捷。配合下载服务器(如 Transmission、µTorrent 等),可一键下载指定的种子。

该版本是对原来的 [PT 助手](https://github.com/ronggang/PT-Plugin) 进行了重构,去掉了繁琐的配置,以获得更好的使用体验;

> ~~注意:`1.0.0` 以下的配置不能直接用于该版本,请勿将 `1.0.0` 以下的版本配置进行导入操作。~~

最新版本请登录后从[Pre-release](https://github.com/pt-plugins/PT-Plugin-Plus/releases?include_prereleases/latest)获取。如不会安装请参看Wiki

**提Issue前请务必检查Dev版本、Pull Request以及之前的Issue**

**M-Team 请于站点控制台 -> 实验室 获取 Token 填入后使用**

## 已支持的浏览器
- <a href="https://chrome.google.com/webstore/detail/abkdiiddckphbigmakaojlnmakpllenb" title="已在 Chrome Web Store 市场上发布的版本">![Google Chrome](https://img.shields.io/chrome-web-store/v/abkdiiddckphbigmakaojlnmakpllenb.svg?label=Google%20Chrome)</a> (已下架,见[原因](https://github.com/pt-plugins/PT-Plugin-Plus/wiki#%E5%B7%B2%E8%A2%AB%E4%B8%8B%E6%9E%B6%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8))
- <a href="https://addons.mozilla.org/zh-CN/firefox/addon/pt-plugin-plus/" title="已在 Mozilla Add-on 上发布的版本">![Mozilla Firefox](https://img.shields.io/amo/v/pt-plugin-plus.svg?label=Mozilla%20Firefox)</a> (已下架,见[原因](https://github.com/pt-plugins/PT-Plugin-Plus/wiki#%E5%B7%B2%E8%A2%AB%E4%B8%8B%E6%9E%B6%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8))
- <a href="https://microsoftedge.microsoft.com/addons/detail/ekhingnlcjebipkdcgkkheigmljefepn" title="已在 Microsoft Edge 上发布的版本">![Microsoft Edge](https://img.shields.io/badge/dynamic/json?label=Edge%20Addons&prefix=v&query=%24.version&url=https%3A%2F%2Fmicrosoftedge.microsoft.com%2FAddons%2Fgetproductdetailsbycrxid%2Fekhingnlcjebipkdcgkkheigmljefepn)</a>
- 及其他基于 `Chromium` 内核的浏览器

## 功能

- 一键发送指定的种子到下载服务器,目前已支持:
  - Transmission
  - Synology Download Station
  - µTorrent
  - Deluge
  - qBittorrent `v4.1+`
  - ruTorrent
  - Flood
- 比 RSS 更灵活的下载方式:
  - 针对不同的站点发送到不同的下载服务器;
  - 针对不同的站点、下载服务器设置不同的保存路径;
- 批量下载当前页所有种子;
- 批量复制当前页面所有种子的下载链接(`部分站点需要设置 passkey`);
- 显示默认下载服务器当前可用空间,目前已支持:
  - Transmission
- 多站聚合搜索相同关键字的种子;
  - 查看 [已支持的站点列表](https://github.com/pt-plugins/PT-Plugin-Plus/wiki/supported-sites)
- 根据当前站点显示专属功能,如:
  - 封面模式浏览种子页面;
- 保存下载历史记录(默认关闭);
- `豆瓣` 电影页面、[Top250](https://movie.douban.com/top250)、[选电影](https://movie.douban.com/explore) 一键搜索 PT 种子支持;
- `IMDb` 电影页面、[Top250](https://www.imdb.com/chart/top?ref_=nv_mv_250) 一键搜索 PT 种子支持;
- 更多功能请参考 [Wiki](https://github.com/pt-plugins/PT-Plugin-Plus/wiki) ;

## 安装及使用

- 如何安装和使用,请参考 [Wiki](https://github.com/pt-plugins/PT-Plugin-Plus/wiki) 的详细说明;
- 常见问题可 [点这里](https://github.com/pt-plugins/PT-Plugin-Plus/wiki/frequently-asked-questions) 找到答案;


================================================
FILE: babel.config.js
================================================
module.exports = {
  presets: [
    '@vue/app'
  ]
}


================================================
FILE: debug/config/config.json
================================================
{
	"port": 8001,
	"from": "../../resource",
	"to": "/"
}

================================================
FILE: debug/data/beforeSearching.json
================================================
{
  "count": 8,
  "start": 0,
  "total": 5608,
  "subjects": [
    {
      "rating": {
        "max": 10,
        "average": 9.7,
        "stars": "50",
        "min": 0
      },
      "genres": [
        "\u7eaa\u5f55\u7247"
      ],
      "title": "\u521b\u9020\u201c\u54c8\u5229\u00b7\u6ce2\u7279\u201d\u7684\u4e16\u754c\uff1a\u97f3\u4e50",
      "casts": [],
      "collect_count": 1184,
      "original_title": "Creating the World of Harry Potter, Part 4: Sound and Music",
      "subtype": "movie",
      "directors": [],
      "year": "2010",
      "images": {
        "small": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2493762829.webp",
        "large": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2493762829.webp",
        "medium": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2493762829.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/6718144\/",
      "id": "6718144"
    },
    {
      "rating": {
        "max": 10,
        "average": 7.1,
        "stars": "35",
        "min": 0
      },
      "genres": [
        "\u5267\u60c5"
      ],
      "title": "\u97f3\u4e50",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1342515\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/f\/movie\/ca527386eb8c4e325611e22dfcb04cc116d6b423\/pics\/movie\/celebrity-default-small.png",
            "large": "https://img3.doubanio.com\/f\/movie\/63acc16ca6309ef191f0378faf793d1096a3e606\/pics\/movie\/celebrity-default-large.png",
            "medium": "https://img1.doubanio.com\/f\/movie\/8dd0c794499fe925ae2ae89ee30cd225750457b4\/pics\/movie\/celebrity-default-medium.png"
          },
          "name": "\u9ed2\u6ca2\u306e\u308a\u5b50",
          "id": "1342515"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1323580\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1357565957.64.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1357565957.64.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1357565957.64.webp"
          },
          "name": "\u7ec6\u5ddd\u4fca\u4e4b",
          "id": "1323580"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1039227\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1550212443.35.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1550212443.35.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1550212443.35.webp"
          },
          "name": "\u9ad8\u6865\u957f\u82f1",
          "id": "1039227"
        }
      ],
      "collect_count": 117,
      "original_title": "\u97f3\u697d",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1050508\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p17532.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p17532.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p17532.webp"
          },
          "name": "\u589e\u6751\u4fdd\u9020",
          "id": "1050508"
        }
      ],
      "year": "1972",
      "images": {
        "small": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2509743913.webp",
        "large": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2509743913.webp",
        "medium": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2509743913.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/3169341\/",
      "id": "3169341"
    },
    {
      "rating": {
        "max": 10,
        "average": 7.4,
        "stars": "40",
        "min": 0
      },
      "genres": [
        "\u5267\u60c5"
      ],
      "title": "\u97f3\u4e50",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1000594\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p45463.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p45463.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p45463.webp"
          },
          "name": "\u5fb7\u83f2\u56e0\u00b7\u585e\u91cc\u683c",
          "id": "1000594"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1016766\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p14457.webp",
            "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p14457.webp",
            "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p14457.webp"
          },
          "name": "\u7f57\u8d1d\u5c14\u00b7\u4faf\u8d5b\u56e0",
          "id": "1016766"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1085159\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1471197409.54.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1471197409.54.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1471197409.54.webp"
          },
          "name": "Julie Dassin",
          "id": "1085159"
        }
      ],
      "collect_count": 84,
      "original_title": "La Musica",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1000758\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p12067.webp",
            "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p12067.webp",
            "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p12067.webp"
          },
          "name": "\u739b\u683c\u4e3d\u7279\u00b7\u675c\u62c9\u65af",
          "id": "1000758"
        },
        {
          "alt": null,
          "avatars": null,
          "name": "Paul Seban",
          "id": null
        }
      ],
      "year": "1967",
      "images": {
        "small": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2264840662.webp",
        "large": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2264840662.webp",
        "medium": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2264840662.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/3223707\/",
      "id": "3223707"
    },
    {
      "rating": {
        "max": 10,
        "average": 0,
        "stars": "00",
        "min": 0
      },
      "genres": [
        "\u559c\u5267",
        "\u5267\u60c5"
      ],
      "title": "\u97f3\u4e50",
      "casts": [],
      "collect_count": 1,
      "original_title": "Muzika",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1034597\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1391190217.73.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1391190217.73.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1391190217.73.webp"
          },
          "name": "Juraj Nvota",
          "id": "1034597"
        }
      ],
      "year": "2008",
      "images": {
        "small": "https://img3.doubanio.com\/view\/subject\/s\/public\/s3678106.jpg",
        "large": "https://img3.doubanio.com\/view\/subject\/l\/public\/s3678106.jpg",
        "medium": "https://img3.doubanio.com\/view\/subject\/m\/public\/s3678106.jpg"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/3617774\/",
      "id": "3617774"
    },
    {
      "rating": {
        "max": 10,
        "average": 8.1,
        "stars": "40",
        "min": 0
      },
      "genres": [
        "\u5267\u60c5",
        "\u7231\u60c5"
      ],
      "title": "\u542c\u8bf4",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1013782\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1368156632.65.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1368156632.65.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1368156632.65.webp"
          },
          "name": "\u5f6d\u4e8e\u664f",
          "id": "1013782"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1274316\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p31663.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p31663.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p31663.webp"
          },
          "name": "\u9648\u610f\u6db5",
          "id": "1274316"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1313303\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p37554.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p37554.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p37554.webp"
          },
          "name": "\u9648\u598d\u5e0c",
          "id": "1313303"
        }
      ],
      "collect_count": 435902,
      "original_title": "\u807d\u8aaa",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1276077\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p13508.webp",
            "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p13508.webp",
            "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p13508.webp"
          },
          "name": "\u90d1\u82ac\u82ac",
          "id": "1276077"
        }
      ],
      "year": "2009",
      "images": {
        "small": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250788664.webp",
        "large": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250788664.webp",
        "medium": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250788664.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/3824672\/",
      "id": "3824672"
    },
    {
      "rating": {
        "max": 10,
        "average": 9.0,
        "stars": "45",
        "min": 0
      },
      "genres": [
        "\u5267\u60c5",
        "\u4f20\u8bb0",
        "\u7231\u60c5"
      ],
      "title": "\u97f3\u4e50\u4e4b\u58f0",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1041081\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p4777.webp",
            "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p4777.webp",
            "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p4777.webp"
          },
          "name": "\u6731\u8389\u00b7\u5b89\u5fb7\u9c81\u65af",
          "id": "1041081"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1036321\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p42033.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p42033.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p42033.webp"
          },
          "name": "\u514b\u91cc\u65af\u6258\u5f17\u00b7\u666e\u5362\u9ed8",
          "id": "1036321"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1010671\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1355087417.43.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1355087417.43.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1355087417.43.webp"
          },
          "name": "\u57c3\u7433\u8bfa\u00b7\u5e15\u514b",
          "id": "1010671"
        }
      ],
      "collect_count": 453324,
      "original_title": "The Sound of Music",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1049929\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1485851955.3.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1485851955.3.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1485851955.3.webp"
          },
          "name": "\u7f57\u4f2f\u7279\u00b7\u6000\u65af",
          "id": "1049929"
        }
      ],
      "year": "1965",
      "images": {
        "small": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p453788577.webp",
        "large": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p453788577.webp",
        "medium": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p453788577.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/1294408\/",
      "id": "1294408"
    },
    {
      "rating": {
        "max": 10,
        "average": 9.2,
        "stars": "50",
        "min": 0
      },
      "genres": [
        "\u8131\u53e3\u79c0"
      ],
      "title": "\u542c\u8bf4 \u7b2c\u4e00\u5b63",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1350153\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1434704950.63.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1434704950.63.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1434704950.63.webp"
          },
          "name": "\u9a6c\u4e16\u82b3",
          "id": "1350153"
        }
      ],
      "collect_count": 3941,
      "original_title": "\u542c\u8bf4 \u7b2c\u4e00\u5b63",
      "subtype": "tv",
      "directors": [
        {
          "alt": null,
          "avatars": null,
          "name": "\u9648\u6021\u5206",
          "id": null
        }
      ],
      "year": "2015",
      "images": {
        "small": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250357437.webp",
        "large": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250357437.webp",
        "medium": "https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250357437.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/26425523\/",
      "id": "26425523"
    },
    {
      "rating": {
        "max": 10,
        "average": 8.5,
        "stars": "45",
        "min": 0
      },
      "genres": [
        "\u559c\u5267",
        "\u7231\u60c5",
        "\u97f3\u4e50"
      ],
      "title": "\u518d\u6b21\u51fa\u53d1\u4e4b\u7ebd\u7ea6\u9047\u89c1\u4f60",
      "casts": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1054448\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p10192.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p10192.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p10192.webp"
          },
          "name": "\u51ef\u62c9\u00b7\u5948\u7279\u8389",
          "id": "1054448"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1040505\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p15885.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p15885.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p15885.webp"
          },
          "name": "\u9a6c\u514b\u00b7\u9c81\u5f17\u6d1b",
          "id": "1040505"
        },
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1174312\/",
          "avatars": {
            "small": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1380782810.86.webp",
            "large": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1380782810.86.webp",
            "medium": "https://img3.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1380782810.86.webp"
          },
          "name": "\u4e9a\u5f53\u00b7\u83b1\u6587",
          "id": "1174312"
        }
      ],
      "collect_count": 299015,
      "original_title": "Begin Again",
      "subtype": "movie",
      "directors": [
        {
          "alt": "https:\/\/movie.douban.com\/celebrity\/1280127\/",
          "avatars": {
            "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1470662353.8.webp",
            "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1470662353.8.webp",
            "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p1470662353.8.webp"
          },
          "name": "\u7ea6\u7ff0\u00b7\u5361\u5c3c",
          "id": "1280127"
        }
      ],
      "year": "2013",
      "images": {
        "small": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250287733.webp",
        "large": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250287733.webp",
        "medium": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2250287733.webp"
      },
      "alt": "https:\/\/movie.douban.com\/subject\/6874403\/",
      "id": "6874403"
    }
  ],
  "title": "\u641c\u7d22 \"\u97f3\u4e50\" \u7684\u7ed3\u679c"
}

================================================
FILE: debug/package.json
================================================
{
  "name": "pt-plugin-plus-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "cors": "^2.8.5",
    "express": "^4.16.4",
    "lodash": "^4.17.19"
  },
  "devDependencies": {
    "@types/express": "^4.17.2",
    "@types/lodash": "^4.14.117",
    "@types/node": "^10.12.0",
    "ts-node": "^7.0.1",
    "typescript": "^3.1.3"
  }
}


================================================
FILE: debug/src/App.ts
================================================
// 导入基础库
import * as Express from "express";
import * as cors from "cors";
import * as BodyParser from "body-parser";
import * as PATH from "path";
import * as FS from "fs";
import { BuildPlugin } from "./BuildPlugin";
import { SearchData } from "./SearchData";

/**
 * 默认APP
 */
class App {
  public express = Express();
  public options;
  public systemConfig;
  public i18n;

  constructor(options) {
    this.options = options || {
      port: 80,
      from: "../resource",
      to: "/"
    };

    let buildPlugin = new BuildPlugin("../../resource");
    this.systemConfig = JSON.stringify(buildPlugin.getSystemConfig());
    this.i18n = JSON.stringify(buildPlugin.geti18n());

    this.useModules();
    this.mountRoutes();
  }

  /**
   * 使用一些模块
   */
  private useModules() {
    const from = PATH.join(__dirname, this.options.from);

    this.express.use(cors());
    // 启用静态文件目录
    this.express.use(this.options.to, Express.static(from));
    this.express.use(BodyParser.json()); // for parsing application/json
    this.express.use(BodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
  }

  /**
   * 挂载路由
   */
  private mountRoutes(): void {
    this.express.get("/systemConfig.json", (req, res) => {
      res.send(this.systemConfig);
    });

    this.express.get("/i18n.json", (req, res) => {
      res.send(this.i18n);
    });

    const config = JSON.parse(this.systemConfig);
    this.express.get("/test/searchData.json", (req, res) => {
      res.send(new SearchData(config).generate());
    });

    this.express.get("/test/*.*", (req, res) => {
      console.log(req.url);
      let fileName = (req.url as any).match(/test\/(.[^\?]+)/)[1];
      console.log(fileName);
      let path = PATH.resolve(__dirname, "../data/");
      let file = PATH.join(path, fileName);
      if (FS.existsSync(file)) {
        let content = FS.readFileSync(PATH.join(path, fileName), "utf-8");
        if (fileName.substr(-5) === ".json") {
          res.send(JSON.parse(content));
        } else {
          console.log(PATH.join(path, fileName));
          res.sendFile(PATH.join(path, fileName));
          // res.send(content);
        }
      } else {
        res.send("no file.");
      }
    });
  }

  /**
   * 启动服务
   */
  public start() {
    this.express.listen(this.options.port, err => {
      if (err) {
        return console.log(err);
      }

      return console.log(`Base Web Service Run at ${this.options.port}`);
    });
  }
}

export default App;


================================================
FILE: debug/src/BuildPlugin.ts
================================================
import * as FS from "fs";
import * as PATH from "path";

export type Dictionary<T> = { [key: string]: T };

/**
 * 构建过程辅助工具
 */
export class BuildPlugin {
  public resourcePath: string = "";
  private resourceMap = ["sites", "schemas", "clients", "publicSites"];

  constructor(rootPaht: string = "../../dist/resource") {
    this.resourcePath = PATH.resolve(__dirname, rootPaht);
    console.log(this.resourcePath);
  }

  /**
   * 创建资源文件列表
   */
  public buildResource() {
    let fileName = PATH.join(this.resourcePath, `systemConfig.json`);
    FS.writeFileSync(fileName, JSON.stringify(this.getSystemConfig()));
    fileName = PATH.join(this.resourcePath, `i18n.json`);
    FS.writeFileSync(fileName, JSON.stringify(this.geti18n()));
  }

  /**
   * 获取系统配置信息
   */
  public getSystemConfig() {
    let result = {};
    this.resourceMap.forEach((name: string) => {
      result[name] = this.getResourceConfig(name);
    });

    return result;
  }

  /**
   * 获取指定的资源配置信息
   * @param name
   */
  private getResourceConfig(name: string): any {
    let parentFolder = PATH.join(this.resourcePath, name);
    let list = FS.readdirSync(parentFolder);

    let results: any[] = [];
    list.forEach((path: string) => {
      let _path = PATH.join(parentFolder, path);
      var stat = FS.statSync(_path);
      // 仅获取目录
      if (stat && stat.isDirectory()) {
        let file = PATH.join(_path, `config.json`);
        if (FS.existsSync(file)) {
          let content = JSON.parse(FS.readFileSync(file, "utf-8"));

          // 获取解析器
          let parser = this.getParser(PATH.join(_path, "parser"));
          if (parser) {
            content["parser"] = parser;
          }
          results.push(content);
        }
      }
    });

    return results;
  }

  /**
   * 创建架构和站点的解析器
   */
  private makeParser(name: string) {
    let parentFolder = PATH.join(this.resourcePath, name);
    let list = FS.readdirSync(parentFolder);

    list.forEach((path: string) => {
      let _path = PATH.join(parentFolder, path);
      var stat = FS.statSync(_path);
      // 仅获取目录
      if (stat && stat.isDirectory()) {
        let parser = this.getParser(PATH.join(_path, "parser"));
        if (parser) {
          let fileName = PATH.join(_path, `config.json`);
          let content = JSON.parse(FS.readFileSync(fileName, "utf-8"));
          content["parser"] = parser;

          FS.writeFileSync(fileName, JSON.stringify(content));
        }
      }
    });
  }

  /**
   * 获取解析器
   * @param parentFolder
   */
  private getParser(parentFolder): any {
    if (!FS.existsSync(parentFolder)) {
      return null;
    }
    let list = FS.readdirSync(parentFolder);

    let results: any = {};
    list.forEach((path: string) => {
      let _path = PATH.join(parentFolder, path);
      var stat = FS.statSync(_path);
      // 仅获取目录
      if (stat && stat.isFile() && PATH.extname(_path) == ".js") {
        results[PATH.basename(_path, ".js")] = FS.readFileSync(_path, "utf-8");
      }
    });

    return results;
  }

  /**
   * 获取已支持站点列表
   */
  public getSupportedSites() {
    let schemaFolder = PATH.join(this.resourcePath, "schemas");
    let schemaList = FS.readdirSync(schemaFolder);

    let schemas: any = {};
    schemaList.forEach((path: string) => {
      let file = PATH.join(schemaFolder, path);
      var stat = FS.statSync(file);
      // 仅获取目录
      if (stat && stat.isDirectory()) {
        schemas[path] = [];
      }
    });

    schemas["其他架构"] = [];

    let parentFolder = PATH.join(this.resourcePath, "sites");

    let list = FS.readdirSync(parentFolder);

    let itemTemplate =
      "| $schema$ | $name$ | $search$ | $imdbSearch$ | $userData$ | $sendTorrent$ | $torrentProgress$ | $collaborator$ |";

    list.forEach((path: string) => {
      let file = PATH.join(parentFolder, path);
      var stat = FS.statSync(file);
      // 仅获取目录
      if (stat && stat.isDirectory()) {
        let fileName = PATH.join(file, `config.json`);
        let content = JSON.parse(FS.readFileSync(fileName, "utf-8"));
        let schema = content.schema;
        if (!schemas[schema]) {
          schema = "其他架构";
        }

        let supportedFeatures = {
          search: true,
          imdbSearch: true,
          userData: true,
          sendTorrent: true
        };

        if (content.supportedFeatures) {
          supportedFeatures = Object.assign(
            supportedFeatures,
            content.supportedFeatures
          );
        }

        // 判断是否有跳过 IMDb 选项,有则定为不支持 IMDb
        if (content.searchEntryConfig) {
          if (content.searchEntryConfig.skipIMDbId === true) {
            supportedFeatures.imdbSearch = false;
          }
        }

        let count = schemas[schema].length;
        let item = this.replaceKeys(itemTemplate, {
          schema: count == 0 ? schema : "",
          name: content.name,
          search: supportedFeatures.search === true ? "√" : "",
          imdbSearch: supportedFeatures.imdbSearch === true ? "√" : "",
          userData:
            supportedFeatures.userData === true
              ? "√"
              : supportedFeatures.userData === false
              ? ""
              : supportedFeatures.userData,
          sendTorrent: supportedFeatures.sendTorrent === true ? "√" : "",
          torrentProgress:
            content.searchEntryConfig &&
            content.searchEntryConfig.fieldSelector &&
            content.searchEntryConfig.fieldSelector.progress
              ? "√"
              : "",
          collaborator: this.getCollaborator(content.collaborator)
        });
        schemas[schema].push(item);
      }
    });

    console.log("\n");
    for (const key in schemas) {
      if (schemas.hasOwnProperty(key)) {
        const items: Array<any> = schemas[key];
        // console.log(`\n## ${key}`);

        items.forEach((item: string) => {
          console.log(item);
        });
      }
    }
    console.log("\n");

    // console.log(results);
  }

  public getCollaborator(source: string | Array<string>): string {
    if (!source) {
      return "";
    }
    if (typeof source == "string") {
      return source;
    } else if (source.length > 0) {
      let result: Array<string> = [];
      source.forEach((item: string) => {
        result.push(item);
      });

      return result.join(", ");
    }
    return "";
  }

  /**
   * 获取语言配置信息
   */
  public geti18n() {
    let parentFolder = PATH.join(this.resourcePath, "i18n");

    let list = FS.readdirSync(parentFolder);
    let results: Array<any> = [];

    list.forEach((path: string) => {
      let file = PATH.join(parentFolder, path);
      var stat = FS.statSync(file);
      // 获取语言配置文件
      if (stat && stat.isFile() && PATH.extname(file) == ".json") {
        let content = JSON.parse(FS.readFileSync(file, "utf-8"));
        if (content && content.code && content.name) {
          console.log(path, content.name);
          results.push({
            name: content.name,
            code: content.code
          });
        }
      }
    });
    return results;
  }

  /**
   * 替换指定的字符串列表
   * @param source
   * @param keys
   */
  replaceKeys(
    source: string,
    keys: Dictionary<any>,
    prefix: string = ""
  ): string {
    let result: string = source;

    for (const key in keys) {
      if (keys.hasOwnProperty(key)) {
        const value = keys[key];
        let search = "$" + key + "$";
        if (prefix) {
          search = `$${prefix}.${key}$`;
        }
        result = result.replace(search, value);
      }
    }
    return result;
  }
}


================================================
FILE: debug/src/SearchData.ts
================================================
/**
 * 生成搜索测试数据
 */
export class SearchData {
  constructor(public config: any = {}) {}

  public generate() {
    let results: any[] = [];
    let count = Math.floor(Math.random() * 100);
    const status = [1, 2, 255, undefined];
    for (let i = 0; i < count; i++) {
      let host = this.getHost();
      let data = {
        title: this.getTitle(),
        subTitle: this.getSubTitle(),
        link: `https://${host}/details.php?id=${i}`,
        url: `https://${host}/download.php?id=${i}`,
        size: this.getSize(),
        time: Math.floor(
          new Date().getTime() / 1000 - Math.floor(Math.random() * 10000000)
        ),
        author: "匿名",
        seeders: Math.floor(Math.random() * 1000),
        leechers: Math.floor(Math.random() * 1000),
        completed: Math.floor(Math.random() * 1000),
        comments: Math.floor(Math.random() * 1000),
        host: host,
        tags: this.getTags(),
        entryName: "全部",
        progress: Math.floor(Math.random() * 100),
        status: status[Math.floor(Math.random() * status.length)]
      };

      results.push(data);
    }

    return JSON.stringify(results);
  }

  private getTitle() {
    let title: string[] = ["The Shawshank Redemption 1994"];
    let datas = ["BluRay", "720p", "1080p", "x265", "10bit"];

    const count = Math.floor(Math.random() * datas.length) - 1;
    if (count <= 0) {
      return title.join(" ");
    }

    for (let i = 0; i < count; i++) {
      let index = Math.floor(Math.random() * datas.length);
      title.push(datas[index]);
      datas.splice(index, 1);
    }

    return title.join(" ");
  }

  private getSubTitle() {
    let title: string[] = ["肖申克的救赎"];
    let datas = [" / 刺激1995(台)", " / 月黑高飞(港)", "英简繁特效"];

    const count = Math.floor(Math.random() * datas.length) - 1;
    if (count <= 0) {
      return title.join(" ");
    }

    for (let i = 0; i < count; i++) {
      let index = Math.floor(Math.random() * datas.length);
      title.push(datas[index]);
      datas.splice(index, 1);
    }

    return title.join(" ");
  }

  private getHost() {
    let index = Math.floor(Math.random() * this.config.sites.length);

    return this.config.sites[index].host;
  }

  private getTags() {
    let datas = [
      {
        name: "Free",
        color: "blue"
      },
      {
        name: "2xFree",
        color: "green"
      },
      {
        name: "2xUp",
        color: "lime"
      },
      {
        name: "2x50%",
        color: "light-green"
      },
      {
        name: "30%",
        color: "indigo"
      },
      {
        name: "50%",
        color: "orange"
      }
    ];

    const index = Math.floor(Math.random() * datas.length) - 1;
    if (index <= 0) {
      return [];
    }

    return [datas[index]];
  }

  private getSize() {
    const units = ["MB", "GB"];

    return (
      (Math.random() * 1000).toFixed(2) +
      units[Math.floor(Math.random() * units.length)]
    );
  }
}


================================================
FILE: debug/src/buildResource.ts
================================================
import { BuildPlugin } from "./BuildPlugin";

let buildPlugin = new BuildPlugin();
buildPlugin.buildResource();
buildPlugin.getSupportedSites();
console.log("编译完成于:%s \n", new Date().toLocaleString());


================================================
FILE: debug/src/index.ts
================================================


import App from "./App";
import * as config from "../config/config.json";
new App(config).start();


================================================
FILE: debug/tsconfig.json
================================================
// {
//   "compilerOptions": {
//     /* Basic Options */
//     "target": "es5",
//     /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
//     "module": "commonjs",
//     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
//     // "lib": [],                             /* Specify library files to be included in the compilation. */
//     // "allowJs": true,                       /* Allow javascript files to be compiled. */
//     // "checkJs": true,                       /* Report errors in .js files. */
//     // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
//     // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
//     // "sourceMap": true,                     /* Generates corresponding '.map' file. */
//     // "outFile": "transmission.js",
//     /* Concatenate and emit output to single file. */
//     // "outDir": "dist",
//     /* Redirect output structure to the directory. */
//     // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
//     // "removeComments": true,                /* Do not emit comments to output. */
//     // "noEmit": true,                        /* Do not emit outputs. */
//     // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
//     // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
//     // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

//     /* Strict Type-Checking Options */
//     "strict": true,
//     /* Enable all strict type-checking options. */
//     // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
//     // "strictNullChecks": true,              /* Enable strict null checks. */
//     // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
//     // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
//     // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
//     // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

//     /* Additional Checks */
//     // "noUnusedLocals": true,                /* Report errors on unused locals. */
//     // "noUnusedParameters": true,            /* Report errors on unused parameters. */
//     // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
//     // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

//     /* Module Resolution Options */
//     // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
//     // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
//     // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
//     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
//     // "typeRoots": [],                       /* List of folders to include type definitions from. */
//     // "types": [],                           /* Type declaration files to be included in compilation. */
//     // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
//     "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
//     // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

//     /* Source Map Options */
//     // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
//     // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
//     // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
//     // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

//     /* Experimental Options */
//     // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
//     // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
//   },
//   "include": [
//     "src/**/*"
//   ],
//   "exclude": [
//     "node_modules",
//     "typings/main",
//     "typings/main.d.ts"
//   ]
// }

{
	// tsconfig 所在的根目录, 则是一个project
	"compilerOptions": {
		 "module": "commonjs", // 模块系统
		 "target": "es2015",   // 生成目标, 一般选择ES6,因为不是客户端环境,没必要还编译成  ES5
		 "outDir": "dist",
		 
		 // 一组严苛的编译选项
		 "noImplicitAny": false,
		 "strictNullChecks": true,
		 "strict": true,
		 "alwaysStrict": true,
		 "sourceMap": false,
		 "noImplicitReturns": true,
		 "noImplicitThis": true,
		 "pretty": true,
		 
		 "listFiles": true,  // 包含了哪些库,这个必要的时候还是很有用的
		 "listEmittedFiles": true, 
		 "lib": [            // 要那些 lib,按需选择即可
			  "es2016"
		 ],
		 // "noUnusedLocals": true,
		 // "noUnusedParameters": true,
		 // "noFallthroughCasesInSwitch": true,
		 // 指定库的搜索路径,这个比较有用,一般会指定 @types,还可以按需添加
		 "typeRoots": [
			  "./node_modules/@types"
		 ]
		 // 库搜索路径下, 仅使用哪些库, 一般没啥用
		 // "types": [
			  
		 // ]
	},
	// file include会算出一个交集, 指明哪些是项目的 ts 文件
	"include": [
		 "./**/*"
	],
	// 排除项目下面不符合要求的文件,这个按需设定即可,可以放心排除乱七八糟的文件
	"exclude": [
		 "node_modules",
		 "**/*.spec.ts",
		 "*.js"
	]
}


================================================
FILE: debug/typings.d.ts
================================================
declare module "*.json" {
	const value: any;
	export default value;
}

================================================
FILE: package.json
================================================
{
  "name": "pt-plugin-plus",
  "version": "1.6.1",
  "packageManager": "yarn@1.19.1",
  "author": {
    "name": "ronggang",
    "url": "https://github.com/ronggang"
  },
  "archiverName": "PT-Plugin-Plus",
  "displayName": "PT 助手 Plus",
  "homepage": "https://github.com/pt-plugins/PT-Plugin-Plus",
  "scripts": {
    "serve": "set NODE_OPTIONS=--openssl-legacy-provider & vue-cli-service serve --mode=test",
    "build": "set NODE_OPTIONS=--openssl-legacy-provider & yarn build:index && yarn build:background && yarn build:content && yarn resource",
    "lint": "vue-cli-service lint",
    "background": "webpack --config webpack/prod-background.js && webpack --config webpack/prod-content.js",
    "dev": "yarn dev:index && yarn dev:background && yarn dev:content && yarn resource",
    "dev-s": "cd debug && yarn install && tsc && node ./dist/index.js",
    "dev:index": "yarn install && vue-cli-service build --mode=development",
    "dev:background": "webpack --config webpack/dev-background.js --progress",
    "dev:content": "webpack --config webpack/dev-content.js --progress",
    "dev:bc": "yarn dev:background && yarn dev:content",
    "resource": "cd debug && yarn install && tsc && node ./dist/buildResource.js",
    "build:index": "yarn install && vue-cli-service build",
    "build:background": "webpack --config webpack/prod-background.js --progress",
    "build:content": "webpack --config webpack/prod-content.js --progress"
  },
  "dependencies": {
    "@typescript-eslint/eslint-plugin": "^4.4.0",
    "@typescript-eslint/parser": "^4.4.0",
    "basiccontext": "^3.5.1",
    "blueimp-md5": "^2.19.0",
    "crypto-js": "^3.1.9-1",
    "dayjs": "^1.11.5",
    "dom-to-image": "^2.6.0",
    "dotenv": "^8.2.0",
    "extend": "^3.0.2",
    "file-saver": "^2.0.5",
    "github-markdown-css": "^5.1.0",
    "highcharts": "^10.2.1",
    "highcharts-vue": "^1.4.0",
    "i18next": "^21.9.1",
    "jszip": "^3.10.1",
    "marked": "^4.2.4",
    "parse-torrent": "^7.0.1",
    "ua-parser-js": "^1.0.2",
    "url-parse": "^1.5.10",
    "vue": "~2.6.14",
    "vue-class-component": "^6.3.2",
    "vue-i18n": "^8.11.2",
    "vue-property-decorator": "^7.0.0",
    "vue-router": "~3.5.4",
    "vuetify": "^1.3.0",
    "vuex": "^3.0.1",
    "webdav": "^3.6.2"
  },
  "devDependencies": {
    "@types/blueimp-md5": "^2.18.0",
    "@types/chrome": "^0.0.75",
    "@types/crypto-js": "^3.1.43",
    "@types/dom-to-image": "^2.6.4",
    "@types/extend": "^3.0.1",
    "@types/file-saver": "^2.0.5",
    "@types/jquery": "^3.5.14",
    "@types/marked": "^4.0.8",
    "@types/parse-torrent": "^5.8.4",
    "@types/ua-parser-js": "^0.7.36",
    "@types/url-parse": "^1.4.8",
    "@vue/cli-plugin-babel": "^3.0.5",
    "@vue/cli-plugin-eslint": "^5.0.0",
    "@vue/cli-plugin-typescript": "^3.2.0",
    "@vue/cli-service": "^3.0.5",
    "@vue/eslint-config-typescript": "^11.0.0",
    "babel-eslint": "^10.0.1",
    "copy-webpack-plugin": "^4.6.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^9.0.0",
    "git-rev-sync": "^3.0.2",
    "sass": "^1.54.8",
    "sass-loader": "~7.3.1",
    "stylus": "^0.54.5",
    "stylus-loader": "^3.0.1",
    "terser-webpack-plugin": "^2.2.1",
    "ts-loader": "^5.3.1",
    "ts-node": "^8.5.2",
    "typescript": "^3.0.0",
    "uglifyjs-webpack-plugin": "~2.1.3",
    "vue-cli-plugin-vuetify": "^0.4.6",
    "vue-template-compiler": "~2.6.14",
    "vuetify-loader": "~1.7.3",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12",
    "webpack-merge": "^4.2.2"
  },
  "resolutions": {
    "@types/node": "~18.11.9"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended",
      "@vue/typescript"
    ],
    "rules": {
      "no-console": 0
    },
    "parserOptions": {
      "parser": "@typescript-eslint/parser"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}


================================================
FILE: privacy-statement.md
================================================
感谢使用PT助手(下称『助手』),为了让您能够安心的使用助手,特此向您说明助手的隐私权保护政策,以保障您的权益,请您详阅下列内容:

## 一、隐私权保护政策的适用范围
- 隐私权保护政策仅适用于助手,不适用于助手以外的相关网站;

## 二、个人信息的收集、处理及利用方式
- 在您使用助手的过程中,助手会使用您的个人信息进行展示,展示内容包括:
  - 您在已配置站点里的个人信息;
  - 已保存的个人信息历史记录生成图表;
- 个人信息仅用于在助手里展示,不会用于其他用途;

## 三、信息存储和交换
- 在您使用助手的过程中,会产生一些配置数据和历史记录,这些信息全部存储于当前浏览器;
- 当您已配置了备份服务器,这些信息会由您决定是否上传至这些服务器;
- 除此之外,助手不会将这些数据上传至任何第三方服务器;
- 如果您选择了使用备份服务器,我们强烈建议您使用以下方式进行配置:
  - 使用 `https` 的方式配置服务器地址;
  - 启用备份数据加密(采用 [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard))功能后再进行备份操作;

## 四、与第三人共用个人信息之政策
- 助手不会收集任何相关的个人信息,所以助手绝不会提供、交换、出租或出售任何您的个人信息给其他个人、团体、私人企业或公务机关;

## 五、Cookie之使用
- 当您进行搜索操作时,助手会访问您已配置的站点,Cookie 由浏览器提供,助手无权访问也不会对内容进行探测和修改,一切内容由浏览器和站点进行相互验证;
- 当您授权助手访问 Cookie 信息时,这些信息仅用于备份和恢复操作,助手不会在除此之外的任何其他操作中使用它们;

## 六、隐私权保护政策之修正
- 助手隐私权保护政策将按用户需求变更而随时进行修正,修正后的条款将在本页面显示。


================================================
FILE: public/_locales/en/messages.json
================================================
{
	"manifest_appName": {
		"message": "PT Plugin Plus"
	},
	"manifest_shortName": {
		"message": "PT Plugin Plus"
	},
	"manifest_appDescription": {
		"message": "Just a tools for Private Tracker."
	}
}

================================================
FILE: public/_locales/zh_CN/messages.json
================================================
{
	"manifest_appName": {
		"message": "PT Plugin Plus"
	},
	"manifest_shortName": {
		"message": "PTPP"
	},
	"manifest_appDescription": {
		"message": "PT 助手,一个可以提升 PT 站点使用效率的工具。"
	}
}

================================================
FILE: public/assets/base.css
================================================
.pt-plugin-body {
  background-color: aliceblue;
  border-radius: 8px;
  padding-bottom: 5px;
  width: 80px;
  position: fixed;
  right: 5px;
  opacity: .3;
  z-index: 10000;
  font-size: 12px;
}

.pt-plugin-body .pt-plugin-drag-title {
  cursor: move;
  width: 100%;
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  background-color: #ffc107;
  height: 7px;
}

.pt-plugin-body:hover,
.pt-plugin-body-over {
  opacity: 0.9
}

.pt-plugin-body hr {
  height: 2px;
  border: 0;
  border-bottom: 1px dotted #ccc;
  margin: 4px 5px 5px 5px;
}

.pt-plugin-body .pt-plugin-logo {
  background-image: url('chrome-extension://__MSG_@@extension_id__/assets/icon-64.png');
  background-size: cover;
  height: 32px;
  width: 32px;
  margin: 5px auto;
  opacity: 0.7;
  cursor: pointer;
}

@-moz-document url-prefix() {
  .pt-plugin-body .pt-plugin-logo {
    background-image: url('moz-extension://__MSG_@@extension_id__/assets/icon-64.png');
  }
}


.pt-plugin-body .pt-plugin-droper {
  height: 45px;
  width: 100%;
  background-color: #1976d2;
  opacity: 0.2;
  border-radius: 5px;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
}

.pt-plugin-body .pt-plugin-button {
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #1976d2;
  text-decoration: none;
}

.pt-plugin-body a.pt-plugin-button {
  cursor: pointer;
  text-decoration: none;
  color: #1976d2;
}

.pt-plugin-body a.pt-plugin-button:hover {
  background-color: rgba(77, 154, 231, 0.226)
}

.pt-plugin-body .pt-plugin-button .pt-plugin-button-inner {
  text-align: center;
}

.pt-plugin-body .pt-plugin-onLoading {
  animation: onLoading 1.9s linear infinite running;
}

/* 正在执行动画 */
.pt-plugin-body .pt-plugin-loading {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border-width: 5px;
  border-style: solid;
  border-color: #1976d2;
  border-left-style: dashed;
  border-left-color: rgba(77, 154, 231, 0.226);
  animation: onLoading 1.9s linear infinite running;
  display: none;
}

@keyframes onLoading {
  100% {
    transform: rotate(360deg);
  }
}

.action-success {
  width: 40px;
  height: 40px;
  position: relative;
  /* background: #4caf50; */
  background-color: transparent;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  display: none;
}

.action-success-ani {
  width: 100%;
  height: 50%;
  margin: 10% auto;
  transform: rotate(-45deg);
  transform: rotate(-45deg);
  overflow: hidden;
}

.action-success-ani:before,
.action-success-ani:after {
  content: "";
  position: absolute;
  background: #4caf50;
  border-radius: 2px
}

.action-success-ani:before {
  width: 4px;
  height: 100%;
  left: 0;
  animation: onActionSuccessLeft 0.2s linear 0.2s 1 both;
  animation: onActionSuccessLeft 0.2s linear 0.2s 1 both
}

.action-success-ani:after {
  width: 100%;
  height: 4px;
  bottom: 0;
  animation: onActionSuccessRight 0.2s linear 0.4s 1 both;
  animation: onActionSuccessRight 0.2s linear 0.4s 1 both
}

@keyframes onActionSuccessLeft {
  0% {
    top: -100%
  }

  100% {
    top: 0%
  }
}

@keyframes onActionSuccessLeft {
  0% {
    top: -100%
  }

  100% {
    top: 0%
  }
}

@keyframes onActionSuccessRight {
  0% {
    left: -100%
  }

  100% {
    left: 0%
  }
}

@keyframes onActionSuccessRight {
  0% {
    left: -100%
  }

  100% {
    left: 0%
  }
}

================================================
FILE: public/assets/options.css
================================================
.btn-mini {
  width: 20px !important;
  height: 20px !important;
  font-size: 12px !important;
}

.btn-mini .v-btn__content i {
  font-size: 12px !important;
}

================================================
FILE: public/changelog.html
================================================
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title><%= htmlWebpackPlugin.options.title %></title>
  <link rel="icon" type="image/x-icon" href="./assets/icon-19.png">
</head>

<body>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>

</html>


================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title><%= htmlWebpackPlugin.options.title %></title>
  <link rel="stylesheet" href="./libs/materialIcons/style.css">
  <link rel="stylesheet" href="./assets/options.css">
  <link rel="icon" type="image/x-icon" href="./assets/icon-19.png">
  <script src="./libs/jquery/jquery-3.3.1.min.js"></script>
  <script src="./libs/Base64.js"></script>
</head>

<body>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>

</html>

================================================
FILE: public/libs/Base64.js
================================================
/**
*
*  Base64 encode / decode
*
*  @author haitao.tu
*  @date   2010-04-26
*  @email  tuhaitao@foxmail.com
*
*/
 
function Base64() {
 
	// private property
	_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
 
	// public method for encoding
	this.encode = function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;
		input = _utf8_encode(input);
		while (i < input.length) {
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}
			output = output +
			_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
			_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
		}
		return output;
	}
 
	// public method for decoding
	this.decode = function (input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		while (i < input.length) {
			enc1 = _keyStr.indexOf(input.charAt(i++));
			enc2 = _keyStr.indexOf(input.charAt(i++));
			enc3 = _keyStr.indexOf(input.charAt(i++));
			enc4 = _keyStr.indexOf(input.charAt(i++));
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
			output = output + String.fromCharCode(chr1);
			if (enc3 != 64) {
				output = output + String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output = output + String.fromCharCode(chr3);
			}
		}
		output = _utf8_decode(output);
		return output;
	}
 
	// private method for UTF-8 encoding
	_utf8_encode = function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";
		for (var n = 0; n < string.length; n++) {
			var c = string.charCodeAt(n);
			if (c < 128) {
				utftext += String.fromCharCode(c);
			} else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			} else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
 
		}
		return utftext;
	}
 
	// private method for UTF-8 decoding
	_utf8_decode = function (utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;
		while ( i < utftext.length ) {
			c = utftext.charCodeAt(i);
			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			} else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			} else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}
		}
		return string;
	}
}

================================================
FILE: public/libs/drag.js
================================================
/**
 * 拖放功能
 * @see http://demo.jb51.net/js/2015/js-mxdx-draw-plug-codes/
 */
function Drag() {
  // 初始化
  this.initialize.apply(this, arguments);
}
Drag.prototype = {
  // 参数设置
  setOptions: function(options) {
    this.options = {
      // 事件对象
      handle: this.drag,
      // 锁定范围
      limit: true,
      // 锁定位置
      lock: false,
      // 锁定水平位置
      lockX: false,
      // 锁定垂直位置
      lockY: false,
      // 指定限制容器
      maxContainer: document.documentElement || document.body,

      // 开始时回调函数
      onStart: function() {},
      // 拖拽时回调函数
      onMove: function() {},
      // 停止时回调函数
      onStop: function() {}
    };
    for (var p in options) this.options[p] = options[p];
  },
  // 初始化
  initialize: function(drag, options) {
    this.drag = this.$(drag);
    this._x = this._y = this.left = this.top = 0;
    this._moveDrag = this.bind(this, this.moveDrag);
    this._stopDrag = this.bind(this, this.stopDrag);

    this.setOptions(options);

    this.handle = this.$(this.options.handle);
    this.maxContainer = this.$(this.options.maxContainer);

    this.maxTop =
      Math.max(this.maxContainer.clientHeight, this.maxContainer.scrollHeight) -
      this.drag.offsetHeight;
    this.maxLeft =
      Math.max(this.maxContainer.clientWidth, this.maxContainer.scrollWidth) -
      this.drag.offsetWidth;
    this.limit = this.options.limit;
    this.lockX = this.options.lockX;
    this.lockY = this.options.lockY;
    this.lock = this.options.lock;
    this.onStart = this.options.onStart;
    this.onMove = this.options.onMove;
    this.onStop = this.options.onStop;
    this.handle.style.cursor = "move";
    this.addHandler(this.handle, "mousedown", this.bind(this, this.startDrag));
  },
  startDrag: function(event) {
    var event = event || window.event;
    this._x = event.clientX - this.drag.offsetLeft;
    this._y = event.clientY - this.drag.offsetTop;
    this.addHandler(document, "mousemove", this._moveDrag);
    this.addHandler(document, "mouseup", this._stopDrag);
    event.preventDefault && event.preventDefault();
    this.handle.setCapture && this.handle.setCapture();
    this.onStart();
  },
  moveDrag: function(event) {
    var event = event || window.event;
    var iTop = event.clientY - this._y;
    var iLeft = event.clientX - this._x;
    if (this.lock) return;
    this.limit &&
      (iTop < 0 && (iTop = 0),
      iLeft < 0 && (iLeft = 0),
      iTop > this.maxTop && (iTop = this.maxTop),
      iLeft > this.maxLeft && (iLeft = this.maxLeft));
    this.lockY || (this.drag.style.top = iTop + "px");
    this.lockX || (this.drag.style.left = iLeft + "px");
    event.preventDefault && event.preventDefault();
    this.left = iLeft;
    this.top = iTop;
    this.onMove();
  },
  stopDrag: function() {
    this.removeHandler(document, "mousemove", this._moveDrag);
    this.removeHandler(document, "mouseup", this._stopDrag);
    this.handle.releaseCapture && this.handle.releaseCapture();
    this.onStop({
      left: this.left,
      top: this.top
    });
  },

  // 获取id
  $: function(id) {
    return typeof id === "string" ? document.getElementById(id) : id;
  },
  // 添加绑定事件
  addHandler: function(oElement, sEventType, fnHandler) {
    return oElement.addEventListener
      ? oElement.addEventListener(sEventType, fnHandler, false)
      : oElement.attachEvent("on" + sEventType, fnHandler);
  },
  // 删除绑定事件
  removeHandler: function(oElement, sEventType, fnHandler) {
    return oElement.removeEventListener
      ? oElement.removeEventListener(sEventType, fnHandler, false)
      : oElement.detachEvent("on" + sEventType, fnHandler);
  },
  // 绑定事件到对象
  bind: function(object, fnHandler) {
    return function() {
      return fnHandler.apply(object, arguments);
    };
  }
};


================================================
FILE: public/libs/materialIcons/content_style.css
================================================
/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url('chrome-extension://__MSG_@@extension_id__/libs/materialIcons/font.woff2') format('woff2');
}

/*This will work for firefox*/
@-moz-document url-prefix() {
  @font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: url('moz-extension://__MSG_@@extension_id__/libs/materialIcons/font.woff2') format('woff2');
  }
}

.material-icons {
  font-family: 'Material Icons';
  font-weight: normal;
  font-style: normal;
  font-size: 24px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
}

================================================
FILE: public/libs/materialIcons/style.css
================================================
/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(font.woff2) format('woff2');
}

.material-icons {
  font-family: 'Material Icons';
  font-weight: normal;
  font-style: normal;
  font-size: 24px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
}


================================================
FILE: public/libs/notice/notice.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("NoticeJs", [], factory);
	else if(typeof exports === 'object')
		exports["NoticeJs"] = factory();
	else
		root["NoticeJs"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "dist/";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 2);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});
var noticeJsModalClassName = exports.noticeJsModalClassName = 'noticejs-modal';
var closeAnimation = exports.closeAnimation = 'noticejs-fadeOut';

var Defaults = exports.Defaults = {
    title: '',
    text: '',
    type: 'success',
    position: 'topRight',
    newestOnTop: false,
    timeout: 30,
    progressBar: true,
    indeterminate: false,
    closeWith: ['button'],
    animation: null,
    modal: false,
    width: 320,
    scroll: {
        maxHeightContent: 300,
        showOnHover: true
    },
    rtl: false,
    callbacks: {
        beforeShow: [],
        onShow: [],
        afterShow: [],
        onClose: [],
        afterClose: [],
        onClick: [],
        onHover: [],
        onTemplate: []
    }
};

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.appendNoticeJs = exports.addListener = exports.CloseItem = exports.AddModal = undefined;
exports.getCallback = getCallback;

var _api = __webpack_require__(0);

var API = _interopRequireWildcard(_api);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

var options = API.Defaults;

/**
 * @param {NoticeJs} ref
 * @param {string} eventName
 * @return {void}
 */
function getCallback(ref, eventName) {
    if (ref.callbacks.hasOwnProperty(eventName)) {
        ref.callbacks[eventName].forEach(function (cb) {
            if (typeof cb === 'function') {
                cb.apply(ref);
            }
        });
    }
}

var AddModal = exports.AddModal = function AddModal() {
    if (document.getElementsByClassName(API.noticeJsModalClassName).length <= 0) {
        var element = document.createElement('div');
        element.classList.add(API.noticeJsModalClassName);
        element.classList.add('noticejs-modal-open');
        document.body.appendChild(element);
        // Remove class noticejs-modal-open
        setTimeout(function () {
            element.className = API.noticeJsModalClassName;
        }, 200);
    }
};

var CloseItem = exports.CloseItem = function CloseItem(item) {
    getCallback(options, 'onClose');

    // Set animation to close notification item
    if (options.animation !== null && options.animation.close !== null) {
        item.className += ' ' + options.animation.close;
    }
    setTimeout(function () {
        item.remove();
    }, 200);

    // Close modal
    if (options.modal === true && document.querySelectorAll("[noticejs-modal='true']").length >= 1) {
        document.querySelector('.noticejs-modal').className += ' noticejs-modal-close';
        setTimeout(function () {
            document.querySelector('.noticejs-modal').remove();
        }, 500);
    }

    // Remove container
    var position = '.' + item.closest('.noticejs').className.replace('noticejs', '').trim();
    setTimeout(function () {
        if (document.querySelectorAll(position + ' .item').length <= 0) {
            document.querySelector(position) && document.querySelector(position).remove();
        }
    }, 500);
};

var addListener = exports.addListener = function addListener(item) {
    // Add close button Event
    if (options.closeWith.includes('button')) {
        item.querySelector('.close').addEventListener('click', function () {
            CloseItem(item);
        });
    }

    // Add close by click Event
    if (options.closeWith.includes('click')) {
        item.style.cursor = 'pointer';
        item.addEventListener('click', function (e) {
            if (e.target.className !== 'close') {
                getCallback(options, 'onClick');
                CloseItem(item);
            }
        });
    } else {
        item.addEventListener('click', function (e) {
            if (e.target.className !== 'close') {
                getCallback(options, 'onClick');
            }
        });
    }

    item.addEventListener('mouseover', function () {
        getCallback(options, 'onHover');
    });
};

var appendNoticeJs = exports.appendNoticeJs = function appendNoticeJs(noticeJsHeader, noticeJsBody, noticeJsProgressBar) {
    var target_class = '.noticejs-' + options.position;
    // Create NoticeJs item
    var noticeJsItem = document.createElement('div');
    noticeJsItem.classList.add('item');
    noticeJsItem.classList.add(options.type);
    if (options.rtl === true) {
        noticeJsItem.classList.add('noticejs-rtl');
    }
    if (options.width !== '' && Number.isInteger(options.width)) {
        noticeJsItem.style.width = options.width + 'px';
    }

    // Add Header
    if (noticeJsHeader && noticeJsHeader !== '') {
        noticeJsItem.appendChild(noticeJsHeader);
    }

    // Add body
    noticeJsItem.appendChild(noticeJsBody);

    // Add progress bar
    if (noticeJsProgressBar && noticeJsProgressBar !== '') {
        noticeJsItem.appendChild(noticeJsProgressBar);
    }

    // Empty top and bottom container
    if (['top', 'bottom'].includes(options.position)) {
        document.querySelector(target_class).innerHTML = '';
    }

    // Add open animation
    if (options.animation !== null && options.animation.open !== null) {
        noticeJsItem.className += ' ' + options.animation.open;
    }

    // Add Modal
    if (options.modal === true) {
        noticeJsItem.setAttribute('noticejs-modal', 'true');
        AddModal();
    }

    // Add Listener
    addListener(noticeJsItem, options.closeWith);

    getCallback(options, 'beforeShow');
    getCallback(options, 'onShow');
    if (options.newestOnTop === true) {
        document.querySelector(target_class).insertAdjacentElement('afterbegin', noticeJsItem);
    } else {
        document.querySelector(target_class).appendChild(noticeJsItem);
    }
    getCallback(options, 'afterShow');

    return noticeJsItem;
};

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _noticejs = __webpack_require__(3);

var _noticejs2 = _interopRequireDefault(_noticejs);

var _api = __webpack_require__(0);

var API = _interopRequireWildcard(_api);

var _components = __webpack_require__(4);

var _helpers = __webpack_require__(1);

var helper = _interopRequireWildcard(_helpers);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var NoticeJs = function () {
  /**
   * @param {object} options 
   * @returns {Noty}
   */
  function NoticeJs() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    _classCallCheck(this, NoticeJs);

    this.options = Object.assign(API.Defaults, options);
    this.component = new _components.Components();
    this.id = "noticejs-" + Math.random();

    this.on('beforeShow', this.options.callbacks.beforeShow);
    this.on('onShow', this.options.callbacks.onShow);
    this.on('afterShow', this.options.callbacks.afterShow);
    this.on('onClose', this.options.callbacks.onClose);
    this.on('afterClose', this.options.callbacks.afterClose);
    this.on('onClick', this.options.callbacks.onClick);
    this.on('onHover', this.options.callbacks.onHover);

    return this;
  }

  /**
   * @returns {NoticeJs}
   */


  _createClass(NoticeJs, [{
    key: 'show',
    value: function show() {
      var container = this.component.createContainer();
      if (document.querySelector('.noticejs-' + this.options.position) === null) {
        document.body.appendChild(container);
      }

      var noticeJsHeader = void 0;
      var noticeJsBody = void 0;
      var noticeJsProgressBar = void 0;

      // Create NoticeJs header
      noticeJsHeader = this.component.createHeader(this.options.title, this.options.closeWith);

      // Create NoticeJs body
      noticeJsBody = this.component.createBody(this.options.text);

      // Create NoticeJs progressBar
      if (this.options.progressBar === true) {
        noticeJsProgressBar = this.component.createProgressBar();
      }

      //Append NoticeJs
      var noticeJs = helper.appendNoticeJs(noticeJsHeader, noticeJsBody, noticeJsProgressBar);
      noticeJs.setAttribute("id", this.id);
      this.dom = noticeJs;
      return noticeJs;
    }

    /**
     * @param {string} eventName
     * @param {function} cb
     * @return {NoticeJs}
     */

  }, {
    key: 'on',
    value: function on(eventName) {
      var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};

      if (typeof cb === 'function' && this.options.callbacks.hasOwnProperty(eventName)) {
        this.options.callbacks[eventName].push(cb);
      }

      return this;
    }

    /**
     * @param {Object} options 
     * @return {Notice}
     */

  }, {
    key: 'close',


    /**
     * close
     */
    value: function close() {
      helper.CloseItem(this.dom);
    }
  }], [{
    key: 'overrideDefaults',
    value: function overrideDefaults(options) {
      this.options = Object.assign(API.Defaults, options);
      return this;
    }
  }]);

  return NoticeJs;
}();

exports.default = NoticeJs;
module.exports = exports['default'];

/***/ }),
/* 3 */
/***/ (function(module, exports) {

// removed by extract-text-webpack-plugin

/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Components = undefined;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _api = __webpack_require__(0);

var API = _interopRequireWildcard(_api);

var _helpers = __webpack_require__(1);

var helper = _interopRequireWildcard(_helpers);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var options = API.Defaults;

var Components = exports.Components = function () {
  function Components() {
    _classCallCheck(this, Components);
  }

  _createClass(Components, [{
    key: 'createContainer',
    value: function createContainer() {
      var element_class = 'noticejs-' + options.position;
      var element = document.createElement('div');
      element.classList.add('noticejs');
      element.classList.add(element_class);

      return element;
    }
  }, {
    key: 'createHeader',
    value: function createHeader() {
      var element = void 0;
      if (options.title && options.title !== '') {
        element = document.createElement('div');
        element.setAttribute('class', 'noticejs-heading');
        element.textContent = options.title;
      }

      // Add close button
      if (options.closeWith.includes('button')) {
        var close = document.createElement('div');
        close.setAttribute('class', 'close');
        close.innerHTML = '&times;';
        if (element) {
          element.appendChild(close);
        } else {
          element = close;
        }
      }

      return element;
    }
  }, {
    key: 'createBody',
    value: function createBody() {
      var element = document.createElement('div');
      element.setAttribute('class', 'noticejs-body');
      var content = document.createElement('div');
      content.setAttribute('class', 'noticejs-content');
      content.innerHTML = options.text;
      element.appendChild(content);

      if (options.scroll !== null && options.scroll.maxHeight !== '') {
        element.style.overflowY = 'auto';
        element.style.maxHeight = options.scroll.maxHeight + 'px';

        if (options.scroll.showOnHover === true) {
          element.style.visibility = 'hidden';
        }
      }
      return element;
    }
  }, {
    key: 'createProgressBar',
    value: function createProgressBar() {
      var element = document.createElement('div');
      element.setAttribute('class', 'noticejs-progressbar');
      var bar = document.createElement('div');
      bar.setAttribute('class', 'noticejs-bar');
      element.appendChild(bar);

      // Progress bar animation
      if (options.progressBar === true && typeof options.timeout !== 'boolean' && options.timeout !== false) {
        var _frame = function _frame() {
          if (width <= 0) {
            clearInterval(id);

            var item = element.closest('div.item');
            // Add close animation
            if (options.animation !== null && options.animation.close !== null) {

              // Remove open animation class
              item.className = item.className.replace(new RegExp('(?:^|\\s)' + options.animation.open + '(?:\\s|$)'), ' ');
              // Add close animation class
              item.className += ' ' + options.animation.close;

              // Close notification after 0.5s + timeout
              var close_time = parseInt(options.timeout) + 500;
              setTimeout(function () {
                helper.CloseItem(item);
              }, close_time);
            } else {
              // Close notification when progress bar completed
              helper.CloseItem(item);
            }
          } else {
            width--;
            bar.style.width = width + '%';
          }
        };

        var _indeterminateFrame = function _indeterminateFrame() {
          if (progressDirection === 0) {
            width--;
            if (width === 0) {
              progressDirection = 1;
            }
          } else {
            width++;
            if (width === 100) {
              progressDirection = 0;
            }
          }

          if (document.getElementById(domId) == null) {
            clearInterval(id);
          } else {
            bar.style.width = width + '%';
          }
        };

        var width = 100;
        var progressDirection = 0;
        var id = 0;
        var domId = "";

        if (options.indeterminate === true) {
          id = setInterval(_indeterminateFrame, options.timeout);
          domId = "noticejs-progressbar-" + id;
          element.setAttribute("id", domId);
        } else {
          id = setInterval(_frame, options.timeout);
        }
      }

      return element;
    }
  }]);

  return Components;
}();

/***/ })
/******/ ]);
});

================================================
FILE: public/libs/notice/noticejs.css
================================================
.noticejs-top{top:0;width:100%!important}.noticejs-top .item{border-radius:0!important;margin:0!important}.noticejs-topRight{top:10px;right:10px}.noticejs-topLeft{top:10px;left:10px}.noticejs-topCenter{top:10px;left:50%;transform:translate(-50%)}.noticejs-middleLeft,.noticejs-middleRight{right:10px;top:50%;transform:translateY(-50%)}.noticejs-middleLeft{left:10px}.noticejs-middleCenter{top:50%;left:50%;transform:translate(-50%,-50%)}.noticejs-bottom{bottom:0;width:100%!important}.noticejs-bottom .item{border-radius:0!important;margin:0!important}.noticejs-bottomRight{bottom:10px;right:10px}.noticejs-bottomLeft{bottom:10px;left:10px}.noticejs-bottomCenter{bottom:10px;left:50%;transform:translate(-50%)}.noticejs{font-family:Helvetica Neue,Helvetica,Arial,sans-serif}.noticejs .item{margin:0 0 10px;border-radius:3px;overflow:hidden}.noticejs .item .close{float:right;font-size:18px;font-weight:700;line-height:1;color:#fff;text-shadow:0 1px 0 #fff;opacity:1;margin-right:7px}.noticejs .item .close:hover{opacity:.5;color:#000}.noticejs .item a{color:#fff;border-bottom:1px dashed #fff}.noticejs .item a,.noticejs .item a:hover{text-decoration:none}.noticejs .success{background-color:#64ce83}.noticejs .success .noticejs-heading{background-color:#3da95c;color:#fff;padding:10px}.noticejs .success .noticejs-body{color:#fff;padding:10px}.noticejs .success .noticejs-body:hover{visibility:visible!important}.noticejs .success .noticejs-content{visibility:visible}.noticejs .info{background-color:#3ea2ff}.noticejs .info .noticejs-heading{background-color:#067cea;color:#fff;padding:10px}.noticejs .info .noticejs-body{color:#fff;padding:10px}.noticejs .info .noticejs-body:hover{visibility:visible!important}.noticejs .info .noticejs-content{visibility:visible}.noticejs .warning{background-color:#ff7f48}.noticejs .warning .noticejs-heading{background-color:#f44e06;color:#fff;padding:10px}.noticejs .warning .noticejs-body{color:#fff;padding:10px}.noticejs .warning .noticejs-body:hover{visibility:visible!important}.noticejs .warning .noticejs-content{visibility:visible}.noticejs .error{background-color:#e74c3c}.noticejs .error .noticejs-heading{background-color:#ba2c1d;color:#fff;padding:10px}.noticejs .error .noticejs-body{color:#fff;padding:10px}.noticejs .error .noticejs-body:hover{visibility:visible!important}.noticejs .error .noticejs-content{visibility:visible}.noticejs .progressbar{width:100%}.noticejs .progressbar .bar{width:1%;height:30px;background-color:#4caf50}.noticejs .success .noticejs-progressbar{width:100%;background-color:#64ce83;margin-top:-1px}.noticejs .success .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#3da95c}.noticejs .info .noticejs-progressbar{width:100%;background-color:#3ea2ff;margin-top:-1px}.noticejs .info .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#067cea}.noticejs .warning .noticejs-progressbar{width:100%;background-color:#ff7f48;margin-top:-1px}.noticejs .warning .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#f44e06}.noticejs .error .noticejs-progressbar{width:100%;background-color:#e74c3c;margin-top:-1px}.noticejs .error .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#ba2c1d}@keyframes noticejs-fadeOut{0%{opacity:1}to{opacity:0}}.noticejs-fadeOut{animation-name:noticejs-fadeOut}@keyframes noticejs-modal-in{to{opacity:.3}}@keyframes noticejs-modal-out{to{opacity:0}}.noticejs-rtl .noticejs-heading{direction:rtl}.noticejs-rtl .close{float:left!important;margin-left:7px;margin-right:0!important}.noticejs-rtl .noticejs-content{direction:rtl}.noticejs{position:fixed;z-index:10050}.noticejs ::-webkit-scrollbar{width:8px}.noticejs ::-webkit-scrollbar-button{width:8px;height:5px}.noticejs ::-webkit-scrollbar-track{border-radius:10px}.noticejs ::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.5);border-radius:10px}.noticejs ::-webkit-scrollbar-thumb:hover{background:#fff}.noticejs-modal{position:fixed;width:100%;height:100%;background-color:#000;z-index:10000;opacity:.3;left:0;top:0}.noticejs-modal-open{opacity:0;animation:noticejs-modal-in .3s ease-out}.noticejs-modal-close{animation:noticejs-modal-out .3s ease-out;animation-fill-mode:forwards}

================================================
FILE: public/libs/types.expand.js
================================================
String.prototype.getQueryString = function(name, split) {
  if (split == undefined) split = "&";
  var rule =
    "(^|" + split + "|\\?)" + name + "=([^" + split + "#]*)(" + split + "|#|$)";
  var reg = new RegExp(rule),
    r;
  if ((r = this.match(reg))) return decodeURI(r[2]);
  return null;
};

/**
 * @return {number}
 */
String.prototype.sizeToNumber = function() {
  let _size_raw_match = this.match(
    /^(\d*\.?\d+)(.*[^ZEPTGMK])?([ZEPTGMK](B|iB))$/i
  );
  if (_size_raw_match) {
    let _size_num = parseFloat(_size_raw_match[1]);
    let _size_type = _size_raw_match[3];
    switch (true) {
      case /Zi?B/i.test(_size_type):
        return _size_num * Math.pow(2, 70);
      case /Ei?B/i.test(_size_type):
        return _size_num * Math.pow(2, 60);
      case /Pi?B/i.test(_size_type):
        return _size_num * Math.pow(2, 50);
      case /Ti?B/i.test(_size_type):
        return _size_num * Math.pow(2, 40);
      case /Gi?B/i.test(_size_type):
        return _size_num * Math.pow(2, 30);
      case /Mi?B/i.test(_size_type):
        return _size_num * Math.pow(2, 20);
      case /Ki?B/i.test(_size_type):
        return _size_num * Math.pow(2, 10);
      default:
        return _size_num;
    }
  }
  return 0;
};


================================================
FILE: public/manifest.json
================================================
{
	"name": "__MSG_manifest_appName__",
	"short_name": "__MSG_manifest_shortName__",
	"version": "1.6.1",
	"description": "__MSG_manifest_appDescription__",
	"manifest_version": 2,
	"default_locale": "zh_CN",
	"homepage_url": "https://github.com/pt-plugins/PT-Plugin-Plus",
	"browser_action": {
		"default_icon": "assets/icon-19.png",
		"default_title": "__MSG_manifest_appName__"
	},
	"permissions": [
		"activeTab",
		"clipboardRead",
		"clipboardWrite",
		"storage",
		"contextMenus",
		"notifications",
		"http://*/*",
		"https://*/*",
		"unlimitedStorage"
	],
	"optional_permissions": ["downloads", "cookies"],
	"icons": {
		"16": "assets/icon.png",
		"19": "assets/icon-19.png",
		"64": "assets/icon-64.png",
		"128": "assets/icon-128.png"
	},
	"options_ui": {
		"page": "index.html",
		"open_in_tab": true
	},
	"background": {
		"scripts": [
			"libs/types.expand.js",
			"libs/jquery/jquery-3.3.1.min.js",
			"libs/Base64.js",
			"js/background/libs.js",
			"js/background/background.js"
		]
	},
	"content_scripts": [{
		"matches": [
			"http://*/*",
			"https://*/*"
		],
		"exclude_matches": [
			"https://fonts.google.com/*"
		],
		"css": [
			"assets/base.css",
			"libs/materialIcons/content_style.css",
			"libs/notice/noticejs.css",
			"libs/basicContext/basicContext.min.css",
			"libs/basicContext/themes/default.min.css"
		],
		"js": [
			"libs/types.expand.js",
			"libs/jquery/jquery-3.3.1.min.js",
			"libs/Base64.js",
			"libs/notice/notice.js",
			"libs/basicContext/basicContext.min.js",
			"libs/drag.js",
			"js/content/libs.js",
			"js/content/content.js"
		]
	}],
	"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
	"web_accessible_resources": [
		"libs/materialIcons/*.woff2",
		"assets/*",
		"resource/*"
	],
	"omnibox": {
		"keyword": "pt"
	},
	"minimum_chrome_version": "64.0.3242",
	"browser_specific_settings": {
		"gecko": {
			"update_url": "https://pt-plugins.github.io/PT-Plugin-Plus/update/firefox.json"
		}
	}
}


================================================
FILE: public/popup.html
================================================
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>PT 助手</title>
  <script type="text/javascript" src="./libs/jquery/jquery-3.3.1.min.js"></script>
</head>

<body style="width: 70px;">
  <button id="btnConfig">参数配置</button>
  <button id="btnSystemLog">查看日志</button>
</body>

<script type="text/javascript" src="./js/popup.js"></script>

</html>

================================================
FILE: resource/clients/README.md
================================================
# 下载客户端定义说明

## 目录说明

该目录存放所有支持的下载客户端,目录名为架构名称

```
--目录名
----config.json
----init.js
```

- 目录名为该客户端的类型
- config.json : 下载客户端定义文件
- init.js : 下载客户端初始化脚本

## config.json 文件

示例:

```json
{
  "name": "qBittorrent",
  "type": "qbittorrent",
  "ver": "0.0.1",
  "icon": "https://www.qbittorrent.org/favicon.ico",
  "scripts": ["init.js"],
  "description": "当前支持 qBittorrent v4.1+,由于浏览器限制,需要禁用 qBittorrent 的『启用跨站请求伪造(CSRF)保护』功能才能正常使用",
  "warning": "注意:由于 qBittorrent 验证机制限制,第一次测试连接成功后,后续测试无论密码正确与否都会提示成功。",
  "allowCustomPath": true,
  "pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/ 或 D:\\download\\music\\"
}
```

- `name` : 客户端名称
- `type` : 客户端类型,必需和目录名相同
- `ver` : 当前定义的版本号,目前暂无特别用处
- `icon` : 用于显示客户端的图标
- `scripts`: <可选>数组,用于执行该客户端执行的脚本列表,目前暂无特别用处
- `description` : <可选>当前客户端描述
- `warning` : <可选>用于配置时显示的警告信息,要用于一些特殊提示
- `allowCustomPath` : <可选>是否允许自定义目录,默认为 false
- `pathDescription` : <可选>自定义目录说明

## init.js

> 客户端初始化脚本文件

- 脚本最终需要将自身挂载到 `window` 对象下,挂载名称必需和 `type` 相同,且区分大小写!
  - 如:type='uTorrent'
  - 那么挂载名称为 `window.uTorrent` = xxx;

* 客户端对象需对外暴露以下公用方法
  - `init` : 用于初始化客户端,接收一个参数:`options` 表示客户端定义的相关参数
    - 属性参考 [common.ts](https://github.com/pt-plugins/PT-Plugin-Plus/blob/master/src/interface/common.ts) 的 `DownloadClient`
  - `call` : 用于方法调用,接收两个参数:`action`, `data`,返回一个 `Promise` 对象
    - `action` : 字符串,需要执行的方法(动作)名称,方法说明:
      - `addTorrentFromURL` : 从指定的链接增加种子文件
      - `testClientConnectivity` : 测试当前客户端是否可连接
      - 更多方法参考 [common.ts](https://github.com/pt-plugins/PT-Plugin-Plus/blob/master/src/interface/common.ts) 的 `EAction`
    - `data` : 任意类型,接收的数据,根据 `action` 不同,数据格式也不同

- 在脚本中可用的系统对象
  - `PTBackgroundService` : 助手后台服务程序,详情参考 [service.ts](https://github.com/pt-plugins/PT-Plugin-Plus/blob/master/src/background/service.ts)
  - `PTSevriceFilters` : 系统定义的过滤器,详情参考 [filters.ts](https://github.com/pt-plugins/PT-Plugin-Plus/blob/master/src/service/filters.ts)


================================================
FILE: resource/clients/deluge/config.json
================================================
{
  "name": "Deluge",
  "type": "deluge",
  "ver": "0.0.1",
  "icon": "https://www.deluge-torrent.org/images/deluge-icon.png",
  "scripts": [
    "init.js"
  ],
  "passwordOnly": true,
  "warning": "注意:由于 Deluge 验证机制限制,第一次测试连接成功后,后续测试无论密码正确与否都会提示成功。",
  "allowCustomPath": true,
  "pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/ 或 D:\\download\\music\\"
}

================================================
FILE: resource/clients/deluge/init.js
================================================
/**
 * @see https://deluge.readthedocs.io/en/develop/reference/index.html
 */
(function ($) {
  //Deluge
  // id:1,method:auth.login,params:[url,null]
  // 380 web.download_torrent_from_url
  // 2 core.add_torrent_url
  class Deluge {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * url: 服务器地址
     */
    init(options) {
      this.options = options;
      this.requestCount = -1;

      if (this.options.address.indexOf("/json") == -1) {
        let url = PTServiceFilters.parseURL(this.options.address);
        let address = [url.protocol, "://", url.host];
        if (url.port) {
          address.push(`:${url.port}`);
        }

        address.push(url.path);
        if (url.path.substr(-1) != "/") {
          address.push("/");
        }

        address.push("json");
        this.options.address = address.join("");
      }
      console.log("Deluge.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("Deluge.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, result => {
              resolve(result);
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.getSessionId()
              .then(result => {
                resolve(result != "");
              })
              .catch(result => {
                reject(result);
              });
            break;
        }
      });
    }

    /**
     * 获取Session
     * @param {*} callback
     */
    getSessionId(callback) {
      return new Promise((resolve, reject) => {
        var data = {
          id: ++this.requestCount,
          method: "auth.login",
          params: [this.options.loginPwd]
        };

        $.ajax({
          type: "POST",
          url: this.options.address,
          dataType: "json",
          contentType: "application/json",
          data: JSON.stringify(data),
          timeout: PTBackgroundService.options.connectClientTimeout
        })
          .done((resultData, textStatus) => {
            this.isInitialized = true;
            if (callback) {
              callback(resultData);
            }
            resolve(this.token);
          })
          .fail((jqXHR, textStatus) => {
            let result = {
              status: textStatus || "error",
              code: jqXHR.status,
              msg: textStatus === "timeout" ? i18n.t("downloadClient.timeout") : i18n.t("downloadClient.unknownError") //"连接超时" : "未知错误"
            };
            switch (jqXHR.status) {
              case 0:
                result.msg = i18n.t("downloadClient.serverIsUnavailable") //"服务器不可用或网络错误"
                break;

              case 401:
                result.msg = i18n.t("downloadClient.serverConnectionFailed"); //"身份验证失败";
                break;

              case 404:
                result.msg = i18n.t("downloadClient.notFound"); //"指定的地址未找到,服务器返回了 404";
                break;
            }
            reject(result);
          });
      });
    }

    /**
     * 调用指定的RPC
     * @param {*} options
     * @param {*} callback
     * @param {*} tags
     */
    exec(options, callback, tags) {
      var data = {
        id: ++this.requestCount
      };

      $.extend(data, options);

      var settings = {
        type: "POST",
        url: this.options.address,
        data: JSON.stringify(data),
        contentType: "application/json",
        timeout: PTBackgroundService.options.connectClientTimeout,
        success: (resultData, textStatus) => {
          // 未认证
          if (resultData && resultData.error && resultData.error.code == 1) {
            this.getSessionId()
              .then(() => {
                this.exec(options, callback, tags);
              })
              .catch(result => {
                callback && callback(result);
              });
            return;
          }
          if (callback) {
            callback(resultData, tags);
          }
        },
        error: (request, event, page) => {
          console.log(request);
          this.getSessionId()
            .then(() => {
              this.exec(options, callback, tags);
            })
            .catch(result => {
              callback && callback(result);
            });
        }
      };
      $.ajax(settings);
    }

    /**
     * 添加种子链接
     * @param {*} data
     * @param {*} callback
     */
    addTorrentFromUrl(data, callback) {
      let url = data.url;

      // 磁性连接(代码来自原版WEBUI)
      if (url.startsWith('magnet:')) {
        this.addTorrent({
            method: "core.add_torrent_url",
            params: [
              url,
              {
                download_location: data.savePath
              }
            ]
          },
          callback
        );
        return;
      }

      PTBackgroundService.requestMessage({
        action: "getTorrentDataFromURL",
        data: url
      })
        .then(result => {
          var fileReader = new FileReader();

          fileReader.onload = e => {
            var contents = e.target.result;
            var key = "base64,";
            var index = contents.indexOf(key);
            if (index == -1) {
              return;
            }
            var metainfo = contents.substring(index + key.length);

            this.addTorrent({
              method: "core.add_torrent_file",
              params: [
                "",
                metainfo,
                {
                  download_location: data.savePath
                }
              ]
            },
              callback
            );
          };
          fileReader.readAsDataURL(result);
        })
        .catch(result => {
          callback && callback(result);
        });
    }

    addTorrent(options, callback) {
      this.exec(options, resultData => {
        if (callback) {
          var result = resultData;
          if (!resultData.error && resultData.result) {
            result.status = "success";
            result.msg = i18n.t("downloadClient.addURLSuccess", {
              name: this.options.name
            }); //"URL已添加至 Deluge 。";
          }
          callback(result);
        }
        console.log(resultData);
      });
    }
  }

  window.deluge = Deluge;
})(jQuery, window);

================================================
FILE: resource/clients/flood/config.json
================================================
{
  "name": "Flood",
  "type": "flood",
  "ver": "0.0.1",
  "icon": "https://github.com/Flood-UI/flood/raw/master/flood.png",
  "scripts": [
    "init.js"
  ],
  "description": "ruTorrent 的另一款基于Node的Web前端面板,界面美观,加载速度快。",
  "warning": "1.仅支持jesec/Flood,不支持原版Flood;2.如果当前已登录Flood面板,请退出登陆后再做连接性测试;3. 目前无法准确获得Flood添加种子是否成功。",
  "allowCustomPath": true
}


================================================
FILE: resource/clients/flood/init.js
================================================
/**
 * @see https://github.com/jesec/flood/tree/master/server/routes/api
 */

(function ($) {
  // Flood
  class Client {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * address: 服务器地址
     */
    init(options) {
      this.options = options;

      // 重写用户给的地址
      let url = PTServiceFilters.parseURL(this.options.address);
      let address = [url.protocol, "://", url.host];
      if (url.port) {
        address.push(`:${url.port}`)
      }
      address.push(url.path);
      if (url.path.substr(-1) !== "/") {
        address.push("/");
      }
      this.options.address = address.join("");


      console.log("Flood.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("Flood.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, (result) => {
              if (result.status === "success") {
                resolve(result);
              } else {
                reject(result);
              }
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.testClientConnectivity().then(result => {
              resolve(true);
            }).catch((code, msg) => {
              reject({
                status: "error",
                code,
                msg
              });
            });
            break;
        }
      });
    }

    authenticate(callback) {
      // run the login first
      let options = this.options;
      $.ajax({
        type: "POST",
        url: options.address + "api/auth/authenticate",
        contentType: 'application/json',
        data: JSON.stringify({
          username: options.loginName,
          password: options.loginPwd,
        }),
        success: function (resultData, textStatus) {
          console.log(resultData);
          if (resultData.success) {
            console.log(resultData.token);
            if (callback && typeof callback === 'function') {
              callback(resultData);
            }
          }
        }
      });
    }

    /**
     * 测试可连接性
     * 接口返回 { isConnected: true } 时说明可连接
     *
     * @see https://github.com/Flood-UI/flood/blob/master/server/routes/client.js#L16-L36
     *
     * @param callback
     */
    testClientConnectivity(callback) {
      let that = this;
      return new Promise((resolve, reject) => {
        this.authenticate(function (authData) {
          $.ajax({
            type: 'GET',
            url: that.options.address + 'api/client/connection-test',  // ping_addr
            timeout: PTBackgroundService.options.connectClientTimeout
          }).done((resultData, textStatus, request) => {
            if (resultData.isConnected) {
              that.isInitialized = true;
              if (callback) {
                callback(resultData);
              }
              resolve();
            }
          }).fail((jqXHR, textStatus, errorThrown) => {
            reject(jqXHR.status, textStatus)
          })
        });
      })
    }

    /**
     * 添加种子链接
     *
     * @see https://github.com/Flood-UI/flood/blob/master/server/routes/client.js#L38-L44
     *
     * @param {*} data
     * @param {*} callback
     */
    addTorrentFromUrl(data, callback) {
      let url = data.url;

      let addTorrentData = {
        destination: data.savePath || '',
        /** isBasePath
         * @see https://github.com/Flood-UI/flood/blob/master/server/models/ClientRequest.js#L143-L149
         * @see https://rtorrent-docs.readthedocs.io/en/latest/cmd-ref.html
         */
        isBasePath: false,
        start: !data.autoStart,
        tag: [],
      }

      // 处理magent链接
      if (url.startsWith('magnet:')) {
        addTorrentData.urls = [url];

        this.addTorrentUrl(addTorrentData, callback);
        return;
      }

      // 种子文件
      PTBackgroundService.requestMessage({
        action: "getTorrentDataFromURL",
        data: url
      })
        .then((result) => {
          var fileReader = new FileReader();

          fileReader.onload = e => {
            var contents = e.target.result;
            var key = "base64,";
            var index = contents.indexOf(key);
            if (index == -1) {
              return;
            }
            var metainfo = contents.substring(index + key.length);
            addTorrentData.files = [metainfo];
            this.addTorrentFile(addTorrentData, callback);
          }
        })
        .catch((result) => {
          callback && callback(result);
        });
    }

    addTorrentUrl(data, callback) {
      this.addTorrent('api/torrents/add-urls', data, callback);
    }

    addTorrentFile(data, callback) {
      this.addTorrent('api/torrents/add-files', data, callback);
    }

    /**
     *
     * @param suffix
     * @param data
     * @param callback
     */
    addTorrent(suffix, data, callback) {
      let options = this.options;
      this.authenticate(function () {
        $.ajax({
          type: "POST",
          url: options.address + suffix,
          timeout: PTBackgroundService.options.connectClientTimeout,
          data: data,
          contentType: false,
          processData: false,
          success: (resultData, textStatus) => {
            if (callback) {
              var result = Object.assign({
                status: "success",
                msg: i18n.t("downloadClient.addURLSuccess", {
                  name: options.name
                })
              }, resultData);
              callback(result);
            }
          },
        })
      })

    }
  }

  // 添加到 window 对象,用于客户页面调用
  window.flood = Client;

})(jQuery, window);


================================================
FILE: resource/clients/qbittorrent/config.json
================================================
{
  "name": "qBittorrent",
  "type": "qbittorrent",
  "ver": "0.0.1",
  "icon": "https://www.qbittorrent.org/favicon.ico",
  "scripts": [
    "init.js"
  ],
  "description": "当前支持 qBittorrent v4.1+,由于浏览器限制,需要禁用 qBittorrent 的『启用跨站请求伪造(CSRF)保护』功能才能正常使用",
  "warning": "注意:由于 qBittorrent 验证机制限制,第一次测试连接成功后,后续测试无论密码正确与否都会提示成功。",
  "allowCustomPath": true,
  "pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/ 或 D:\\download\\music\\"
}

================================================
FILE: resource/clients/qbittorrent/init.js
================================================
/**
 * @see https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation
 */
(function($) {
  //qBittorrent
  class Client {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * url: 服务器地址
     */
    init(options) {
      this.options = options;
      this.headers = {};
      this.sessionId = "";

      if (this.options.address.substr(-1) == "/") {
        this.options.address = this.options.address.substr(
          0,
          this.options.address.length - 1
        );
      }

      console.log("qBittorrent.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("qBittorrent.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, result => {
              if (result.status === "success") {
                resolve(result);
              } else {
                reject(result);
              }
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.getSessionId()
              .then(result => {
                resolve(true);
              })
              .catch((code, msg) => {
                reject({
                  status: "error",
                  code,
                  msg
                });
              });
            break;
        }
      });
    }

    /**
     * 获取Session
     * @param {*} callback
     */
    getSessionId(callback) {
      return new Promise((resolve, reject) => {
        var data = {
          username: this.options.loginName,
          password: this.options.loginPwd
        };

        // qb 需要禁用『启用跨站请求伪造保护』
        var settings = {
          type: "POST",
          url: this.options.address + "/api/v2/auth/login",
          data: data,
          timeout: PTBackgroundService.options.connectClientTimeout
        };
        $.ajax(settings)
          .done((resultData, textStatus, request) => {
            this.isInitialized = true;
            if (callback) {
              callback(resultData);
            }
            resolve();
            console.log(this.sessionId);
          })
          .fail((jqXHR, textStatus, errorThrown) => {
            reject(jqXHR.status, textStatus);
          });
      });
    }

    /**
     * 调用指定的RPC
     * @param {*} options
     * @param {*} callback
     * @param {*} tags
     */
    exec(options, callback, tags) {
      var settings = {
        type: "POST",
        processData: false,
        contentType: false,
        method: "POST",
        url: this.options.address + options.method,
        data: options.params,
        timeout: PTBackgroundService.options.connectClientTimeout,
        success: (resultData, textStatus) => {
          if (callback) {
            callback(resultData, tags);
          }
        },
        error: (jqXHR, textStatus, errorThrown) => {
          switch (jqXHR.status) {
            // Unsupported Media Type
            case 415:
              callback({
                status: "error",
                code: jqXHR.status,
                msg: i18n.t("downloadClient.unsupportedMediaType") //"种子文件有误"
              });
              return;

            default:
              break;
          }
          console.log(jqXHR);
          this.getSessionId()
            .then(() => {
              this.exec(options, callback, tags);
            })
            .catch((code, msg) => {
              callback({
                status: "error",
                code,
                msg:
                  msg || code === 0
                    ? i18n.t("downloadClient.serverIsUnavailable")
                    : i18n.t("downloadClient.unknownError") //"服务器不可用或网络错误" : "未知错误"
              });
            });
        }
      };
      $.ajax(settings);
    }

    /**
     * 添加种子链接
     * @param {*} data
     * @param {*} callback
     */
    addTorrentFromUrl(data, callback) {
      let formData = new FormData();

      if (data.savePath) {
        formData.append("savepath", data.savePath);
        // 禁用自动管理种子
        formData.append("autoTMM", false);
      }

      if (data.autoStart != undefined) {
        formData.append("paused", !data.autoStart);
      }

      if (data.imdbId != undefined) {
        formData.append("tags", data.imdbId);
      }

      if (data.upLoadLimit && data.upLoadLimit > 0) {
        formData.append("upLimit", data.upLoadLimit * 1024);
      }

      let url = data.url;

      // 磁性连接
      if (url.startsWith('magnet:')) {
        formData.append('urls', url);
        this.addTorrent(formData, callback);
      } else {
        PTBackgroundService.requestMessage({
          action: "getTorrentDataFromURL",
          data: url
        })
          .then(result => {
            formData.append("torrents", result, "file.torrent");
            this.addTorrent(formData, callback);
          })
          .catch(result => {
            callback && callback(result);
          });
      }
    }

    addTorrent(params, callback) {
      this.exec(
        {
          method: "/api/v2/torrents/add",
          params: params
        },
        resultData => {
          if (callback) {
            var result = Object.assign(
              {
                status: "",
                msg: ""
              },
              resultData
            );
            if (
              (!resultData.error && resultData.result) ||
              resultData == "Ok."
            ) {
              result.status = "success";
              result.msg = i18n.t("downloadClient.addURLSuccess", {
                name: this.options.name
              }); //"URL已添加至 qBittorrent 。";
            }
            callback(result);
          }
          console.log(resultData);
        }
      );
    }
  }

  window.qbittorrent = Client;
})(jQuery, window);


================================================
FILE: resource/clients/ruTorrent/config.json
================================================
{
  "name": "ruTorrent",
  "type": "ruTorrent",
  "ver": "0.0.1",
  "icon": "https://raw.githubusercontent.com/Novik/ruTorrent/master/images/favicon.ico",
  "scripts": [
    "init.js"
  ],
  "description": "ruTorrent",
  "warning": "",
  "allowCustomPath": true,
  "pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/ 或 D:\\download\\music\\"
}

================================================
FILE: resource/clients/ruTorrent/init.js
================================================
/**
 * @see https://github.com/Novik/ruTorrent/blob/master/php/addtorrent.php
 * @see https://github.com/Rhilip/PT-Plugin/blob/master/src/script/client.js#L477_L543
 *
 */

(function($) {
  // ruTorrent
  class Client {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * address: 服务器地址
     */
    init(options) {
      this.options = options;

      // 重写用户给的地址
      let url = PTServiceFilters.parseURL(this.options.address);
      let address = [url.protocol, "://", url.host];
      if (url.port) {
        address.push(`:${url.port}`);
      }
      address.push(url.path);
      if (url.path.substr(-1) !== "/") {
        address.push("/");
      }
      this.options.address = address.join("");

      console.log("ruTorrent.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("ruTorrent.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, result => {
              if (result.result === "Success") {
                resolve(result);
              } else {
                reject(result);
              }
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.testClientConnectivity()
              .then(result => {
                resolve(true);
              })
              .catch((code, msg) => {
                reject({
                  status: "error",
                  code,
                  msg
                });
              });
            break;
        }
      });
    }

    /**
     * 测试可连接性
     * 鉴于ruTorrent没有相关方法,则考虑请求 `/php/getsettings.php` 页面,如果返回json格式的信息
     * 则说明可连接
     *
     * @see https://github.com/Novik/ruTorrent/blob/master/php/getsettings.php
     *
     * @param callback
     */
    testClientConnectivity(callback) {
      return new Promise((resolve, reject) => {
        $.ajax({
          type: "GET",
          url: this.options.address + "php/getsettings.php", // ping_addr
          username: this.options.loginName,
          password: this.options.loginPwd,
          timeout: PTBackgroundService.options.connectClientTimeout
        })
          .done((resultData, textStatus, request) => {
            this.isInitialized = true;
            if (callback) {
              callback(resultData);
            }
            resolve();
          })
          .fail((jqXHR, textStatus, errorThrown) => {
            reject(jqXHR.status, textStatus);
          });
      });
    }

    /**
     * 添加种子链接
     * @param {*} data
     * @param {*} callback
     */
    addTorrentFromUrl(data, callback) {
      let url = data.url;

      // 磁性连接
      if (url.startsWith("magnet:")) {
        this.addTorrent(
          {
            dir_edit: data.savePath,
            paused: !data.autoStart,
            url: url,
            json: 1 // 输出json格式
          },
          callback
        );
        return;
      }

      // 种子文件
      PTBackgroundService.requestMessage({
        action: "getTorrentDataFromURL",
        data: url
      })
        .then(result => {
          let formData = new FormData();
          formData.append("json", 1); // 输出json格式
          // 如果有传值时,则设置路径参数
          if (data.savePath) {
            formData.append("dir_edit", data.savePath);
          }

          formData.append("paused", !data.autoStart);
          formData.append("torrent_file", result, "file.torrent");

          this.addTorrent(formData, callback);
        })
        .catch(result => {
          callback && callback(result);
        });
    }

    /**
     * POST完成后会被302到类似  /php/addtorrent.php?result[]=Success&name[]=file.torrent&json=1
     * 得到类似 { "result" : "Success" } 的结果
     * 其中result取值为 enum(
     *    'Success',   // 添加成功
     *    'Failed',    // 添加失败(magnet,不存在种子文件,种子上传失败)
     *    'FailedURL', // 添加链接失败(ruT获取不到对应种子)
     *    'FailedFile' // 添加文件失败(rT返回错误)
     * )
     *
     *
     * @param data
     * @param callback
     */
    addTorrent(data, callback) {
      $.ajax({
        type: "POST",
        url: this.options.address + "php/addtorrent.php",
        username: this.options.loginName,
        password: this.options.loginPwd,
        timeout: PTBackgroundService.options.connectClientTimeout,
        data: data,
        contentType: false,
        processData: false,
        dataType: 'json',
        success: (resultData, textStatus) => {
          if (callback) {
            callback(resultData);
          }
        }
      });
    }
  }

  // 添加到 window 对象,用于客户页面调用
  window.ruTorrent = Client;
})(jQuery, window);


================================================
FILE: resource/clients/synologyDownloadStation/config.json
================================================
{
  "name": "Synology Download Station",
  "type": "synologyDownloadStation",
  "ver": "0.0.1",
  "icon": "https://www.synology.com/img/icon/favicon.png",
  "scripts": [
    "init.js"
  ],
  "allowCustomPath": true,
  "pathDescription": "因 Synology Download Station API 接口限制,保存目录依赖于“暂存位置”,并且只允许使用相对路径;<br/>如暂存位置为 /volume1/,期望存储目的地位置为 /volume1/music/,那么请在“目录列表”中填写:<span style='color:red'>music</span>"
}

================================================
FILE: resource/clients/synologyDownloadStation/init.js
================================================
/**
 * @see https://global.download.synology.com/download/Document/DeveloperGuide/Synology_Download_Station_Web_API.pdf
 * @backport https://github.com/pt-plugins/PT-Plugin-Plus/blob/48c2d42a1d05c129c0abbbecf653b1b7d88a8a8e/src/resource/btClients/src/clients/synologyDownloadStation.ts
 */
(function ($, window) {
  class Client {

    init(options) {
      this.options = options;
      this.sessionId = "";
      this.synoToken = "";
      if (this.options.address.substr(-1) == "/") {
        this.options.address = this.options.address.substr(0, this.options.address.length - 1);
      }
    }

    /**
     * 获取 SID
     */
    // FIXME 这个方法已经不止获取SID了,CSRFToken也是在此获得,该考虑换个名字了
    getSessionId() {
      return new Promise((resolve, reject) => {
        let url = `${this.options.address}/webapi/auth.cgi?api=SYNO.API.Auth&version=3&method=login&account=${encodeURIComponent(this.options.loginName)}&passwd=${encodeURIComponent(this.options.loginPwd)}&session=DownloadStation&format=sid&enable_syno_token=yes`;
        $.ajax({
          url,
          timeout: PTBackgroundService.options.connectClientTimeout,
          dataType: "json"
        }).done((result) => {
          if (result && result.success) {
            this.sessionId = result.data.sid;
            this.synoToken = result.data.synotoken
            resolve(this.sessionId)
          } else {
            reject({
              status: "error",
              code: result.error.code,
              msg: i18n.t("downloadClient.permissionDenied") //"身份验证失败"
            })
          }
          /**
            400 No such account or incorrect password
            401 Account disabled
            402 Permission denied
            403 2-step verification code required
            404 Failed to authenticate 2-step verification code
           */

        }).fail(() => {
          reject()
        })
      })
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("synologyDownloadStation.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, (result) => {
              if (result && result.success) {
                resolve(result);
              } else {
                reject(result)
              }
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.getSessionId().then(result => {
              resolve(result != "");
            }).catch(result => {
              reject(result);
            })
            break;
        }
      });
    }

    /**
     * 添加种子链接
     * @param {*} options
     * @param {*} callback
     */
    addTorrentFromUrl(options, callback) {
      if (!this.sessionId) {
        this.getSessionId().then((result) => {
          if (result) {
            this.addTorrentFromUrl(options, callback)
          } else {
            callback({
              status: "error",
              msg: i18n.t("downloadClient.serverConnectionFailed") //"服务器连接失败"
            })
          }
        }).catch((result) => {
          callback(result)
        })
        return;
      }

      let postData = {
        api: 'SYNO.DownloadStation2.Task',
        method: 'create',
        version: 2,
        create_list: false,
        _sid: this.sessionId  // fxxk, _sid 参数不能放在第一位,不然会直接 101 报错
      }

      let headers = {
        'X-SYNO-TOKEN': this.synoToken
      }

      // fxxk, 没有 destination 参数也会直接报错
      let savePath = (options.savePath || "") + "";
      if (savePath.substr(-1) === "/") {  // 去除路径最后的 / ,以确保可以正常添加目录信息
        savePath = savePath.substr(0, savePath.length - 1);
      }
      postData.destination = `"${savePath || ''}"`;

      if (options.url.startsWith('magnet:')) {
        postData.type = '"url"';
        postData.url = [options.url];

        this.addTorrent(postData, options, callback);
      } else {
        postData.type = '"file"';
        postData.file = ['torrent'];

        let formData = new FormData();
        Object.keys(postData).forEach((k) => {
          let v = postData[k];
          if (v !== undefined) {
            if (Array.isArray(v)) {
              v = JSON.stringify(v);
            }
            formData.append(k, v);
          }
        });


        PTBackgroundService.requestMessage({
          action: "getTorrentDataFromURL",
          data: {
            url: options.url,
            parseTorrent: true
          }
        })
          .then((result) => {
            formData.append("torrent", result.content, `${result.torrent.name}.torrent`)

            this.addTorrent(formData, headers, options, callback);
          })
          .catch((result) => {
            callback && callback(result);
          });

      }
    }

    addTorrent(formData, headers, options, callback) {
      $.ajax({
        url: `${this.options.address}/webapi/entry.cgi`,
        headers,
        timeout: PTBackgroundService.options.connectClientTimeout,
        type: "POST",
        processData: false,
        contentType: false,
        data: formData,
        dataType: "json"
      }).done((result) => {
        console.log(result)
        if (result.error) {
          let errorMap = {
            400: i18n.t("downloadClient.fileUploadFailed"), // "文件上传失败",
            401: i18n.t("downloadClient.maxNumberOfTasksReached"), //"达到的最大任务数",
            402: i18n.t("downloadClient.destinationDenied", {
              path: options.savePath
            }), //`指定的目录[${options.savePath}]不可用或无权限`,
            403: i18n.t("downloadClient.destinationDoesNotExist", {
              path: options.savePath
            }) //`指定的目录[${options.savePath}]不存在`
          };
          /**
           * 400 File upload failed
              401 Max number of tasks reached
              402 Destination denied
              403 Destination does not exist
              404 Invalid task id
              405 Invalid task action
              406 No default destination
              407 Set destination failed
              408 File does not exist
           */
          if (result.error.code) {
            result.msg = errorMap[result.error.code];
          }
        }

        callback(result)
      }).fail(() => {
        callback({
          status: "error",
          msg: i18n.t("downloadClient.serverConnectionFailed") //"服务器连接失败"
        })
      })
    }
  }
  // 添加到 window 对象,用于客户页面调用
  window.synologyDownloadStation = Client;
})(jQuery, window)


================================================
FILE: resource/clients/transmission/config.json
================================================
{
  "name": "Transmission",
  "type": "transmission",
  "ver": "0.0.1",
  "icon": "https://raw.githubusercontent.com/transmission/transmission/master/web/images/favicon.png",
  "scripts": [
    "init.js"
  ],
  "allowCustomPath": true,
  "pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/",
  "description": "默认情况下,系统会请求 http://ip:port/transmission/rpc 这个路径,如果无法连接,请确认 `settings.json` 文件的 `rpc-url` 值;详情可参考:https://github.com/pt-plugins/PT-Plugin-Plus/issues/32"
}

================================================
FILE: resource/clients/transmission/init.js
================================================
/**
 * @see https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt
 */
(function ($, window) {
  const XHEADER = "X-Transmission-Session-Id";
  class Transmission {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * url: 服务器地址
     */
    init(options) {
      this.options = options;
      this.headers = [];
      if (options.loginName && options.loginPwd) {
        this.headers["Authorization"] = "Basic " + (new Base64()).encode(options.loginName + ":" + options.loginPwd);
      }

      if (this.options.address.indexOf("rpc") == -1) {
        let url = PTServiceFilters.parseURL(this.options.address);

        let address = [
          url.protocol,
          "://",
          url.host
        ];
        if (url.port) {
          address.push(`:${url.port}`)
        }

        address.push(url.path);
        if (url.path.substr(-1) != "/") {
          address.push("/");
        }

        address.push("transmission/rpc");

        this.options.address = address.join("");
      }
      console.log("transmission.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("transmission.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          // 从指定的URL添加种子
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data.url, data.savePath, data.autoStart, (result) => {
              resolve(result);
            }, data.upLoadLimit);
            break;

            // 获取可用空间
          case "getFreeSpace":
            this.getFreeSpace(data.path, (result) => {
              resolve(result);
            });

            break;

            // 测试是否可连接
          case "testClientConnectivity":
            this.sessionStats().then(result => {
              resolve(result.result == "success");
            }).catch(result => {
              reject(result);
            })
            break;

        }
      });
    }

    /**
     * 调用指定的RPC
     * @param {*} options
     * @param {*} callback
     * @param {*} tags
     */
    exec(options, callback, tags) {
      return new Promise((resolve, reject) => {
        var data = {
          method: "",
          arguments: {},
          tag: ""
        };
        let result = {};

        $.extend(data, options);

        this.sendRequest({
          type: "POST",
          url: this.options.address,
          dataType: 'json',
          data: JSON.stringify(data),
          timeout: PTBackgroundService.options.connectClientTimeout,
          headers: this.headers
        }, (resultData) => {
          if (callback) {
            callback(resultData, tags);
          }
          resolve(resultData);
        }, (request, event, page) => {
          switch (request.status) {
            case 0:
              result = {
                status: "error",
                code: request.status,
                msg: i18n.t("downloadClient.serverIsUnavailable") //"服务器不可用或网络错误"
              };
              reject && reject(result)
              break;

            case 401:
              result = {
                status: "error",
                code: request.status,
                msg: i18n.t("downloadClient.permissionDenied") //"身份验证失败"
              };
              reject && reject(result)
              break;

            default:
              result = {
                status: "error",
                code: request.status,
                msg: event || i18n.t("downloadClient.unknownError") //"未知错误"
              };
              reject && reject(result)
              break;

          }
        });
      });
    }

    /**
     * 发送请求
     * @param {*} options
     * @param {*} success
     * @param {*} error
     */
    sendRequest(options, success, error) {
      $.ajax(options).done((resultData, textStatus) => {
        success && success(resultData, textStatus);
      }).fail((request, event, page) => {
        switch (request.status) {
          case 409:
            this.sessionId = request.getResponseHeader(XHEADER);
            this.headers[XHEADER] = this.sessionId;
            options.headers = this.headers;
            this.sendRequest(options, success, error);
            break;

          default:
            error && error(request, event, page);
            break;
        }

      });
    }

    sessionStats() {
      return this.exec({
        method: "session-stats"
      });
    }

    /**
     * 添加种子
     * @param string url 需要添加的地址
     * @param string savePath 保存目录,如果不指定则以服务器配置为准
     * @param bool autoStart 是否自动开始
     * @param function callback 回调
     */
    addTorrentFromUrl(url, savePath, autoStart, callback, uploadLimit = 0) {
      var options = {
        method: "torrent-add",
        arguments: {
          filename: url,
          paused: (!autoStart)
        }
      };

      if (savePath) {
        options.arguments["download-dir"] = savePath;
      }

      if (uploadLimit && uploadLimit > 0) {
        options.arguments["uploadLimit"] = uploadLimit;
      }

      // 磁性连接
      if (url.startsWith('magnet:')) {
        options.arguments["filename"] = url;
        this.addTorrent(options, callback)
      } else {
        PTBackgroundService.requestMessage({
            action: "getTorrentDataFromURL",
            data: url
          })
          .then((result) => {
            var fileReader = new FileReader();

            fileReader.onload = (e) => {
              var contents = e.target.result;
              var key = "base64,";
              var index = contents.indexOf(key);
              if (index == -1) {
                return;
              }
              var metainfo = contents.substring(index + key.length);

              delete options.arguments["filename"];
              options.arguments["metainfo"] = metainfo;

              this.addTorrent(options, callback);
            }
            fileReader.readAsDataURL(result);
          })
          .catch((result) => {
            callback && callback(result);
          });
      }
    }

    /**
     * 添加种子
     * @param {*} options
     * @param {*} callback
     */
    addTorrent(options, callback) {
      this.exec(options).then((data) => {
        switch (data.result) {
          // 添加成功
          case "success":
            if (callback) {
              if (data.arguments["torrent-added"]) {
                callback(data.arguments["torrent-added"]);
              }
              // 重复的种子
              else if (data.arguments["torrent-duplicate"]) {
                callback({
                  status: "duplicate",
                  torrent: data.arguments["torrent-duplicate"]
                });
              }
            }
            break;

            // 重复的种子
          case "duplicate torrent":
          default:
            if (callback) {
              callback(data.result || data);
            }
            break;

        }
      }).catch((result) => {
        callback && callback(result);
      });
    }


    /**
     * 獲取指定目錄的大小
     * @param string path 需要获取的目录地址
     * @param function callback 回调
     */
    getFreeSpace(path, callback) {
      this.exec({
        method: "free-space",
        arguments: {
          "path": path
        }
      }).then((result) => {
        callback && callback(result);
      }).catch((result) => {
        callback && callback(result);
      });
    }
  }

  // 添加到 window 对象,用于客户页面调用
  window.transmission = Transmission;
})(jQuery, window)


================================================
FILE: resource/clients/utorrent/config.json
================================================
{
  "name": "µTorrent",
  "type": "utorrent",
  "ver": "0.0.1",
  "icon": "https://www.utorrent.com/faviconUT.ico",
  "scripts": [
    "init.js"
  ],
  "description": "由于 µTorrent Web API 接口不统一,当前仅支持 µTorrent Windows 版本,Mac 版本测试不可用,其他系统未知。使用前请确认 WebUI 已安装并开启",
  "allowCustomPath": true,
  "pathDescription": "注:目录功能仅支持 µTorrent 3.x.x 及以上版本;<br/><br/>1. 在 µTorrent 的 设置 -> 高级 -> 网页界面 添加一个下载目录,如:D:\\download\\ <br/>2. 在助手里添加目录列表(仅支持相对路径),如:music\\ <br/>3. 最终数据的保存目录为:D:\\download\\music\\"
}

================================================
FILE: resource/clients/utorrent/init.js
================================================
/**
 * @see https://github.com/bittorrent/webui/blob/master/webui.js
 */
(function ($, window) {
  class uTorrent {
    /**
     * 初始化实例
     * @param {*} options
     * loginName: 登录名
     * loginPwd: 登录密码
     * url: 服务器地址
     */
    init(options) {
      this.options = options;
      this.headers = [];
      this.token = "";
      if (options.loginName && options.loginPwd) {
        this.headers["Authorization"] =
          "Basic " +
          new Base64().encode(options.loginName + ":" + options.loginPwd);
      }

      if (this.options.address.indexOf("gui") == -1) {
        let url = PTServiceFilters.parseURL(this.options.address);
        let address = [
          url.protocol,
          "://",
          url.host
        ];
        if (url.port) {
          address.push(`:${url.port}`)
        }

        address.push(url.path);
        if (url.path.substr(-1) != "/") {
          address.push("/");
        }

        address.push("gui/");
        this.options.address = address.join("");
      }
      console.log("uTorrent.init", this.options.address);
    }

    /**
     * 执行指定的操作
     * @param {*} action 需要执行的执令
     * @param {*} data 附加数据
     * @return Promise
     */
    call(action, data) {
      console.log("uTorrent.call", action, data);
      return new Promise((resolve, reject) => {
        switch (action) {
          case "addTorrentFromURL":
            this.addTorrentFromUrl(data, result => {
              resolve(result);
            });
            break;

          // 测试是否可连接
          case "testClientConnectivity":
            this.getSessionId()
              .then(result => {
                resolve(result != "");
              })
              .catch(result => {
                reject(result);
              });
            break;
        }
      });
    }

    /**
     * 获取Session
     * @param {*} callback
     */
    getSessionId(callback) {
      return new Promise((resolve, reject) => {
        $.ajax({
          type: "GET",
          url: this.options.address + "token.html?t=",
          headers: this.headers,
          timeout: PTBackgroundService.options.connectClientTimeout
        })
          .done(resultData => {
            console.log(resultData);
            this.token = $(resultData).html();
            this.isInitialized = true;
            if (callback) {
              callback(this.token);
            }
            resolve(this.token);
          })
          .fail((jqXHR, textStatus) => {
            let result = {
              status: textStatus || "error",
              code: jqXHR.status,
              msg: textStatus === "timeout" ? i18n.t("downloadClient.timeout") : i18n.t("downloadClient.unknownError") //"连接超时" : "未知错误"
            };
            switch (jqXHR.status) {
              case 0:
                result.msg = i18n.t("downloadClient.serverIsUnavailable") //"服务器不可用或网络错误"
                break;

              case 401:
                result.msg = i18n.t("downloadClient.permissionDenied");//"身份验证失败";
                break;

              case 404:
                result.msg = i18n.t("downloadClient.notFound");// "指定的地址未找到,服务器返回了 404";
                break;
            }
            reject(result);
          });
      });
    }

    /**
     * 调用指定的RPC
     * @param {*} options
     * @param {*} callback
     * @param {*} tags
     */
    exec(options, callback, tags) {
      if (!this.token) {
        this.getSessionId().then(() => {
          this.exec(options, callback, tags);
        }).catch((result) => {
          callback && callback(result);
        });
        return;
      }
      var data = {};

      var _settings = $.extend({
        method: "GET",
        processData: undefined,
        contentType: undefined,
        queryString: ""
      }, options.settings);

      if (options.settings) {
        delete options.settings;
      }
      if (options.formData) {
        data = options.formData;
      } else {
        $.extend(data, options);
      }

      var settings = {
        type: _settings.method,
        url: this.options.address + "?token=" + this.token + _settings.queryString,
        dataType: "json",
        processData: _settings.processData,
        contentType: _settings.contentType,
        data: data,
        timeout: PTBackgroundService.options.connectClientTimeout,
        success: (resultData, textStatus) => {
          if (callback) {
            callback(resultData, tags);
          }
        },
        error: (request, event, page) => {
          console.log(request);
          this.getSessionId().then(() => {
            this.exec(options, callback, tags);
          }).catch((result) => {
            callback && callback(result);
          });
        },
        headers: this.headers
      };
      $.ajax(settings);
    }

    /**
     * 添加种子链接
     * @param {*} data
     * @param {*} callback
     */
    addTorrentFromUrl(data, callback) {
      let url = data.url;

      // 磁性连接
      if (url.startsWith('magnet:')) {
        this.addTorrent({
          action: "add-url",
          s: url,
          download_dir: 0,
          path: data.savePath ? data.savePath : ""
        }, callback);
        return;
      }

      PTBackgroundService.requestMessage({
        action: "getTorrentDataFromURL",
        data: url
      })
        .then((result) => {
          let formData = new FormData();
          formData.append("torrent_file", result, "file.torrent")

          this.addTorrent({
            settings: {
              method: "POST",
              processData: false,
              contentType: false,
              queryString: `&action=add-file&download_dir=0&path=` + (data.savePath ? data.savePath : "")
            },
            formData
          }, callback);
        })
        .catch((result) => {
          callback && callback(result);
        });

    }

    addTorrent(options, callback) {
      this.exec(options,
        resultData => {
          if (callback) {
            var result = resultData;
            if (resultData.build) {
              result.status = "success";
              result.msg = result.msg = i18n.t("downloadClient.addURLSuccess", {
                name: this.options.name
              });//"URL已添加至 µTorrent 。";
            }
            callback(result);
          }
          console.log(resultData);
        }
      );
    }
  }

  // 添加到 window 对象,用于客户页面调用
  window.utorrent = uTorrent;
})(jQuery, window);

================================================
FILE: resource/i18n/README.md
================================================
# 关于多语言
> 多语言环境正在构建中,现阶段正在努力将中文翻译为英文,需要各位英文达人参与翻译、复核,待英文文案可用后,将作为其他语言的源文件以供翻译。

## 如何参与英文翻译?
- 您可以通过以下方式来参与
  - 通过在线翻译的方式来参与,欢迎加入:https://www.transifex.com/ronggang/pt-plugin-plus-dev
  - 直接以 `git` 方式更新本目录下的 `en.json` 文件;

## 多语言文案更新流程
- 中文词汇增加 -> 提交至 `transifex` 的 `dev` 项目 -> 翻译为英文 -> 复核 -> 转为正式文案 -> 翻译为其他语言

## 如何测试已翻译的新语言
- 更新助手至 `v1.0.9` 之后的版本;
- 进入助手配置页面;
- 点击右下角的 `切换语言` -> `临时添加新语言` ;
- 选择已经翻译好的语言文件,如:`zh-TW.json` ;
- 如果格式正确,应该就可以看到新的语言内容了;
- 如果是通过 `transifex` 在线翻译的,可以从 `transifex` 下载已翻译好的文件进行测试;
- 使用该方式添加的语言文件仅本次生效,浏览器重启后恢复到默认状态;

================================================
FILE: resource/i18n/en.json
================================================
{
  "name": "English (Beta)",
  "code": "en",
  "authors": [
    "ronggang",
    "ylxb2016",
    "xiongqiwei",
    "jackson008",
    "MewX"
  ],
  "words": {
    "app": {
      "initError": "The configuration information failed to be loaded. The system definition information was not obtained. Please try to refresh the current page.",
      "initializing": "The data is being prepared, please wait...",
      "author": "ronggang",
      "name": "PT Plugin Plus"
    },
    "common": {
      "debugMode": "Debug mode active",
      "changeLanguage": "Switch language",
      "addLanguage": "Add a new language temporarily",
      "version": "Version",
      "systemLog": "System Log",
      "darkMode": "Invert Color",
      "haveNewReleases": "Update available",
      "add": "Add",
      "edit": "Edit",
      "copy": "Copy",
      "ok": "OK",
      "cancel": "Cancel",
      "remove": "Remove",
      "clear": "Clear",
      "removeConfirm": "Are you sure you want to delete this record?",
      "removeSelectedConfirm": "Are you sure you want to delete these {count} selected records?",
      "removeConfirmTitle": "Delete confirmation",
      "clearConfirm": "Are you sure you want to delete all records?",
      "id": "ID",
      "readyToStart": "Ready to start...",
      "help": "Help",
      "export": "Export",
      "import": "Import",
      "share": "Share",
      "actionConfirm": "Are you sure you want to do this?",
      "importFailed": "Import failed",
      "importSuccess": "Import success",
      "all": "All",
      "setDefault": "Set Default",
      "cancelDefault": "Cancel Default",
      "search": "Search",
      "color": "Color",
      "orderBy": "Order By",
      "orderMode": {
        "asc": "ASC",
        "desc": "DESC"
      },
      "close": "Close",
      "copyed": "Copyed",
      "hot": "HOT",
      "loading": "Loading...",
      "lastUpdate": "last updated on {time}",
      "refresh": "Refresh"
    },
    "topbar": {
      "title": "@:(app.name)",
      "navBarTip": "Click to show\/hide the navigation bar",
      "help": "Wiki",
      "github": "Github",
      "showNewTorrents": "Get the first page of each tracker",
      "showNewTorrentsTip": "Search the first page torrents of each tracker according to the current solution"
    },
    "navigation": {
      "dashboard": {
        "title": "Overview",
        "userData": "My Data",
        "searchResults": "Search Results",
        "history": "Download History",
        "collection": "Collection",
        "searchResultSnapshot": "Search Snapshot",
        "keepUploadTask": "Reseed Task"
      },
      "settings": {
        "title": "Settings",
        "base": "General",
        "sites": "Sites",
        "downloadClients": "Download Server",
        "downloadPaths": "Download Paths",
        "searchSolution": "Search Solution",
        "backup": "Backup & Restore",
        "permissions": "Permissions"
      },
      "thanks": {
        "title": "Thanks",
        "reference": "Project Reference",
        "specialThanksTo": "Special Thanks"
      },
      "support": {
        "title": "Support This Project",
        "bugReport": "Bug Report",
        "donate": "Donate",
        "debugger": "Debugger"
      }
    },
    "permissions": {
      "title": "Thank you for choosing PT Plugin Plus",
      "subtitle": "In order to work properly, please authorize the required permission.",
      "authorize": "Authorization",
      "cancel": "Cancel",
      "cancelled": "Bye!",
      "details": {
        "allSites": "Access to all trackers for searching and get torrents data;",
        "tabs": "Read permission of the activity tab to display the PT Plugin Plus icon;",
        "downloads": "Download permission for batch download of torrents",
        "cookies": "Import/Export site cookies"
      },
      "headers": {
        "title": "Permission description",
        "enabled": "Authorized"
      },
      "request": {
        "default": "Are you sure granting this permission?",
        "cookies": "Are you sure granting cookies permission to read and write?"
      }
    },
    "searchBox": {
      "searchTip": "Input keyword, IMDb numbers, then press <Enter> to search",
      "default": "<Default>",
      "defaultTip": "Search only allowed sites",
      "all": "<All Sites>",
      "noSearchSolution": "No Search Solution, please add a solution",
      "noAllowSearchSites": "The site to search is not configured yet. Please configure it before.",
      "searchThisKey": "Search “{key}”",
      "doubanTip": "The above data comes from the Douban Movie API v2 ; if you do not want to display these results for pre-selection, you can close it in the General Settings",
      "toDouban": "View in Douban"
    },
    "donate": {
      "title": "Thanks for the support"
    },
    "history": {
      "title": "Download history",
      "remove": "Remove",
      "clear": "Clear",
      "removeConfirm": "Confirm to delete this record?",
      "removeConfirmTitle": "Delete confirmed",
      "clearConfirm": "Confirm to delete all download records?",
      "ok": "@:(common.ok)",
      "cancel": "@:(common.cancel)",
      "download": "Download again",
      "fail": "Failure",
      "success": "Success",
      "unknown": "N/A",
      "defaultPath": "Default Path",
      "seedingTorrent": "Sending torrents to download server...",
      "headers": {
        "site": "Source",
        "title": "Title",
        "status": "Status",
        "time": "Download time",
        "action": "Action"
      }
    },
    "home": {
      "title": "My Data",
      "getInfos": "Refresh my data",
      "cancelRequest": "Cancel request",
      "requesting": "Requesting",
      "siteName": "Site name",
      "userName": "User name",
      "userLevel": "User level",
      "levelRequirements": "Level requirements",
      "seedingPoints": "Seeding Points",
      "showHnR": "H&R",
      "selectColumns": "Select Columns",
      "week": "Expressed in weeks",
      "timeline": "Time line",
      "settings": "Settings",
      "statistic": "Statistic",
      "newMessage": "New message",
      "startGetting": "Getting user profile...",
      "gettingForSite": "Getting {siteName} user profile",
      "requestCompleted": "Request completed, time: {second} seconds.",
      "getUserInfoError": "An error occurred",
      "getUserInfoAbort": "Get user profile request has been canceled. ({siteName})",
      "getUserInfoAbortError": "Cancellation failed to get user profile request. ({siteName})",
      "offline":"Offline",
      "headers": {
        "date": "Date",
        "site": "Site",
        "userName": "User name",
        "levelName": "Level",
        "activitiyData": "Activitiy data",
        "ratio": "Ratio",
        "seeding": "Seeding",
        "seedingSize": "Seeding size",
        "bonus": "Bonus",
        "seedingPoints": "Seeding Points",
        "bonusPerHour": "Bonus per hour",
        "joinTime": "Join time",
        "lastUpdateTime": "Update at",
        "status": "Status",
        "comments": "Comments",
        "uploads": "Uploaded",
        "trueDownloaded": "True Downloaded",
        "classPoints": "Class Points",
        "unsatisfieds": "Unsatisfieds",
        "prewarn": "H&R Prewarn"
      },
      "levelRequirement": {
        "levelRequirements": "Level Requirements",
        "date": "Date",
        "site": "Site",
        "userName": "User name",
        "levelName": "Level",
        "activitiyData": "Activitiy data",
        "ratio": "Ratio",
        "seeding": "Seeding",
        "seedingSize": "Seeding size",
        "bonus": "Bonus",
        "seedingPoints": "Seeding Points",
        "seedingTime": "Seeding Time",
        "bonusPerHour": "Bonus per hour",
        "joinTime": "Join time",
        "lastUpdateTime": "Update at",
        "status": "Status",
        "comments": "Comments",
        "uploaded": "Uploaded",
        "downloaded":"Downloaded",
        "uploads": "Uploaded",
        "downloads": "Downloaded",
        "trueDownloaded": "True Downloaded",
        "classPoints": "Class Points",
        "uniqueGroups": "Unique Groups",
        "perfectFLAC": "\"Perfect\" FLAC",
        "alternative": "Alternative"
      },
      "tip": "N/A means no support",
      "nodata": "You have not added a site, please go to [Sites] to add a site."
    },
    "systemLog": {
      "title": "System Log",
      "save": "Save log",
      "headers": {
        "module": "Module",
        "event": "Events",
        "time": "Time",
        "msg": "Description",
        "action": "Action"
      }
    },
    "reference": {
      "title": "This project uses or references the following items",
      "thanks": "The birth of 'PT Plugin Plus' is established on the base of these projects. Thanks all the participants of the project, thanks for your contribution!",
      "headers": {
        "name": "Name",
        "ver": "Version",
        "url": "URL"
      }
    },
    "team": {
      "title": "Alphabetical Order",
      "contributors": "Contributors:",
      "issues": "Suggesters:"
    },
    "timeline": {
      "share": "Generate a share image",
      "siteName": "Tracker name",
      "blurSiteIcon": "Blur site icon",
      "userName": "User name",
      "userLevel": "User level",
      "userId": "User UID",
      "showSites": "Show Sites",
      "close": "Close",
      "shareMessage": "Growth process",
      "time": {
        "year": " year(s) ",
        "month": " month(s) ",
        "day": " day(s) ",
        "hour": " hour(s) ",
        "mins": " minute(s) ",
        "week": " week(s) ",
        "ago": " ago",
        "lessThanAWeek": "Less than a week"
      },
      "total": {
        "uploaded": "Total uploads: ",
        "downloaded": "Total download: ",
        "seedingSize": "Seeding size: ",
        "ratio": "Total ratio: ",
        "years": "PT ages:≈ {year} year(s)"
      },
      "updateat": "Update at: ",
      "user": {
        "uploaded": "Uploaded: ",
        "downloaded": "Downloaded: ",
        "seedingSize": "Seeding size: ",
        "ratio": "Ratio: ",
        "bonus": "Bonus: ",
        "bonusPerHour": "Bonus per hour: "
      },
      "inputDisplayName": "Please enter a name to display:",
      "inputShareMessage": "Please enter a message to display:"
    },
    "searchTorrent": {
      "title": "Search Result",
      "download": "Download",
      "downloadFailed": "Re-download failed",
      "sendToClient": "Send to server",
      "sendToClientTip": "Send torrents to download server",
      "save": "Save",
      "saveTip": "Save torrents",
      "collection": "Collection",
      "searching": "Searching, please wait...",
      "cancelSearch": "Cancel search",
      "showCheckbox": "Multiple selection",
      "noTag": "No tag",
      "allSites": "All Sites",
      "multiDownloadConfirm": "The number of torrents currently downloaded exceeds one, and the browser may prompt multiple times to save. Do you want to continue?",
      "copyToClipboard": "Copy Links",
      "copyToClipboardTip": "Copy download links to clipboard",
      "reSearch": "Re-search",
      "showCategory": "Category",
      "filterSearchResults": "Filter search results",
      "noResultsSites": "Site with 0 result:",
      "failedSites": "Failed site:",
      "reSearchFailedSites": "Re-search failed site",
      "failUrl": "Invalid link",
      "headers": {
        "site": "Site",
        "title": "Title",
        "category": "Category\/Entrance",
        "size": "Size",
        "seeders": "S",
        "leechers": "L",
        "completed": "C",
        "comments": "Comments",
        "time": "Time(≈)",
        "action": "Action"
      },
      "optionsIsMissing": "System parameter is missing",
      "sitesIsMissing": "Please set up the site first",
      "optionsIsMissingErrorMsg": "System parameters are lost, please re-open this page",
      "doubanIdConversionFailed": "Douban ID Conversion Failed",
      "skipSites": "Sites that do not support search at this time:",
      "noAllowSearchSites": "You have not configured a site that allows search. Please go to [Site Settings] to configure.",
      "searchStartMsg": "Ready to start searching, a total of {count} sites",
      "siteIsSearching": "[{siteName}] is searching.",
      "siteIsSearchDone": "{siteName} Search completed with {count} results.",
      "siteSearchAbort": "{host} Search request canceled",
      "siteSearchAbortError": "{host} Search request cancellation failed",
      "siteSearchTimeout": "{host} Connection timed out",
      "siteSearchError": "{host} Network or other error",
      "notLogged": "Not logged",
      "searchFinished": "Search completed, found {count} results, time: {second} seconds.",
      "searchProgress": "{count} results have been received, search is still in progress...",
      "seedingTorrent": "Sending torrent to download server...",
      "userCanceled": "User Canceled",
      "sendTorrentToClient": "Send torrent to the download server",
      "sendTorrentToClientSuccess": "Send torrent to download server successfully",
      "sendTorrentToClientError": "Send torrent to download server failed",
      "downloadSelectedError": "Failed to download torrent file: {name}",
      "copyLinkToClipboardSuccess": "Download link has been copied to the clipboard",
      "copyLinkToClipboardError": "Copying the download link failed!",
      "copySelectedToClipboardSuccess": "{count} download links have been copied to the clipboard",
      "downloadTo": "Download to: {path}",
      "noReSearchSites": "No sites that need to be re-searched",
      "doubanIdConverting": "Trying to convert the douban id, please wait...",
      "invalidDoubanId": "Invalid douban id",
      "torrentStatus": {
        "downloading": "Downloading",
        "sending": "Sending",
        "completed": "Completed",
        "inactive": "Inactive"
      }
    },
    "settings": {
      "backup": {
        "title": "Parameter backup and recovery",
        "subTitle": "Note: Unless encryption is set, the backup file is in plain text and may contain personal information. Please take care of it.",
        "backup": "Backup",
        "restore": "Restore",
        "backupToGoogle": "Backup To Google",
        "restoreFromGoogle": "Restore From Google",
        "restoreConfirm": "Are you sure you want to restore the settings from the backup data? This will overwrite all current settings.",
        "restoreSuccess": "Parameter has been restored",
        "restoreError": "Parameter recovery failed!",
        "loadError": "Configuration information failed to load",
        "backupDone": "Backup completed",
        "backupError": "Backup parameters failed!",
        "errorMessage": {
          "QUOTA_BYTES_PER_ITEM": "The size of the content to be saved exceeds the Google limit (8K)"
        },
        "clearFromGoogle": "Clear",
        "clearFromGoogleTip": "Clear backed up parameters from Google",
        "clearFromGoogleConfirm": "Do you want to clear the backed up parameters from Google?",
        "clearFromGoogleError": "Clear failed!",
        "clearFromGoogleSuccess": "Content cleared",
        "index": {
          "headers": {
            "name": "Service name",
            "type": "Type",
            "lastBackupTime": "Last backup time",
            "action": "Action"
          }
        },
        "server": {
          "add": {
            "title": "Add Backup Server"
          },
          "edit": {
            "title": "Edit Backup Server"
          },
          "editor": {
            "type": "Server Type",
            "name": "Server name",
            "address": "Server address",
            "authCode": "Authorization Code",
            "applyAuthCode": "Apply",
            "loginName": "log-in name",
            "loginPwd": "login password",
            "digest": "Use Digest Authorization"
          },
          "list": {
            "noData": "No backup data yet",
            "backupToServer": "Backup to server",
            "loadBackupList": "Load backup list"
          },
          "getFileListError": "Failed to get the backup file, please confirm whether the network and backup server are available",
          "owss": {
            "addressTip": "The server address contains the port, such as: http:\/\/192.168.1.1:8088\/storage"
          }
        },
        "restoreAll": "Restore All",
        "restoreCollection": "Restore Collection Only",
        "restoreCookies": "Restore Cookies Only",
        "restoreSearchResultSnapshot": "Restore Search Result Snapshot Only",
        "restoreKeepUploadTask": "Restore Reseed Task Only",
        "restoreDownloadHistory": "Restore Download History Only",
        "contentNotExist": {
          "cookies": "Cookies not exist in the backup file",
          "collection": "Collection not exists in the backup file",
          "searchResultSnapshot": "Search Result Snapshot not exists in the backup file",
          "keepUploadTask": "Reseed Task not exists in the backup file",
          "downloadHistory": "Download History not exists in the backup file"
        },
        "backupItem": {
          "base": "General",
          "userDatas": "User Datas",
          "collection": "Collection",
          "cookies": "Cookies",
          "searchResultSnapshot": "Search Result Snapshot",
          "keepUploadTask": "Reseed Task",
          "downloadHistory": "Download History"
        },
        "restoreErrorType": {
          "needSecretKey": "Need Secret Key",
          "errorSecretKey": "Error Secret Key"
        },
        "enterSecretKey": "Please enter a Secret key:",
        "restoreCookiesConfirm": "Are you sure you want to restore cookies? This will overwrite current cookies."
      },
      "base": {
        "title": "General",
        "defaultClient": "Default download server (required)",
        "autoUpdate": "Automatically update official data",
        "save": "Save",
        "allowSelectionTextSearch": "Enable page content selection search",
        "allowDropToSend": "Send a link to the download server when dragging and dropping links to the plugin icon",
        "clearCache": "Clear cache",
        "clearCacheConfirm": "Are you sure you want to clear the cache? After the cleaning is completed, the system configuration information will be re-downloaded from the official website next time.",
        "needConfirmWhenExceedSize": "Confirmation when the total volume of torrent downloaded in batches exceeds the following size",
        "exceedSize": "Size",
        "searchResultRows": "Number of results returned per site at the time of search",
        "saveDownloadHistory": "Enable download history to record torrent information sent with one click at a time",
        "connectClientTimeout": "Global timeout (milliseconds, 1000 milliseconds = 1 second), acting on the connection download server, downloading the torrent file, etc.",
        "noClient": "The download server has not been configured. Please configure the download service before selecting",
        "cacheIsCleared": "The cache has been cleared. If it needs to take effect immediately, please reopen the page.",
        "saved": "Parameter saved",
        "autoRefreshUserData": "Automatically refresh user data when the browser is open (Beta)",
        "autoRefreshUserDataTip1": "Automatically refreshes at",
        "autoRefreshUserDataTip2": "every day (if the browser opens after this time, it will be automatically refreshed when the browser is opened)",
        "autoRefreshUserDataTip3": "Retry",
        "autoRefreshUserDataTip4": "times after failure,",
        "autoRefreshUserDataTip5": "minute apart",
        "searchResultOrderBySitePriority": "When the search results are clicked on the site header, they are sorted by site priority (effective after refreshing the page after saving)",
        "saveSearchKey": "Save historical search keyword records",
        "showMoiveInfoCardOnSearch": "Show movie and rating information when searching by IMDb number",
        "getMovieInformationBeforeSearching": "When entering a search keyword, load relevant information from Douban for pre-selection",
        "maxMovieInformationCount": "Maximum display number of entries (1-20):",
        "searchModeForItem": "When clicking on a pre-selected item:",
        "showToolbarOnContentPage": "Enable site page plugin icons and toolbars (such as one-click downloads, etc.)",
        "lastUpdate": " (last updated on {time})",
        "lastUpdateUnknown": " (Update time is unknown)",
        "lastUpdateFailed": " (Failed to get update time)",
        "autoRefreshUserDataLastUpdate": " (last updated on {time})",
        "beforeSearchingItemSearchMode": {
          "id": "Search by IMDb ID to get more accurate content, but it need more time to get IMDb ID",
          "name": "Fuzzy search by name to get more content"
        },
        "downloadFailedRetry": "Retry after download failed",
        "downloadFailedRetryTip1": "Retry",
        "downloadFailedRetryTip2": "times after failure,",
        "downloadFailedRetryTip3": "second apart. (0 means retry immediately after failure)",
        "tabs": {
          "base": "General",
          "search": "Search",
          "download": "Download",
          "advanced": "Advanced"
        },
        "apiKey": {
          "omdb": "OMDb API Key",
          "douban": "Douban API Key"
        },
        "apiKeyTip": "The OMDb API Key is used to obtain the rating information of the movie;\nThe Douban API Key is used to obtain the basic information of the movie, such as pictures, introductions, etc.",
        "verifyApiKey": "Verify Api Key",
        "batchDownloadInterval": "Per torrent interval (seconds) when downloading in bulk",
        "enableBackgroundDownload": "Enable background download task",
        "position": {
          "label": "Displayed on: ",
          "left": " Left side of page",
          "right": "Rigth side of page"
        },
        "allowBackupCookies": "Backup cookies of the configured site. (Backup to Google is not supported)",
        "encryptBackupData": "Encrypt backup data. (Backup to Google is not supported)",
        "encryptMode": "Encryption: ",
        "encryptSecretKey": "Secret Key:",
        "encryptTip": "Note: The key is only saved in the current browser and will not be backed up. Please save it properly. If the key is lost after encryption, the data will not be recovered.",
        "createSecretKey": "Create",
        "allowSaveSnapshot": "Allow saving search results snapshots"
      },
      "downloadClients": {
        "add": {
          "title": "Add download server",
          "titleStep1": "Select bittorrent client type",
          "titleStep2": "Detailed configuration",
          "validMsg": "Please select a bittorrent client type",
          "helpMsg": "Can't find the bittorrent client type you want? ",
          "nextStep": "Next",
          "prevStep": "Previous",
          "cancel": "Cancel"
        },
        "edit": {
          "title": "Edit download server",
          "ok": "OK",
          "cancel": "Cancel"
        },
        "editor": {
          "name": "Name of server",
          "type": "bittorrent client type",
          "address": "Server address",
          "addressTip": "The server address contains the port, such as: http:\/\/192.168.1.1:5000\/",
          "loginName": "log-in name",
          "loginPwd": "login password",
          "id": "ID",
          "autoStart": "Automatically start downloading when sending a torrent",
          "tagIMDb": "Add IMDb tag when sending a torrent(Beta)",
          "autoCreate": "<Automatically generated after saving>",
          "test": "Test if the server can connect",
          "testSuccess": "Server can be connected",
          "testConnectionError": "Network connection error",
          "testError": "Server connection failed",
          "testUnknownError": "unknown errors",
          "testOtherError": "Other errors, the code returned by the server is: {code}",
          "testAddressError": "Server address error"
        },
        "index": {
          "title": "Download server configuration",
          "subTitle": "You must to add at least one download server before you start using it.",
          "add": "New",
          "remove": "Remove",
          "clear": "Clear",
          "itemDuplicate": "The name already exists",
          "removeConfirm": "Are you sure you want to delete this download server?",
          "removeConfirmTitle": "Delete confirmation",
          "clearConfirm": "Are you sure you want to delete all download servers?",
          "removeSelectedConfirm": "Are you sure you want to delete the selected download server?",
          "ok": "OK",
          "cancel": "Cancel",
          "headers": {
            "name": "Name",
            "type": "Type",
            "address": "Address",
            "action": "Action"
          }
        }
      },
      "downloadPaths": {
        "add": {
          "title": "Add new download Path",
          "path": "Paths list",
          "pathTip": "Press Enter multiple Paths separated first as the default Path",
          "ok": "OK",
          "cancel": "Cancel",
          "selectSite": "Select a site (not selected means all sites are available)"
        },
        "edit": {
          "title": "Edit download Path definition",
          "site": "Site"
        },
        "index": {
          "title": "Download Path settings",
          "selectedClient": "Servers need to be set",
          "add": "New",
          "remove": "Remove",
          "clear": "Clear",
          "itemDuplicate": "The name already exists",
          "removeConfirm": "Are you sure you want to delete this Download Path?",
          "removeConfirmTitle": "Delete confirmation",
          "removeSelectedConfirm": "Are you sure you want to delete the selected Download Path?",
          "ok": "OK",
          "cancel": "Cancel",
          "notSupport": "This server type is not supported at this time",
          "allSite": "<All Sites>",
          "headers": {
            "name": "Site",
            "path": "Download Path",
            "action": "Action"
          }
        },
        "keyDescription": {
          "allowKeys": "The following keywords can be included in the path:",
          "siteName": "Will be replaced with the current site name;",
          "siteHost": "Will be replaced with the current site domain name;",
          "example": "Example: ",
          "dynamic": "An input box will pop up where the user enters the {key} part of the path;",
          "dynamicExample": "Example: \/volume1\/<...> ,input: test,result: \/volume1\/test"
        }
      },
      "searchSolution": {
        "edit": {
          "title": "Search Solution Definition"
        },
        "editor": {
          "name": "Solution Name",
          "range": "Search range",
          "headers": {
            "name": "Site"
          }
        },
        "index": {
          "title": "Search Solution Definition",
          "itemDuplicate": "The name already exists",
          "removeConfirm": "Are you sure you want to delete this search solution?",
          "removeConfirmTitle": "Delete confirmation",
          "removeSelectedConfirm": "Are you sure you want to delete the selected search solution?",
          "help": "How to use?",
          "headers": {
            "name": "Name",
            "range": "Range",
            "action": "Action"
          }
        }
      },
      "sitePlugins": {
        "add": {
          "title": "Add new plugin"
        },
        "edit": {
          "title": "Edit plugin"
        },
        "editor": {
          "defaultClient": "Default download server",
          "name": "Plugin name",
          "pages": "Applicable page",
          "pagesTip": "The page starts with '/' to indicate the root path of the website. After inputting, press Enter to add multiple, which can be a regular expression.",
          "scripts": "Script files",
          "scriptsTip": "\/ means to load the script from the resource directory root, you can add multiple",
          "script": "Javascript",
          "style": "Style",
          "styles": "Style files",
          "stylesTip": "\/ means to load the script from the resource directory root, you can add multiple"
        },
        "index": {
          "title": "Site plugin configuration",
          "importAll": "Import all",
          "removeSelectedConfirm": "Are you sure you want to delete the selected plugin?",
          "removeConfirm": "Are you sure you want to delete this plugin?",
          "removeTitle": "Delete confirmation",
          "headers": {
            "name": "Name",
            "pages": "Applicable page",
            "enable": "Enable",
            "action": "Action"
          },
          "importNameDuplicate": "The name [{name}] already exists. Please re-enter the new name:",
          "invalidPlugin": "Invalid plugin"
        }
      },
      "sites": {
        "add": {
          "title": "Add Site",
          "next": "Next",
          "prev": "Previous",
          "help": "Can't find the site you want?",
          "validMsg": "Please select a site (support search)",
          "custom": "Custom",
          "step1": "Select site",
          "step2": "Confirm site configuration"
        },
        "edit": {
          "title": "Edit Site"
        },
        "editor": {
          "defaultClient": "Download server (if not selected, the default download server of the basic settings will prevail)",
          "name": "Site name",
          "tags": "Tags",
          "inputTags": "press Enter to add multiple",
          "schema": "Site schema",
          "description": "Site description",
          "host": "Host",
          "url": "Full url",
          "urlTip": "The full address of the website, such as: https:\/\/www.github.com\/",
          "passkey": "Passkey",
          "passkeyTip": "The key is only used to copy the download address operation. If you do not need this function, please leave it blank.",
          "allowSearch": "Allow Search",
          "allowGetUserInfo": "Allow access to user information (Beta)",
          "cdn": "Site CDN list",
          "cdnTip": "If you use a different URL than the system definition, you can fill in the currently used website address, fill in one address per line, the first one will be used as the address used for the search.",
          "priority": "Priority",
          "priorityTip": "Can be used for search sorting",
          "offline": "Site is offline (downtime\/shutdown)",
          "timezone": "Timezone",
          "upLoadLimit": "Upload limit",
          "upLoadLimitTip": "Upload limit (KB\/s), 0 or empty for no limit",
          "disableMessageCount": "Display message count"
        },
        "userinfo": {
          "title": "User Info",
          "deleteConfirm": "confirm deletion? This operation cannot be restored, please make sure that you have made a corresponding backup."
        },
        "index": {
          "importAll": "One-click import site",
          "importAllConfirm": "Are you sure you want to import site operations? This will import sites that have been logged in to the browser but have not been added.",
          "removeSelectedConfirm": "Are you sure you want to delete the selected site?",
          "removeConfirm": "Are you sure you want to delete this site?",
          "removeTitle": "Delete confirmation",
          "plugins": "Plugins",
          "showUserInfo": "UserInfo",
          "title": "Site settings",
          "subTitle": "Only the configured site will display the plugin icon and the corresponding function; the offline site will no longer participate in the search and information acquisition;",
          "searchEntry": "Search Entry",
          "importedText": "Imported successfully",
          "siteDuplicateText": "The site already exists",
          "headers": {
            "name": "Name",
            "tags": "Tags",
            "allowSearch": "Allow Search",
            "allowGetUserInfo": "User Info",
            "offline": "Offline",
            "activeURL": "URL",
            "action": "Action"
          },
          "importConfig": "Import from config",
          "importConfirm": "Are you sure you want to import [{name}] ?",
          "importDuplicateConfirm": "The site {name} already exists. Do you need to import search entrys and plugins?",
          "resetFavicons": "Reset site favicons"
        }
      },
      "siteSearchEntry": {
        "add": {
          "title": "New Search Entry"
        },
        "edit": {
          "title": "Edit Search Entry"
        },
        "editor": {
          "name": "Entry name",
          "entry": "Entry page (defaults to the system's defined entry page if not filled)",
          "parseScript": "Search result parsing script",
          "parseScriptFile": "Search result parsing script file",
          "resultSelector": "Torrent list selector",
          "category": "Category (not selected for all)",
          "queryString": "Append query parameters"
        },
        "index": {
          "title": "Site search entry configuration",
          "removeSelectedConfirm": "Are you sure you want to delete the selected search entry?",
          "removeConfirm": "Are you sure you want to delete this search entry?",
          "removeTitle": "Delete confirmation",
          "help": "how to use?",
          "headers": {
            "name": "Name",
            "categories": "Categories",
            "enable": "Enable",
            "action": "Action"
          }
        }
      }
    },
    "statistic": {
      "selectSite": "Select sites that need statistics",
      "goback": "Back",
      "share": "Generate a share image",
      "exportRawData": "Export original data",
      "allSite": "(All Sites)",
      "upload": "Upload",
      "download": "Download",
      "bonus": "Bonus",
      "baseDataTitle": "[{userName}@{site}] Basic Data",
      "baseDataSubTitle": "Uploaded: {uploaded}, Downloaded: {downloaded}, Bonus: {bonus}",
      "data": "Data",
      "seedingDataTitle": "[{userName}@{site}] Seeding",
      "seedingDataSubTitle": "Seeding size: {seedingSize}, count: {count}",
      "seedingSize": "Seeding size",
      "seedingCount": "Seeding count",
      "barDataTitle": "[{userName}@{site}] Upload Data",
      "size": "Size",
      "count": "Count",
      "total": "Total",
      "percent": "Percent",
      "note": "Note: <br>1. Chart history data comes from the overview page, manual or automatic update will be recorded; <br>2. Each tracker only saves one per day;",
      "dateRange": {
        "7day": "7 days",
        "30day": "30 days",
        "60day": "60 days",
        "90day": "90 days",
        "180day": "180 days",
        "all": "All days"
      }
    },
    "service": {
      "testClientConnectivityFailed": "Test client connection failed[{address}]",
      "contextMenus": {
        "history": "View download history",
        "systemLog": "View plugin log",
        "issues": "Use question feedback",
        "searchSelectionText": "Search for \"%s\" related torrents",
        "searchSelectionTextOnThisSite": "Search only for this site \"%s\" related torrents",
        "searchByIMDb": "Search current IMDb related torrents",
        "searchByDouban": "Search current Douban related torrents",
        "searchByDefault": "Search by default",
        "searchInAllSite": "Search across all sites",
        "downloadClientPath": "{clientName} -> Specified path",
        "userCanceled": "User canceled",
        "pluginStatusIsUnknown": "The status of the plugin is unknown. The current operation may fail. Please refresh the page and try again.",
        "sendingLink": "Sending a link to download server",
        "downloadClientGetFailed": "Failed to get the download server.",
        "sendTorrentToClientDone": "The download link is sent.",
        "sendTorrentToClientError": "Download link failed to send!",
        "sendTorrentToDefaultClient": "Sent to the default server {client.name} -> {- client.address}",
        "sendTorrentToClient": "Send to other servers",
        "searchInSite": "Search in site",
        "searchInSolution": "Search in solution"
      },
      "searcher": {
        "siteSearchConfigEntryIsEmpty": "The site [{site.name}] is not configured with a search page. Please configure it first.",
        "siteSearchEntryIsEmpty": "The site [{site.name}] does not specify a search entry. Please specify a search entry first.",
        "siteSearchResultParseFailed": "[{site.name}] data parsing failed!",
        "siteEvalScriptFailed": "[{site.name}] Script execution error!",
        "siteSearchResultError": "[{site.name}] No expected data was returned.",
        "siteAbortSearch": "Deactivating search request for [{site.host}]",
        "siteAbortSearchError": "[{site.host}] Failed to cancel search request!",
        "siteNetworkFailed": "[{site.name}]Network request failed! ({msg})"
      },
      "controller": {
        "invalidAddress": "Invalid Address",
        "invalidDownloadServer": "Invalid Download Server",
        "downloadTimeout": "The download server connection timed out, please check the network settings or adjust the server timeout!",
        "downloadFinished": "Download server {name} handles [{action}] command completion",
        "downloadError": "Download server {name} processing [{action}] command failed!",
        "torrentAdded": "{title} The torrent has been added.",
Download .txt
gitextract_s_ss06tw/

├── .eslintrc.json
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report-cn.md
│   │   ├── bug-report.md
│   │   ├── feature-request-cn.md
│   │   ├── feature-request.md
│   │   ├── new-tracker-request-cn.md
│   │   ├── new-tracker-request.md
│   │   └── suggestions-or-comments.md
│   ├── issue-close-app.yml
│   ├── pull_request_template.md
│   ├── stale.yml
│   └── workflows/
│       ├── build_action.yml
│       └── build_canary.yml
├── .gitignore
├── .nvmrc
├── LICENSE
├── README.md
├── babel.config.js
├── debug/
│   ├── config/
│   │   └── config.json
│   ├── data/
│   │   └── beforeSearching.json
│   ├── package.json
│   ├── src/
│   │   ├── App.ts
│   │   ├── BuildPlugin.ts
│   │   ├── SearchData.ts
│   │   ├── buildResource.ts
│   │   └── index.ts
│   ├── tsconfig.json
│   └── typings.d.ts
├── package.json
├── privacy-statement.md
├── public/
│   ├── _locales/
│   │   ├── en/
│   │   │   └── messages.json
│   │   └── zh_CN/
│   │       └── messages.json
│   ├── assets/
│   │   ├── base.css
│   │   └── options.css
│   ├── changelog.html
│   ├── index.html
│   ├── libs/
│   │   ├── Base64.js
│   │   ├── drag.js
│   │   ├── materialIcons/
│   │   │   ├── content_style.css
│   │   │   └── style.css
│   │   ├── notice/
│   │   │   ├── notice.js
│   │   │   └── noticejs.css
│   │   └── types.expand.js
│   ├── manifest.json
│   └── popup.html
├── resource/
│   ├── clients/
│   │   ├── README.md
│   │   ├── deluge/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── flood/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── qbittorrent/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── ruTorrent/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── synologyDownloadStation/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   ├── transmission/
│   │   │   ├── config.json
│   │   │   └── init.js
│   │   └── utorrent/
│   │       ├── config.json
│   │       └── init.js
│   ├── i18n/
│   │   ├── README.md
│   │   ├── en.json
│   │   └── zh-CN.json
│   ├── libs/
│   │   └── album/
│   │       ├── album.js
│   │       └── style.css
│   ├── publicSites/
│   │   ├── douban.com/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── doulist.js
│   │   │   ├── explore.js
│   │   │   ├── subject.js
│   │   │   └── top250.js
│   │   ├── goodmovieslist.com/
│   │   │   ├── best-movies.js
│   │   │   └── config.json
│   │   ├── imdb.com/
│   │   │   ├── config.json
│   │   │   ├── subject.js
│   │   │   └── top.js
│   │   └── reseed.tongyifan.me/
│   │       ├── config.json
│   │       └── reseed.js
│   ├── schemas/
│   │   ├── Common/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── Discuz/
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── Gazelle/
│   │   │   ├── config.json
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   ├── GazelleJSONAPI/
│   │   │   ├── config.json
│   │   │   └── getSearchResult.js
│   │   ├── NexusPHP/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   ├── parser/
│   │   │   │   └── downloadURL.js
│   │   │   └── torrents.js
│   │   ├── README.md
│   │   ├── TNode/
│   │   │   ├── common.js
│   │   │   ├── config.json
│   │   │   ├── details.js
│   │   │   ├── getSearchResult.js
│   │   │   └── torrents.js
│   │   └── UNIT3D/
│   │       ├── config.json
│   │       ├── details.js
│   │       ├── getSearchResult.js
│   │       ├── torrents.js
│   │       └── userTorrents.js
│   └── sites/
│       ├── 1ptba.com/
│       │   └── config.json
│       ├── 52pt.site/
│       │   └── config.json
│       ├── README.md
│       ├── aidoru-online.me/
│       │   └── config.json
│       ├── aither.cc/
│       │   └── config.json
│       ├── alpharatio.cc/
│       │   └── config.json
│       ├── animebytes.tv/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   └── userTorrents.js
│       ├── anthelion.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── asiancinema.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── audiences.me/
│       │   └── config.json
│       ├── azusa.wiki/
│       │   └── config.json
│       ├── baconbits.org/
│       │   └── config.json
│       ├── bemaniso.ws/
│       │   └── config.json
│       ├── beyond-hd.me/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── bibliotik.me/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── bitbr/
│       │   └── config.json
│       ├── bitpt.cn/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── blutopia.cc/
│       │   └── config.json
│       ├── broadcasthe.net/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── brokenstones.is/
│       │   └── config.json
│       ├── bt.neu6.edu.cn/
│       │   └── config.json
│       ├── bwtorrents.tv/
│       │   └── config.json
│       ├── byr.pt/
│       │   └── config.json
│       ├── carpt.net/
│       │   └── config.json
│       ├── ccfbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── chdbits.co/
│       │   └── config.json
│       ├── cinemageddon.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── club.hares.top/
│       │   └── config.json
│       ├── cnlang.org/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── concertos.live/
│       │   └── config.json
│       ├── cyanbug.net/
│       │   └── config.json
│       ├── dajiao.cyou/
│       │   └── config.json
│       ├── dicmusic.com/
│       │   └── config.json
│       ├── discfan.net/
│       │   └── config.json
│       ├── et8.org/
│       │   └── config.json
│       ├── extremlymtorrents.ws/
│       │   └── config.json
│       ├── femdomcult.org/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── filelist.io/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── fsm.name/
│       │   └── config.json
│       ├── gainbound.net/
│       │   └── config.json
│       ├── gay-torrents.org/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── gazellegames.net/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── gfxpeers.net/
│       │   └── config.json
│       ├── greatposterwall.com/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── hawke.uno/
│       │   └── config.json
│       ├── hd-space.org/
│       │   ├── config.json
│       │   └── details.js
│       ├── hd-torrents.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── hdatmos.club/
│       │   └── config.json
│       ├── hdbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── hdchina.org/
│       │   └── config.json
│       ├── hdcity.city/
│       │   └── config.json
│       ├── hddolby.com/
│       │   └── config.json
│       ├── hdf.world/
│       │   └── config.json
│       ├── hdfans.org/
│       │   └── config.json
│       ├── hdhome.org/
│       │   └── config.json
│       ├── hdmayi.com/
│       │   └── config.json
│       ├── hdpt.xyz/
│       │   └── config.json
│       ├── hdroute.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── hdsky.me/
│       │   └── config.json
│       ├── hdtime.org/
│       │   └── config.json
│       ├── hdvideo.one/
│       │   └── config.json
│       ├── hdzone.me/
│       │   └── config.json
│       ├── hhanclub.top/
│       │   └── config.json
│       ├── htpt.cc/
│       │   └── config.json
│       ├── hudbt.hust.edu.cn/
│       │   └── config.json
│       ├── ihdbits.me/
│       │   └── config.json
│       ├── iptorrents.com/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── joyhd.net/
│       │   └── config.json
│       ├── jpopsuki.eu/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── jptv.club/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── jptvts.us/
│       │   └── config.json
│       ├── kamept.com/
│       │   └── config.json
│       ├── karagarga.in/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── kp.m-team.cc/
│       │   ├── config.json
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── learnflakes.net/
│       │   └── config.json
│       ├── leaves.red/
│       │   └── config.json
│       ├── lztr.me/
│       │   └── config.json
│       ├── monikadesign.uk/
│       │   └── config.json
│       ├── nanyangpt.com/
│       │   └── config.json
│       ├── nebulance.io/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── nicept.net/
│       │   └── config.json
│       ├── npupt.com/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── oldtoons.world/
│       │   └── config.json
│       ├── open.cd/
│       │   └── config.json
│       ├── orpheus.network/
│       │   └── config.json
│       ├── ourbits.club/
│       │   └── config.json
│       ├── passthepopcorn.me/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── piggo.me/
│       │   └── config.json
│       ├── pt.0ff.cc/
│       │   └── config.json
│       ├── pt.2xfree.org/
│       │   └── config.json
│       ├── pt.btschool.club/
│       │   └── config.json
│       ├── pt.eastgame.org/
│       │   └── config.json
│       ├── pt.hd4fans.org/
│       │   └── config.json
│       ├── pt.hdbd.us/
│       │   └── config.json
│       ├── pt.hdpost.top/
│       │   └── config.json
│       ├── pt.hdupt.com/
│       │   └── config.json
│       ├── pt.keepfrds.com/
│       │   └── config.json
│       ├── pt.newworld.plus/
│       │   └── config.json
│       ├── pt.sjtu.edu.cn/
│       │   └── config.json
│       ├── pt.soulvoice.club/
│       │   └── config.json
│       ├── pt.xauat6.edu.cn/
│       │   └── config.json
│       ├── pt.zhixing.bjtu.edu.cn/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── ptchina.org/
│       │   └── config.json
│       ├── pterclub.com/
│       │   └── config.json
│       ├── pthome.net/
│       │   └── config.json
│       ├── ptsbao.club/
│       │   └── config.json
│       ├── pussytorrents.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── redacted.ch/
│       │   └── config.json
│       ├── resource.xidian.edu.cn/
│       │   └── config.json
│       ├── sdbits.org/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── shadowthein.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── speedapp.io/
│       │   └── config.json
│       ├── sportscult.org/
│       │   ├── config.json
│       │   └── details.js
│       ├── springsunday.net/
│       │   └── config.json
│       ├── sugoimusic.me/
│       │   └── config.json
│       ├── teamhd.org/
│       │   └── config.json
│       ├── thegeeks.click/
│       │   └── config.json
│       ├── tjupt.org/
│       │   └── config.json
│       ├── totheglory.im/
│       │   ├── bookmarks.js
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── parser/
│       │       └── downloadURL.js
│       ├── u2.dmhy.org/
│       │   └── config.json
│       ├── ubits.club/
│       │   └── config.json
│       ├── uhdbits.org/
│       │   ├── config.json
│       │   ├── getSearchResult.js
│       │   └── getUserSeedingTorrents.js
│       ├── ultrahd.net/
│       │   └── config.json
│       ├── wintersakura.net/
│       │   └── config.json
│       ├── world-in-hd.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   └── getSearchResult.js
│       ├── www.beitai.pt/
│       │   └── config.json
│       ├── www.cgpeers.com/
│       │   └── config.json
│       ├── www.cinematik.net/
│       │   ├── browse.js
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── getUserSeedingTorrents.js
│       ├── www.empornium.sx/
│       │   └── config.json
│       ├── www.filept.com/
│       │   └── config.json
│       ├── www.gamegamept.com/
│       │   ├── config.json
│       │   └── torrents.js
│       ├── www.gaytor.rent/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── www.haidan.video/
│       │   ├── config.json
│       │   └── getSearchResult.js
│       ├── www.hdarea.co/
│       │   └── config.json
│       ├── www.hitpt.com/
│       │   └── config.json
│       ├── www.icc2022.com/
│       │   └── config.json
│       ├── www.morethantv.me/
│       │   └── config.json
│       ├── www.myanonamouse.net/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   ├── getUserSeedingTorrents.js
│       │   └── torrents.js
│       ├── www.okpt.net/
│       │   ├── config.json
│       │   └── torrents.js
│       ├── www.pttime.org/
│       │   └── config.json
│       ├── www.ptzone.xyz/
│       │   └── config.json
│       ├── www.skyey2.com/
│       │   ├── config.json
│       │   └── getUserSeedingTorrents.js
│       ├── www.torrentday.com/
│       │   └── config.json
│       ├── www.torrentleech.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── www.torrentseeds.org/
│       │   ├── config.json
│       │   ├── details.js
│       │   ├── getSearchResult.js
│       │   └── torrents.js
│       ├── xingtan.one/
│       │   └── config.json
│       ├── zhuque.in/
│       │   └── config.json
│       └── zmpt.cc/
│           └── config.json
├── src/
│   ├── background/
│   │   ├── README.md
│   │   ├── collection.ts
│   │   ├── config.ts
│   │   ├── contextMenus.ts
│   │   ├── controller.ts
│   │   ├── downloadHistory.ts
│   │   ├── downloadQuene.ts
│   │   ├── i18n.ts
│   │   ├── index.ts
│   │   ├── infoParser.ts
│   │   ├── keepUploadTask.ts
│   │   ├── omnibox.ts
│   │   ├── pageParser.ts
│   │   ├── plugins/
│   │   │   ├── OWSS.ts
│   │   │   └── WebDAV.ts
│   │   ├── searchResultSnapshot.ts
│   │   ├── searcher.ts
│   │   ├── service.ts
│   │   ├── site.ts
│   │   ├── syncStorage.ts
│   │   ├── user.ts
│   │   └── userData.ts
│   ├── changelog/
│   │   ├── Index.vue
│   │   └── index.ts
│   ├── content/
│   │   ├── README.md
│   │   └── index.ts
│   ├── debugger/
│   │   ├── Index.vue
│   │   └── index.ts
│   ├── interface/
│   │   ├── common.ts
│   │   ├── enum.ts
│   │   └── types.expand.js
│   ├── options/
│   │   ├── App.vue
│   │   ├── assets/
│   │   │   └── contextMenu.scss
│   │   ├── components/
│   │   │   ├── ColorSelector.vue
│   │   │   ├── Content.vue
│   │   │   ├── DownloadTo.vue
│   │   │   ├── Footer.vue
│   │   │   ├── MovieInfoCard.vue
│   │   │   ├── Navigation.vue
│   │   │   ├── Permissions.vue
│   │   │   ├── SearchBox.vue
│   │   │   ├── Topbar.vue
│   │   │   ├── TorrentProgress.vue
│   │   │   └── WorkingStatus.vue
│   │   ├── i18n.ts
│   │   ├── main.ts
│   │   ├── plugins/
│   │   │   └── vuetify.ts
│   │   ├── router.ts
│   │   ├── shims-tsx.d.ts
│   │   ├── shims-vue.d.ts
│   │   ├── store.ts
│   │   ├── typings.d.ts
│   │   └── views/
│   │       ├── About.vue
│   │       ├── AutoSignWarning.vue
│   │       ├── Donate.vue
│   │       ├── History.vue
│   │       ├── Home.vue
│   │       ├── SystemLogs.vue
│   │       ├── Teams.vue
│   │       ├── TechnologyStack.vue
│   │       ├── UserDataTimeline.vue
│   │       ├── collection/
│   │       │   ├── AddToGroup.vue
│   │       │   ├── GroupCard.vue
│   │       │   └── Index.vue
│   │       ├── keepUpload/
│   │       │   └── KeepUploadTasks.vue
│   │       ├── search/
│   │       │   ├── Actions.vue
│   │       │   ├── AddToCollectionGroup.vue
│   │       │   ├── KeepUpload.vue
│   │       │   ├── SearchResultSnapshot.vue
│   │       │   ├── SearchTorrent.scss
│   │       │   ├── SearchTorrent.ts
│   │       │   └── SearchTorrent.vue
│   │       ├── settings/
│   │       │   ├── Backup/
│   │       │   │   ├── Index.vue
│   │       │   │   └── Server/
│   │       │   │       ├── Add.vue
│   │       │   │       ├── Edit.vue
│   │       │   │       ├── Editor.vue
│   │       │   │       └── List.vue
│   │       │   ├── Base/
│   │       │   │   └── Index.vue
│   │       │   ├── DownloadClients/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── DownloadPaths/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Index.vue
│   │       │   │   └── KeyDescription.vue
│   │       │   ├── Language/
│   │       │   │   └── Index.vue
│   │       │   ├── SearchSolution/
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── SitePlugins/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── SiteSearchEntry/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   └── Index.vue
│   │       │   ├── Sites/
│   │       │   │   ├── Add.vue
│   │       │   │   ├── Edit.vue
│   │       │   │   ├── Editor.vue
│   │       │   │   ├── Index.vue
│   │       │   │   └── UserInfo.vue
│   │       │   └── SupportSchema.vue
│   │       └── statisticCharts/
│   │           └── SiteBase.vue
│   ├── popup/
│   │   └── index.ts
│   └── service/
│       ├── api.ts
│       ├── backupFileParser.ts
│       ├── clientController.ts
│       ├── downloader.ts
│       ├── extension.ts
│       ├── favicon.ts
│       ├── filters.ts
│       ├── localStorage.ts
│       ├── logger.ts
│       ├── movieInfoService.ts
│       ├── pathHandler.ts
│       └── public.ts
├── tsconfig.json
├── update/
│   └── index.xml
├── vue.config.js
└── webpack/
    ├── common.js
    ├── dev-background.js
    ├── dev-content.js
    ├── prod-background.js
    └── prod-content.js
Download .txt
SYMBOL INDEX (1581 symbols across 181 files)

FILE: debug/src/App.ts
  class App (line 13) | class App {
    method constructor (line 19) | constructor(options) {
    method useModules (line 37) | private useModules() {
    method mountRoutes (line 50) | private mountRoutes(): void {
    method start (line 88) | public start() {

FILE: debug/src/BuildPlugin.ts
  type Dictionary (line 4) | type Dictionary<T> = { [key: string]: T };
  class BuildPlugin (line 9) | class BuildPlugin {
    method constructor (line 13) | constructor(rootPaht: string = "../../dist/resource") {
    method buildResource (line 21) | public buildResource() {
    method getSystemConfig (line 31) | public getSystemConfig() {
    method getResourceConfig (line 44) | private getResourceConfig(name: string): any {
    method makeParser (line 74) | private makeParser(name: string) {
    method getParser (line 99) | private getParser(parentFolder): any {
    method getSupportedSites (line 121) | public getSupportedSites() {
    method getCollaborator (line 218) | public getCollaborator(source: string | Array<string>): string {
    method geti18n (line 238) | public geti18n() {
    method replaceKeys (line 267) | replaceKeys(

FILE: debug/src/SearchData.ts
  class SearchData (line 4) | class SearchData {
    method constructor (line 5) | constructor(public config: any = {}) {}
    method generate (line 7) | public generate() {
    method getTitle (line 40) | private getTitle() {
    method getSubTitle (line 58) | private getSubTitle() {
    method getHost (line 76) | private getHost() {
    method getTags (line 82) | private getTags() {
    method getSize (line 118) | private getSize() {

FILE: public/libs/Base64.js
  function Base64 (line 11) | function Base64() {

FILE: public/libs/drag.js
  function Drag (line 5) | function Drag() {

FILE: public/libs/notice/notice.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function _interopRequireWildcard (line 136) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
  function getCallback (line 145) | function getCallback(ref, eventName) {
  function defineProperties (line 294) | function defineProperties(target, props) { for (var i = 0; i < props.len...
  function _interopRequireWildcard (line 310) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
  function _interopRequireDefault (line 312) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
  function _classCallCheck (line 314) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
  function NoticeJs (line 321) | function NoticeJs() {
  function defineProperties (line 441) | function defineProperties(target, props) { for (var i = 0; i < props.len...
  function _interopRequireWildcard (line 451) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
  function _classCallCheck (line 453) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
  function Components (line 458) | function Components() {

FILE: resource/clients/deluge/init.js
  class Deluge (line 9) | class Deluge {
    method init (line 17) | init(options) {
    method call (line 45) | call(action, data) {
    method getSessionId (line 73) | getSessionId(callback) {
    method exec (line 126) | exec(options, callback, tags) {
    method addTorrentFromUrl (line 174) | addTorrentFromUrl(data, callback) {
    method addTorrent (line 229) | addTorrent(options, callback) {

FILE: resource/clients/flood/init.js
  class Client (line 7) | class Client {
    method init (line 15) | init(options) {
    method call (line 40) | call(action, data) {
    method authenticate (line 70) | authenticate(callback) {
    method testClientConnectivity (line 101) | testClientConnectivity(callback) {
    method addTorrentFromUrl (line 132) | addTorrentFromUrl(data, callback) {
    method addTorrentUrl (line 179) | addTorrentUrl(data, callback) {
    method addTorrentFile (line 183) | addTorrentFile(data, callback) {
    method addTorrent (line 193) | addTorrent(suffix, data, callback) {

FILE: resource/clients/qbittorrent/init.js
  class Client (line 6) | class Client {
    method init (line 14) | init(options) {
    method call (line 35) | call(action, data) {
    method getSessionId (line 71) | getSessionId(callback) {
    method exec (line 106) | exec(options, callback, tags) {
    method addTorrentFromUrl (line 159) | addTorrentFromUrl(data, callback) {
    method addTorrent (line 201) | addTorrent(params, callback) {

FILE: resource/clients/ruTorrent/init.js
  class Client (line 9) | class Client {
    method init (line 17) | init(options) {
    method call (line 41) | call(action, data) {
    method testClientConnectivity (line 82) | testClientConnectivity(callback) {
    method addTorrentFromUrl (line 109) | addTorrentFromUrl(data, callback) {
    method addTorrent (line 163) | addTorrent(data, callback) {

FILE: resource/clients/synologyDownloadStation/init.js
  class Client (line 6) | class Client {
    method init (line 8) | init(options) {
    method getSessionId (line 21) | getSessionId() {
    method call (line 60) | call(action, data) {
    method addTorrentFromUrl (line 91) | addTorrentFromUrl(options, callback) {
    method addTorrent (line 167) | addTorrent(formData, headers, options, callback) {

FILE: resource/clients/transmission/init.js
  class Transmission (line 6) | class Transmission {
    method init (line 14) | init(options) {
    method call (line 51) | call(action, data) {
    method exec (line 89) | exec(options, callback, tags) {
    method sendRequest (line 152) | sendRequest(options, success, error) {
    method sessionStats (line 172) | sessionStats() {
    method addTorrentFromUrl (line 185) | addTorrentFromUrl(url, savePath, autoStart, callback, uploadLimit = 0) {
    method addTorrent (line 241) | addTorrent(options, callback) {
    method getFreeSpace (line 280) | getFreeSpace(path, callback) {

FILE: resource/clients/utorrent/init.js
  class uTorrent (line 5) | class uTorrent {
    method init (line 13) | init(options) {
    method call (line 51) | call(action, data) {
    method getSessionId (line 79) | getSessionId(callback) {
    method exec (line 126) | exec(options, callback, tags) {
    method addTorrentFromUrl (line 184) | addTorrentFromUrl(data, callback) {
    method addTorrent (line 222) | addTorrent(options, callback) {

FILE: resource/libs/album/album.js
  method onButtonClick (line 67) | onButtonClick() {}
  method onClose (line 69) | onClose() {}
  method init (line 95) | init() {
  method initSystemButtons (line 188) | initSystemButtons() {
  method initCustomButtons (line 199) | initCustomButtons() {
  method remove (line 224) | remove() {
  method behind (line 234) | behind(zIndex) {
  method bringToFront (line 247) | bringToFront() {
  method initEvents (line 257) | initEvents(self = this) {
  method showTopBar (line 450) | showTopBar(self) {
  method hideThumbsBar (line 463) | hideThumbsBar() {
  method showThumbsBar (line 476) | showThumbsBar() {
  method load (line 493) | load() {
  method addItem (line 537) | addItem(options, isTagClick) {
  method addTag (line 598) | addTag(item) {
  method createTag (line 619) | createTag(tag, setActive) {
  method initImageList (line 662) | initImageList(images, active, isTags) {
  method clear (line 689) | clear() {
  method setActive (line 700) | setActive(key, resize) {
  method _setActiveImage (line 793) | _setActiveImage(resize) {
  method setActiveImage (line 843) | setActiveImage(resize, _self = this) {
  method resize (line 856) | resize(rate) {
  method setSize (line 879) | setSize() {
  method gotoImage (line 904) | gotoImage(action, _self) {
  method loadImage (line 940) | loadImage(url, callback, reload) {

FILE: resource/publicSites/douban.com/common.js
  class Common (line 2) | class Common {
    method init (line 3) | init() {
    method search (line 14) | search(key, button) {

FILE: resource/publicSites/douban.com/doulist.js
  class App (line 2) | class App extends window.DoubanCommon {
    method initButtons (line 6) | initButtons() {
    method createButton (line 36) | createButton(parent, key, title) {

FILE: resource/publicSites/douban.com/explore.js
  class App (line 2) | class App extends window.DoubanCommon {
    method constructor (line 3) | constructor() {
    method initButtons (line 12) | initButtons() {
    method createButton (line 19) | createButton(parent) {

FILE: resource/publicSites/douban.com/subject.js
  class App (line 2) | class App extends window.DoubanCommon {
    method initButtons (line 6) | initButtons() {
    method getIMDbId (line 32) | getIMDbId() {
    method getTitle (line 48) | getTitle() {

FILE: resource/publicSites/douban.com/top250.js
  class App (line 2) | class App extends window.DoubanCommon {
    method initButtons (line 6) | initButtons() {
    method createButton (line 16) | createButton(parent, key) {

FILE: resource/publicSites/goodmovieslist.com/best-movies.js
  class App (line 3) | class App extends window.DoubanCommon {
    method initButtons (line 7) | initButtons() {
    method createButton (line 20) | createButton(parent, key) {
    method showPopupMenus (line 56) | showPopupMenus(event, key) {
    method showPopupMenusForSolutions (line 94) | showPopupMenusForSolutions(event, key) {

FILE: resource/publicSites/imdb.com/subject.js
  class App (line 2) | class App {
    method init (line 3) | init() {
    method initButtons (line 12) | initButtons() {
    method getIMDbId (line 32) | getIMDbId() {

FILE: resource/publicSites/imdb.com/top.js
  class App (line 2) | class App {
    method init (line 3) | init() {
    method initButtons (line 12) | initButtons() {
    method createButton (line 27) | createButton(parent, key, title) {

FILE: resource/publicSites/reseed.tongyifan.me/reseed.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method checkURL (line 50) | checkURL(url) {

FILE: resource/schemas/Common/common.js
  class Common (line 2) | class Common {
    method constructor (line 3) | constructor() {
    method t (line 17) | t(key, options) {
    method initFreeSpaceButton (line 24) | initFreeSpaceButton() {
    method initDetailButtons (line 58) | initDetailButtons() {
    method initListButtons (line 81) | initListButtons(checkPasskey = false) {
    method addSaveAllTorrentFilesButton (line 205) | addSaveAllTorrentFilesButton(checkPasskey) {
    method checkPermissions (line 246) | checkPermissions(permissions) {
    method sendTorrentToDefaultClient (line 254) | sendTorrentToDefaultClient(option, showNotice = true) {
    method hideNotice (line 316) | hideNotice(notice) {
    method sendTorrentToClient (line 329) | sendTorrentToClient(options, showNotice = true) {
    method downloadFromDroper (line 389) | downloadFromDroper(data, callback) {
    method isNexusPHP (line 417) | isNexusPHP() {
    method getDroperURL (line 425) | getDroperURL(url) {
    method call (line 449) | call(action, data) {
    method addSendTorrentToClientButton (line 465) | addSendTorrentToClientButton() {
    method addSendTorrentToDefaultClientButton (line 522) | addSendTorrentToDefaultClientButton() {
    method addCopyTextToClipboardButton (line 582) | addCopyTextToClipboardButton() {
    method initCollectionButton (line 620) | initCollectionButton() {
    method addToCollectionButton (line 634) | addToCollectionButton() {
    method addRemoveCollectionButton (line 711) | addRemoveCollectionButton(item) {
    method getContentMenusForUrl (line 738) | getContentMenusForUrl(url) {
    method showContentMenusForUrl (line 817) | showContentMenusForUrl(options, event, success, error) {
    method checkSize (line 874) | checkSize(doms) {
    method getTotalSize (line 905) | getTotalSize(source) {
    method getSize (line 918) | getSize(size) {
    method confirmSize (line 947) | confirmSize(doms) {
    method startDownloadURLs (line 969) | startDownloadURLs(success, error, downloadOptions) {
    method downloadURLsInBackground (line 1015) | downloadURLsInBackground(urls, callback, downloadOptions) {
    method downloadURLs (line 1057) | downloadURLs(urls, count, callback, downloadOptions) {
    method showStatusMessage (line 1122) | showStatusMessage(msg) {
    method clone (line 1139) | clone(source) {
    method showAllContentMenus (line 1147) | showAllContentMenus(event, success, error) {
    method getFullURL (line 1225) | getFullURL(url) {
    method initSayThanksButton (line 1242) | initSayThanksButton() {

FILE: resource/schemas/Common/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 26) | showTorrentSize() {
    method getTitle (line 37) | getTitle() {
    method getIMDbId (line 44) | getIMDbId() {

FILE: resource/schemas/Common/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 27) | getResult() {
    method getFieldValue (line 134) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {
    method getFullURL (line 163) | getFullURL(url) {
    method getTitle (line 178) | getTitle(row, cells, fieldIndex) {

FILE: resource/schemas/Common/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 41) | confirmWhenExceedSize() {

FILE: resource/schemas/Discuz/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 21) | getDownloadURL() {
    method getTitle (line 59) | getTitle() {

FILE: resource/schemas/Discuz/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getTime (line 232) | getTime(cell) {
    method getSubTitle (line 247) | getSubTitle(title, row) {
    method getCategory (line 255) | getCategory(cell) {

FILE: resource/schemas/Discuz/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 59) | confirmWhenExceedSize() {

FILE: resource/schemas/Gazelle/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 40) | getResult() {
    method getCategory (line 196) | getCategory(link) {
    method getCategoryName (line 220) | getCategoryName(id) {

FILE: resource/schemas/Gazelle/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 47) | confirmWhenExceedSize() {
    method downloadFromDroper (line 60) | downloadFromDroper(data, callback) {

FILE: resource/schemas/GazelleJSONAPI/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method start (line 16) | start() {
    method getResult (line 41) | getResult() {
    method getAuthKey (line 129) | getAuthKey() {

FILE: resource/schemas/NexusPHP/common.js
  class Common (line 2) | class Common {
    method constructor (line 3) | constructor() {
    method t (line 17) | t(key, options) {
    method initFreeSpaceButton (line 24) | initFreeSpaceButton() {
    method initDetailButtons (line 58) | initDetailButtons() {
    method initListButtons (line 82) | initListButtons(checkPasskey = false) {
    method addSaveAllTorrentFilesButton (line 206) | addSaveAllTorrentFilesButton(checkPasskey) {
    method checkPermissions (line 247) | checkPermissions(permissions) {
    method sendTorrentToDefaultClient (line 255) | sendTorrentToDefaultClient(option, showNotice = true) {
    method hideNotice (line 317) | hideNotice(notice) {
    method sendTorrentToClient (line 330) | sendTorrentToClient(options, showNotice = true) {
    method downloadFromDroper (line 390) | downloadFromDroper(data, callback) {
    method isNexusPHP (line 418) | isNexusPHP() {
    method getDroperURL (line 426) | getDroperURL(url) {
    method call (line 450) | call(action, data) {
    method addSendTorrentToClientButton (line 466) | addSendTorrentToClientButton() {
    method addSendTorrentToDefaultClientButton (line 523) | addSendTorrentToDefaultClientButton() {
    method addCopyTextToClipboardButton (line 583) | addCopyTextToClipboardButton() {
    method initCollectionButton (line 621) | initCollectionButton() {
    method addToCollectionButton (line 635) | addToCollectionButton() {
    method addRemoveCollectionButton (line 712) | addRemoveCollectionButton(item) {
    method getContentMenusForUrl (line 739) | getContentMenusForUrl(url) {
    method showContentMenusForUrl (line 818) | showContentMenusForUrl(options, event, success, error) {
    method checkSize (line 875) | checkSize(doms) {
    method getTotalSize (line 906) | getTotalSize(source) {
    method getSize (line 919) | getSize(size) {
    method confirmSize (line 948) | confirmSize(doms) {
    method startDownloadURLs (line 970) | startDownloadURLs(success, error, downloadOptions) {
    method downloadURLsInBackground (line 1016) | downloadURLsInBackground(urls, callback, downloadOptions) {
    method downloadURLs (line 1058) | downloadURLs(urls, count, callback, downloadOptions) {
    method showStatusMessage (line 1123) | showStatusMessage(msg) {
    method clone (line 1140) | clone(source) {
    method showAllContentMenus (line 1148) | showAllContentMenus(event, success, error) {
    method getFullURL (line 1226) | getFullURL(url) {
    method initSayThanksButton (line 1243) | initSayThanksButton() {

FILE: resource/schemas/NexusPHP/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method _getDownloadUrlByPossibleHrefs (line 20) | _getDownloadUrlByPossibleHrefs() {
    method getDownloadURL (line 44) | getDownloadURL() {
    method getTitle (line 85) | getTitle() {
    method getIMDbId (line 97) | getIMDbId() {

FILE: resource/schemas/NexusPHP/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 31) | getResult() {
    method getFieldValue (line 234) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {
    method getTime (line 263) | getTime(cell) {
    method _parseTime (line 279) | _parseTime(timeString) {
    method getTitle (line 311) | getTitle(row, cells, fieldIndex) {
    method getIMDbId (line 348) | getIMDbId(row)
    method getSubTitle (line 375) | getSubTitle(title, row) {
    method getDownloadLink (line 444) | getDownloadLink(row, link) {
    method getCategory (line 481) | getCategory(cell) {

FILE: resource/schemas/NexusPHP/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 84) | confirmWhenExceedSize() {
    method getDroperURL (line 96) | getDroperURL(url) {

FILE: resource/schemas/TNode/common.js
  class Common (line 2) | class Common {
    method constructor (line 3) | constructor() {
    method t (line 17) | t(key, options) {
    method initFreeSpaceButton (line 24) | initFreeSpaceButton() {
    method initDetailButtons (line 58) | initDetailButtons() {
    method initListButtons (line 82) | initListButtons(checkPasskey = false) {
    method addSaveAllTorrentFilesButton (line 206) | addSaveAllTorrentFilesButton(checkPasskey) {
    method checkPermissions (line 247) | checkPermissions(permissions) {
    method sendTorrentToDefaultClient (line 255) | sendTorrentToDefaultClient(option, showNotice = true) {
    method hideNotice (line 317) | hideNotice(notice) {
    method sendTorrentToClient (line 330) | sendTorrentToClient(options, showNotice = true) {
    method downloadFromDroper (line 390) | downloadFromDroper(data, callback) {
    method isNexusPHP (line 418) | isNexusPHP() {
    method getDroperURL (line 426) | getDroperURL(url) {
    method call (line 450) | call(action, data) {
    method addSendTorrentToClientButton (line 466) | addSendTorrentToClientButton() {
    method addSendTorrentToDefaultClientButton (line 523) | addSendTorrentToDefaultClientButton() {
    method addCopyTextToClipboardButton (line 583) | addCopyTextToClipboardButton() {
    method initCollectionButton (line 621) | initCollectionButton() {
    method addToCollectionButton (line 635) | addToCollectionButton() {
    method addRemoveCollectionButton (line 712) | addRemoveCollectionButton(item) {
    method getContentMenusForUrl (line 739) | getContentMenusForUrl(url) {
    method showContentMenusForUrl (line 818) | showContentMenusForUrl(options, event, success, error) {
    method checkSize (line 875) | checkSize(doms) {
    method getTotalSize (line 906) | getTotalSize(source) {
    method getSize (line 919) | getSize(size) {
    method confirmSize (line 948) | confirmSize(doms) {
    method startDownloadURLs (line 970) | startDownloadURLs(success, error, downloadOptions) {
    method downloadURLsInBackground (line 1016) | downloadURLsInBackground(urls, callback, downloadOptions) {
    method downloadURLs (line 1058) | downloadURLs(urls, count, callback, downloadOptions) {
    method showStatusMessage (line 1123) | showStatusMessage(msg) {
    method clone (line 1140) | clone(source) {
    method showAllContentMenus (line 1148) | showAllContentMenus(event, success, error) {
    method getFullURL (line 1226) | getFullURL(url) {
    method initSayThanksButton (line 1243) | initSayThanksButton() {

FILE: resource/schemas/TNode/details.js
  class App (line 3) | class App extends window.TNodeCommon {
    method init (line 4) | init() {
    method initButtons (line 10) | initButtons() {
    method getDownloadURL (line 14) | getDownloadURL() {
    method getTitle (line 22) | getTitle() {
    method getIMDbId (line 27) | getIMDbId() {

FILE: resource/schemas/TNode/getSearchResult.js
  class Parser (line 40) | class Parser {
    method constructor (line 45) | constructor(site, tagsAndCategories = []) {
    method getResult (line 62) | getResult(response) {

FILE: resource/schemas/TNode/torrents.js
  class App (line 3) | class App extends window.TNodeCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 58) | confirmWhenExceedSize() {
    method getDroperURL (line 70) | getDroperURL(url) {

FILE: resource/schemas/UNIT3D/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method getIMDbId (line 39) | getIMDbId() {

FILE: resource/schemas/UNIT3D/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 19) | getResult() {
    method getSubTitle (line 216) | getSubTitle(title, row) {
    method getCategory (line 228) | getCategory(cell) {
    method getFieldValue (line 239) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {

FILE: resource/schemas/UNIT3D/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 53) | confirmWhenExceedSize() {

FILE: resource/schemas/UNIT3D/userTorrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 13) | initButtons() {
    method getDownloadURLs (line 20) | getDownloadURLs() {

FILE: resource/sites/animebytes.tv/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 34) | getResult() {

FILE: resource/sites/animebytes.tv/userTorrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {

FILE: resource/sites/anthelion.me/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method start (line 16) | start() {
    method getResult (line 41) | getResult() {
    method getAuthKey (line 133) | getAuthKey() {

FILE: resource/sites/asiancinema.me/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getTags (line 250) | getTags(row, selectors) {
    method getSubTitle (line 273) | getSubTitle(title, row) {
    method getCategory (line 281) | getCategory(cell) {
    method getFieldValue (line 292) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {

FILE: resource/sites/beyond-hd.me/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getSubTitle (line 237) | getSubTitle(title, row) {
    method getCategory (line 245) | getCategory(cell) {
    method getFieldValue (line 256) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {

FILE: resource/sites/bibliotik.me/getUserSeedingTorrents.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor(options, dataURL) {
    method done (line 22) | done() {
    method parse (line 29) | parse() {
    method getPageInfo (line 58) | getPageInfo() {
    method load (line 77) | load() {

FILE: resource/sites/bitpt.cn/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 21) | getDownloadURL() {
    method getTitle (line 49) | getTitle() {

FILE: resource/sites/bitpt.cn/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 27) | getResult() {
    method getFieldValue (line 140) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {
    method getFullURL (line 169) | getFullURL(url) {
    method getTitle (line 184) | getTitle(row, cells, fieldIndex) {

FILE: resource/sites/bitpt.cn/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 51) | confirmWhenExceedSize() {

FILE: resource/sites/broadcasthe.net/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 28) | getResult() {
    method getTime (line 166) | getTime(timeStr) {
    method getMilliseconds (line 184) | getMilliseconds(num, unit) {

FILE: resource/sites/ccfbits.org/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method getDownloadURL (line 35) | getDownloadURL(id) {
    method call (line 49) | call(action, data) {
    method downloadFromDroper (line 67) | downloadFromDroper(data, callback) {
    method getDroperURL (line 103) | getDroperURL(url) {
    method confirmWhenExceedSize (line 114) | confirmWhenExceedSize() {

FILE: resource/sites/ccfbits.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 43) | showTorrentSize() {
    method getTitle (line 57) | getTitle() {

FILE: resource/sites/ccfbits.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 23) | getResult() {
    method getCategory (line 139) | getCategory(cell) {

FILE: resource/sites/cinemageddon.net/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 49) | confirmWhenExceedSize() {
    method getDroperURL (line 61) | getDroperURL(url) {

FILE: resource/sites/cinemageddon.net/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 34) | showTorrentSize() {
    method getTitle (line 51) | getTitle() {

FILE: resource/sites/cinemageddon.net/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 36) | getResult() {
    method getTime (line 145) | getTime(cell) {
    method getCategory (line 156) | getCategory(cell) {
    method getTags (line 179) | getTags(row, selectors) {

FILE: resource/sites/cnlang.org/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 34) | done() {
    method parse (line 41) | parse() {
    method getPageInfo (line 70) | getPageInfo() {
    method load (line 88) | load() {

FILE: resource/sites/femdomcult.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method start (line 16) | start() {
    method getResult (line 41) | getResult() {
    method getAuthKey (line 139) | getAuthKey() {

FILE: resource/sites/filelist.io/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method getDownloadURL (line 43) | getDownloadURL(id) {
    method call (line 53) | call(action, data) {
    method downloadFromDroper (line 71) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 97) | confirmWhenExceedSize() {

FILE: resource/sites/filelist.io/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 44) | showTorrentSize() {
    method getTitle (line 58) | getTitle() {

FILE: resource/sites/filelist.io/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 27) | getResult() {
    method getCategory (line 155) | getCategory(cell) {

FILE: resource/sites/gay-torrents.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 14) | getResult() {
    method getTags (line 72) | getTags(row, selectors) {

FILE: resource/sites/gazellegames.net/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 39) | getResult() {
    method getTags (line 184) | getTags(row, selectors) {

FILE: resource/sites/greatposterwall.com/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method start (line 16) | start() {
    method getResult (line 41) | getResult() {
    method getAuthKey (line 139) | getAuthKey() {

FILE: resource/sites/hd-space.org/details.js
  class App (line 5) | class App extends window.NexusPHPCommon {
    method init (line 6) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURL (line 22) | getDownloadURL() {
    method showTorrentSize (line 35) | showTorrentSize() {
    method getTitle (line 46) | getTitle() {
    method init (line 53) | init() {
    method initButtons (line 64) | initButtons() {
    method getDownloadURLs (line 71) | getDownloadURLs() {
    method confirmWhenExceedSize (line 98) | confirmWhenExceedSize() {
  class App (line 52) | class App extends window.NexusPHPCommon {
    method init (line 6) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURL (line 22) | getDownloadURL() {
    method showTorrentSize (line 35) | showTorrentSize() {
    method getTitle (line 46) | getTitle() {
    method init (line 53) | init() {
    method initButtons (line 64) | initButtons() {
    method getDownloadURLs (line 71) | getDownloadURLs() {
    method confirmWhenExceedSize (line 98) | confirmWhenExceedSize() {

FILE: resource/sites/hd-torrents.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 33) | showTorrentSize() {
    method getTitle (line 44) | getTitle() {

FILE: resource/sites/hd-torrents.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 23) | getResult() {
    method getCategory (line 116) | getCategory(cell) {

FILE: resource/sites/hd-torrents.org/getUserSeedingTorrents.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor(options, dataURL) {
    method done (line 21) | done() {
    method parse (line 28) | parse() {
    method getPageInfo (line 56) | getPageInfo() {
    method load (line 75) | load() {

FILE: resource/sites/hd-torrents.org/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 62) | getDownloadURLs() {
    method confirmWhenExceedSize (line 89) | confirmWhenExceedSize() {

FILE: resource/sites/hdbits.org/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method isNexusPHP (line 11) | isNexusPHP() {//want use same code
    method initButtons (line 18) | initButtons() {
    method getDownloadURLs (line 25) | getDownloadURLs() {
    method call (line 52) | call(action, data) {
    method getDroperURL (line 65) | getDroperURL(url) {
    method downloadFromDroper (line 104) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 128) | confirmWhenExceedSize() {

FILE: resource/sites/hdbits.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method isNexusPHP (line 10) | isNexusPHP() {//want use same code
    method initButtons (line 17) | initButtons() {
    method getDownloadURL (line 25) | getDownloadURL() {
    method showTorrentSize (line 40) | showTorrentSize() {
    method getTitle (line 54) | getTitle() {

FILE: resource/sites/hdbits.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 22) | getResult() {
    method getCategory (line 140) | getCategory(cell) {
    method getCategoryName (line 155) | getCategoryName(id) {

FILE: resource/sites/hdroute.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method getTitle (line 35) | getTitle() {

FILE: resource/sites/hdroute.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 23) | getResult() {
    method getTorrentCount (line 87) | getTorrentCount(text) {
    method getTime (line 95) | getTime(el) {

FILE: resource/sites/hdroute.org/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 49) | confirmWhenExceedSize() {

FILE: resource/sites/iptorrents.com/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {

FILE: resource/sites/iptorrents.com/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getTime (line 204) | getTime(row) {
    method getSubTitle (line 222) | getSubTitle(title, row) {
    method getCategory (line 230) | getCategory(cell) {

FILE: resource/sites/iptorrents.com/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 49) | confirmWhenExceedSize() {

FILE: resource/sites/jpopsuki.eu/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 39) | getResult() {
    method getCategory (line 234) | getCategory(cell) {

FILE: resource/sites/jpopsuki.eu/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 33) | done() {
    method parse (line 40) | parse() {
    method getPageInfo (line 68) | getPageInfo() {
    method load (line 87) | load() {

FILE: resource/sites/jpopsuki.eu/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 47) | confirmWhenExceedSize() {
    method downloadFromDroper (line 60) | downloadFromDroper(data, callback) {

FILE: resource/sites/jptv.club/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getTags (line 250) | getTags(row, selectors) {
    method getSubTitle (line 273) | getSubTitle(title, row) {
    method getCategory (line 281) | getCategory(cell) {
    method getFieldValue (line 292) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {

FILE: resource/sites/karagarga.in/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 49) | confirmWhenExceedSize() {
    method getDroperURL (line 61) | getDroperURL(url) {

FILE: resource/sites/karagarga.in/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 34) | showTorrentSize() {
    method getTitle (line 51) | getTitle() {

FILE: resource/sites/karagarga.in/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 27) | getResult() {
    method getCategory (line 161) | getCategory(cell) {
    method getTags (line 184) | getTags(row, selectors) {

FILE: resource/sites/kp.m-team.cc/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 34) | done() {
    method parse (line 41) | parse() {
    method getPageInfo (line 70) | getPageInfo() {
    method load (line 89) | load() {

FILE: resource/sites/nebulance.io/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 40) | getResult() {
    method getCategory (line 196) | getCategory(link) {
    method getCategoryName (line 220) | getCategoryName(id) {

FILE: resource/sites/npupt.com/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 26) | getResult() {
    method getTags (line 126) | getTags(row, selectors) {

FILE: resource/sites/passthepopcorn.me/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 31) | getResult() {

FILE: resource/sites/passthepopcorn.me/getUserSeedingTorrents.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor(options, dataURL) {
    method done (line 21) | done() {
    method parse (line 28) | parse() {
    method getPageInfo (line 56) | getPageInfo() {
    method load (line 75) | load() {

FILE: resource/sites/passthepopcorn.me/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 15) | initButtons() {
    method getDownloadURLs (line 22) | getDownloadURLs() {
    method confirmWhenExceedSize (line 47) | confirmWhenExceedSize() {
    method downloadFromDroper (line 58) | downloadFromDroper(data, callback) {

FILE: resource/sites/pt.zhixing.bjtu.edu.cn/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 21) | getDownloadURL() {
    method getTitle (line 46) | getTitle() {

FILE: resource/sites/pt.zhixing.bjtu.edu.cn/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 27) | getResult() {
    method getFieldValue (line 145) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {
    method getFullURL (line 174) | getFullURL(url) {
    method getTitle (line 189) | getTitle(row, cells, fieldIndex) {

FILE: resource/sites/pt.zhixing.bjtu.edu.cn/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 51) | confirmWhenExceedSize() {

FILE: resource/sites/pussytorrents.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 21) | getDownloadURL() {
    method getTitle (line 38) | getTitle() {

FILE: resource/sites/pussytorrents.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 17) | getResult() {
    method getTime (line 188) | getTime(row) {

FILE: resource/sites/pussytorrents.org/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 33) | done() {
    method parse (line 40) | parse() {
    method getPageInfo (line 77) | getPageInfo() {
    method load (line 97) | load() {

FILE: resource/sites/pussytorrents.org/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 13) | initButtons() {
    method getDownloadURLs (line 20) | getDownloadURLs() {
    method downloadFromDroper (line 40) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 76) | confirmWhenExceedSize() {

FILE: resource/sites/sdbits.org/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method isNexusPHP (line 11) | isNexusPHP() {//want use same code
    method initButtons (line 18) | initButtons() {
    method getDownloadURLs (line 25) | getDownloadURLs() {
    method call (line 52) | call(action, data) {
    method getDroperURL (line 65) | getDroperURL(url) {
    method downloadFromDroper (line 104) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 128) | confirmWhenExceedSize() {

FILE: resource/sites/sdbits.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method isNexusPHP (line 10) | isNexusPHP() {//want use same code
    method initButtons (line 17) | initButtons() {
    method getDownloadURL (line 25) | getDownloadURL() {
    method showTorrentSize (line 40) | showTorrentSize() {
    method getTitle (line 54) | getTitle() {

FILE: resource/sites/sdbits.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 22) | getResult() {
    method getCategory (line 134) | getCategory(cell) {
    method getCategoryName (line 149) | getCategoryName(id) {

FILE: resource/sites/shadowthein.net/browse.js
  class App (line 2) | class App extends window.NexusPHPCommon {
    method init (line 3) | init() {
    method initButtons (line 13) | initButtons() {
    method getDownloadURLs (line 20) | getDownloadURLs() {
    method confirmWhenExceedSize (line 48) | confirmWhenExceedSize() {
    method getDroperURL (line 60) | getDroperURL(url) {

FILE: resource/sites/shadowthein.net/details.js
  class App (line 2) | class App extends window.NexusPHPCommon {
    method init (line 3) | init() {
    method initButtons (line 11) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method showTorrentSize (line 33) | showTorrentSize() {
    method getTitle (line 50) | getTitle() {

FILE: resource/sites/shadowthein.net/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 27) | getResult() {
    method getCategory (line 124) | getCategory(cell) {
    method getTags (line 147) | getTags(row, selectors) {

FILE: resource/sites/sportscult.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 33) | showTorrentSize() {
    method getTitle (line 44) | getTitle() {
    method init (line 52) | init() {
    method initButtons (line 63) | initButtons() {
    method getDownloadURLs (line 70) | getDownloadURLs() {
    method confirmWhenExceedSize (line 97) | confirmWhenExceedSize() {
    method downloadFromDroper (line 111) | downloadFromDroper(data, callback) {
  class App (line 51) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 33) | showTorrentSize() {
    method getTitle (line 44) | getTitle() {
    method init (line 52) | init() {
    method initButtons (line 63) | initButtons() {
    method getDownloadURLs (line 70) | getDownloadURLs() {
    method confirmWhenExceedSize (line 97) | confirmWhenExceedSize() {
    method downloadFromDroper (line 111) | downloadFromDroper(data, callback) {

FILE: resource/sites/totheglory.im/bookmarks.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method call (line 51) | call(action, data) {
    method downloadFromDroper (line 69) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 105) | confirmWhenExceedSize() {

FILE: resource/sites/totheglory.im/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method getDownloadURL (line 35) | getDownloadURL(id) {
    method call (line 49) | call(action, data) {
    method downloadFromDroper (line 67) | downloadFromDroper(data, callback) {
    method getDroperURL (line 103) | getDroperURL(url) {
    method confirmWhenExceedSize (line 114) | confirmWhenExceedSize() {

FILE: resource/sites/totheglory.im/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 20) | getDownloadURL() {
    method showTorrentSize (line 45) | showTorrentSize() {
    method getTitle (line 59) | getTitle() {

FILE: resource/sites/totheglory.im/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 27) | getResult() {
    method getIMDbId (line 167) | getIMDbId(row)
    method getCategory (line 188) | getCategory(cell) {

FILE: resource/sites/uhdbits.org/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 40) | getResult() {
    method getCategory (line 169) | getCategory(link) {
    method getTags (line 194) | getTags(row){
    method getCategoryName (line 234) | getCategoryName(id) {

FILE: resource/sites/uhdbits.org/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 35) | done() {
    method parse (line 42) | parse() {
    method getPageInfo (line 76) | getPageInfo() {
    method load (line 95) | load() {

FILE: resource/sites/world-in-hd.net/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 49) | confirmWhenExceedSize() {
    method getDroperURL (line 59) | getDroperURL(url) {

FILE: resource/sites/world-in-hd.net/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method getTitle (line 36) | getTitle() {

FILE: resource/sites/world-in-hd.net/getSearchResult.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor() {
    method getResult (line 31) | getResult() {
    method getTime (line 138) | getTime(timeStr) {
    method getMilliseconds (line 155) | getMilliseconds(num, unit) {
    method getTags (line 175) | getTags(row, selectors) {

FILE: resource/sites/www.cinematik.net/browse.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 53) | confirmWhenExceedSize() {
    method getDroperURL (line 65) | getDroperURL(url) {

FILE: resource/sites/www.cinematik.net/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method getTitle (line 41) | getTitle() {

FILE: resource/sites/www.cinematik.net/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 28) | getResult() {
    method getCategory (line 138) | getCategory(cell) {

FILE: resource/sites/www.cinematik.net/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 34) | done() {
    method parse (line 41) | parse() {
    method getPageInfo (line 70) | getPageInfo() {
    method load (line 89) | load() {

FILE: resource/sites/www.gaytor.rent/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 14) | getResult() {

FILE: resource/sites/www.haidan.video/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 25) | getResult() {

FILE: resource/sites/www.myanonamouse.net/details.js
  class App (line 4) | class App extends window.NexusPHPCommon {
    method init (line 5) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURL (line 22) | getDownloadURL() {
    method showTorrentSize (line 31) | showTorrentSize() {
    method getTitle (line 49) | getTitle() {

FILE: resource/sites/www.myanonamouse.net/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 27) | getResult() {
    method getFullURL (line 115) | getFullURL(url) {

FILE: resource/sites/www.myanonamouse.net/getUserSeedingTorrents.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor(options) {
    method done (line 16) | done() {
    method load (line 23) | load() {

FILE: resource/sites/www.myanonamouse.net/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 41) | confirmWhenExceedSize() {
    method getSize (line 48) | getSize(size) {

FILE: resource/sites/www.skyey2.com/getUserSeedingTorrents.js
  class Parser (line 14) | class Parser {
    method constructor (line 15) | constructor(options, dataURL) {
    method done (line 34) | done() {
    method parse (line 41) | parse() {
    method getPageInfo (line 70) | getPageInfo() {
    method load (line 88) | load() {

FILE: resource/sites/www.torrentleech.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 21) | getDownloadURL() {
    method getTitle (line 38) | getTitle() {

FILE: resource/sites/www.torrentleech.org/getSearchResult.js
  class Parser (line 2) | class Parser {
    method constructor (line 3) | constructor() {
    method getResult (line 18) | getResult() {
    method getTags (line 68) | getTags(item) {

FILE: resource/sites/www.torrentleech.org/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 13) | initButtons() {
    method getDownloadURLs (line 20) | getDownloadURLs() {
    method downloadFromDroper (line 40) | downloadFromDroper(data, callback) {
    method confirmWhenExceedSize (line 76) | confirmWhenExceedSize() {

FILE: resource/sites/www.torrentseeds.org/details.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 12) | initButtons() {
    method getDownloadURL (line 19) | getDownloadURL() {
    method getTitle (line 32) | getTitle() {

FILE: resource/sites/www.torrentseeds.org/getSearchResult.js
  class Parser (line 5) | class Parser {
    method constructor (line 6) | constructor() {
    method getResult (line 28) | getResult() {
    method getFieldValue (line 162) | getFieldValue(row, cells, fieldIndex, fieldName, returnCell) {
    method getFullURL (line 191) | getFullURL(url) {
    method getTitle (line 206) | getTitle(row, cells, fieldIndex) {

FILE: resource/sites/www.torrentseeds.org/torrents.js
  class App (line 3) | class App extends window.NexusPHPCommon {
    method init (line 4) | init() {
    method initButtons (line 14) | initButtons() {
    method getDownloadURLs (line 21) | getDownloadURLs() {
    method confirmWhenExceedSize (line 45) | confirmWhenExceedSize() {

FILE: src/background/collection.ts
  class Collection (line 9) | class Collection {
    method constructor (line 17) | constructor() {
    method load (line 24) | public load(groupId?: string): Promise<any> {
    method updateGroupCount (line 60) | public updateGroupCount() {
    method add (line 78) | public add(newItem: ICollection): Promise<any> {
    method getMoviceInfo (line 117) | private getMoviceInfo(
    method push (line 155) | private push(newItem: ICollection) {
    method update (line 173) | public update(item: ICollection): Promise<any> {
    method updateData (line 213) | private updateData() {
    method delete (line 225) | public delete(item: ICollection): Promise<any> {
    method remove (line 233) | public remove(items: ICollection[]): Promise<any> {
    method clear (line 258) | public clear(): Promise<any> {
    method get (line 271) | public get(link: string): Promise<any> {
    method reset (line 291) | public reset(datas: any): Promise<any> {
    method addGroup (line 319) | public addGroup(newItem: ICollectionGroup): Promise<any> {
    method removeGroup (line 342) | public removeGroup(
    method updateGroup (line 391) | public updateGroup(item: ICollectionGroup): Promise<any> {
    method getGroups (line 412) | public getGroups(): Promise<any> {
    method addToGroup (line 425) | public addToGroup(item: ICollection, groupId: string): Promise<any> {
    method removeFromGroup (line 451) | public removeFromGroup(item: ICollection, groupId: string): Promise<an...
    method getAllLinks (line 470) | public getAllLinks() {

FILE: src/background/config.ts
  type Service (line 31) | type Service = PTPlugin;
  class Config (line 36) | class Config {
    method constructor (line 49) | constructor(public service: Service) {
    method reload (line 53) | public reload() {
    method save (line 122) | public save(options?: Options) {
    method getFavicons (line 132) | public getFavicons(): Promise<any> {
    method getFavicon (line 177) | public getFavicon(url: string, reset: boolean = false): Promise<any> {
    method cleaningOptions (line 209) | public cleaningOptions(options: Options): Options {
    method read (line 287) | public read(): Promise<any> {
    method loadConfig (line 305) | private loadConfig(success: any) {
    method resetRunTimeOptions (line 323) | public resetRunTimeOptions(options?: Options) {
    method arrayUnique (line 468) | private arrayUnique(source: any[]) {
    method upgradeSites (line 485) | public upgradeSites() {
    method readUIOptions (line 555) | public readUIOptions(): Promise<any> {
    method saveUIOptions (line 569) | public saveUIOptions(options: UIOptions): Promise<any> {
    method getSystemConfig (line 579) | public getSystemConfig() {
    method getSchemas (line 597) | public getSchemas(): any {
    method addSchema (line 612) | public addSchema(path: string): void {
    method getSites (line 620) | public getSites() {
    method addSite (line 634) | public addSite(path: string): void {
    method getClients (line 642) | public getClients() {
    method addClient (line 657) | public addClient(path: string): void {
    method getContentFromApi (line 669) | public getContentFromApi(api: string): Promise<any> {
    method backupToGoogle (line 694) | public backupToGoogle(): Promise<any> {
    method restoreFromGoogle (line 744) | public restoreFromGoogle(): Promise<any> {
    method getBackupRawData (line 791) | public getBackupRawData(): Promise<any> {
    method createBackupFile (line 847) | public createBackupFile(fileName?: string): Promise<any> {
    method getBackupFileBlob (line 863) | public getBackupFileBlob(): Promise<any> {
    method getAllSiteCookies (line 886) | public getAllSiteCookies(): Promise<any> {
    method getCookiesFromSite (line 917) | public getCookiesFromSite(site: Site): Promise<any> {
    method restoreCookies (line 944) | public restoreCookies(datas: any[]): Promise<any> {
    method setCookies (line 991) | public setCookies(
    method getNewBackupFileName (line 1043) | private getNewBackupFileName(): string {
    method backupToServer (line 1051) | public backupToServer(server: IBackupServer): Promise<any> {
    method restoreFromServer (line 1102) | public restoreFromServer(server: IBackupServer, path: string): Promise...
    method getBackupListFromServer (line 1149) | public getBackupListFromServer(
    method deleteFileFromBackupServer (line 1187) | public deleteFileFromBackupServer(
    method testBackupServerConnectivity (line 1224) | public testBackupServerConnectivity(server: IBackupServer): Promise<an...

FILE: src/background/contextMenus.ts
  type Service (line 19) | type Service = PTPlugin;
  class ContextMenus (line 21) | class ContextMenus {
    method constructor (line 33) | constructor(public service: Service) {
    method initBrowserEvent (line 40) | private initBrowserEvent() {
    method init (line 82) | public init(options: Options) {
    method createPluginIconPopupMenus (line 98) | public createPluginIconPopupMenus() {
    method clear (line 141) | public clear() {
    method clearSiteMenus (line 148) | private clearSiteMenus() {
    method getSiteDocumentUrlPatterns (line 160) | private getSiteDocumentUrlPatterns(site: Site): string[] {
    method createPathMenus (line 184) | private createPathMenus(
    method createSiteMenus (line 219) | public createSiteMenus(host: string) {
    method getSiteUrlPatterns (line 302) | private getSiteUrlPatterns(site: Site): string[] {
    method getSiteSchema (line 321) | private getSiteSchema(site: Site): SiteSchema {
    method sendTorrentToClient (line 340) | private sendTorrentToClient(tabid: number = 0, options: DownloadOption...
    method hideNotice (line 486) | private hideNotice(tabid: number = 0, notice: any) {
    method createSearchMenus (line 501) | private createSearchMenus() {
    method pushMoreSearchMenus (line 599) | private pushMoreSearchMenus(
    method createClientMenus (line 729) | private createClientMenus() {
    method add (line 792) | private add(
    method remove (line 809) | private remove(id: string, callback?: (() => void) | undefined) {
    method getRandomString (line 826) | private getRandomString(
    method getParsedURL (line 847) | private getParsedURL(source: string | any): string | DataResult {
    method getSiteParser (line 883) | private getSiteParser(host: string, name: string): string {
    method getSiteFromURL (line 916) | private getSiteFromURL(source: string) {

FILE: src/background/controller.ts
  type Service (line 32) | type Service = PTPlugin;
  class Controller (line 33) | class Controller {
    method constructor (line 63) | constructor(public service: Service) { }
    method init (line 65) | public init(options: Options) {
    method reset (line 74) | public reset(options: Options) {
    method getSearchResult (line 103) | public getSearchResult(options: any): Promise<any> {
    method abortSearch (line 115) | public abortSearch(options: any): Promise<any> {
    method getDownloadHistory (line 122) | public getDownloadHistory(): Promise<any> {
    method saveDownloadHistory (line 132) | private saveDownloadHistory(
    method removeDownloadHistory (line 148) | public removeDownloadHistory(items: any[]): Promise<any> {
    method clearDownloadHistory (line 155) | public clearDownloadHistory(): Promise<any> {
    method resetDownloadHistory (line 162) | public resetDownloadHistory(datas: any): Promise<any> {
    method sendTorrentToClient (line 170) | public sendTorrentToClient(data: DownloadOptions): Promise<any> {
    method sendTorrentToDefaultClient (line 206) | public sendTorrentToDefaultClient(
    method doDownload (line 253) | private doDownload(
    method downloadFailedRetry (line 399) | private downloadFailedRetry(
    method getSiteFromHost (line 472) | public getSiteFromHost(host: string): Site {
    method getSiteDefaultPath (line 484) | public getSiteDefaultPath(site: Site, clientId: string = ""): string {
    method formatSendResult (line 511) | private formatSendResult(
    method getClient (line 600) | private getClient(clientOptions: any): Promise<any> {
    method initDefaultClient (line 607) | private initDefaultClient() {
    method initSiteDefaultClient (line 627) | private initSiteDefaultClient(hostname: string): Promise<any> {
    method copyTextToClipboard (line 652) | public copyTextToClipboard(text: string = "") {
    method getFreeSpace (line 669) | public getFreeSpace(data: any): Promise<any> {
    method getDefaultClientFreeSpace (line 693) | public getDefaultClientFreeSpace(data: any): Promise<any> {
    method updateOptionsTabId (line 703) | public updateOptionsTabId(id: number) {
    method searchTorrent (line 712) | public searchTorrent(key: string = "", host: string = "") {
    method openOptions (line 729) | public openOptions(path: string = "") {
    method openURL (line 755) | public openURL(url: string = "") {
    method getSiteSchema (line 776) | private getSiteSchema(site: Site): SiteSchema {
    method replaceKeys (line 801) | private replaceKeys(source: string, keys: Dictionary<any>): string {
    method call (line 818) | public call(
    method addContentPage (line 835) | public addContentPage(
    method backupToGoogle (line 854) | public backupToGoogle(): Promise<any> {
    method restoreFromGoogle (line 861) | public restoreFromGoogle(): Promise<any> {
    method clearFromGoogle (line 868) | public clearFromGoogle(): Promise<any> {
    method reloadConfig (line 875) | public reloadConfig(): Promise<any> {
    method getTorrentDataFromURL (line 886) | public getTorrentDataFromURL(options: string | any): Promise<any> {
    method getSiteOptionsFromURL (line 967) | public getSiteOptionsFromURL(url: string): Site | undefined {
    method getUserInfoForAllSite (line 979) | public getUserInfoForAllSite(): Promise<any> {
    method getUserInfo (line 1025) | public getUserInfo(site: Site): Promise<any> {
    method abortGetUserInfo (line 1029) | public abortGetUserInfo(site: Site): Promise<any> {
    method getBase64FromImageUrl (line 1037) | public getBase64FromImageUrl(url: string): Promise<any> {
    method getUserHistoryData (line 1077) | public getUserHistoryData(host: string): Promise<any> {
    method resetUserDatas (line 1084) | public resetUserDatas(datas: any) {
    method getMovieInfos (line 1095) | public getMovieInfos(key: string): Promise<any> {
    method getMovieRatings (line 1103) | public getMovieRatings(IMDbId: string): Promise<any> {
    method getIMDbIdFromDouban (line 1111) | public getIMDbIdFromDouban(doubanId: string): Promise<any> {
    method queryMovieInfoFromDouban (line 1120) | public queryMovieInfoFromDouban(options: any): Promise<any> {
    method addBrowserDownloads (line 1131) | public addBrowserDownloads(options: any): Promise<any> {
    method getCurrentLanguageResource (line 1164) | public getCurrentLanguageResource(parentKey: string): Promise<any> {
    method addLanguage (line 1184) | public addLanguage(resource: i18nResource): Promise<any> {
    method replaceLanguage (line 1188) | public replaceLanguage(resource: i18nResource): Promise<any> {
    method backupToServer (line 1192) | public backupToServer(server: IBackupServer): Promise<any> {
    method getBackupListFromServer (line 1196) | public getBackupListFromServer(options: any = {}): Promise<any> {
    method restoreFromServer (line 1202) | public restoreFromServer(options: any = {}): Promise<any> {
    method deleteFileFromBackupServer (line 1206) | public deleteFileFromBackupServer(options: any = {}): Promise<any> {
    method sendTorrentsInBackground (line 1213) | public sendTorrentsInBackground(items: any[] = []): Promise<any> {
    method createBackupFile (line 1224) | public createBackupFile(fileName: string): Promise<any> {
    method addTorrentToCollection (line 1228) | public addTorrentToCollection(data: any): Promise<any> {
    method getTorrentCollections (line 1235) | public getTorrentCollections(groupId?: string): Promise<any> {
    method deleteTorrentFromCollention (line 1239) | public deleteTorrentFromCollention(data: any): Promise<any> {
    method clearTorrentCollention (line 1246) | public clearTorrentCollention(): Promise<any> {
    method getTorrentCollention (line 1250) | public getTorrentCollention(link: string): Promise<any> {
    method getSiteSelectorConfig (line 1254) | public getSiteSelectorConfig(options: any): Promise<any> {
    method resetTorrentCollections (line 1265) | public resetTorrentCollections(items: any): Promise<any> {
    method getTorrentCollectionGroups (line 1269) | public getTorrentCollectionGroups(): Promise<any> {
    method addTorrentCollectionGroup (line 1273) | public addTorrentCollectionGroup(data: any): Promise<any> {
    method addTorrentCollectionToGroup (line 1277) | public addTorrentCollectionToGroup(options: any): Promise<any> {
    method updateTorrentCollectionGroup (line 1281) | public updateTorrentCollectionGroup(data: any): Promise<any> {
    method removeTorrentCollectionFromGroup (line 1285) | public removeTorrentCollectionFromGroup(options: any): Promise<any> {
    method removeTorrentCollectionGroup (line 1292) | public removeTorrentCollectionGroup(data: any): Promise<any> {
    method updateTorrentCollention (line 1296) | public updateTorrentCollention(data: any): Promise<any> {
    method getAllTorrentCollectionLinks (line 1300) | public getAllTorrentCollectionLinks(): Promise<any> {
    method restoreCookies (line 1311) | public restoreCookies(data: any): Promise<any> {
    method resetFavicons (line 1315) | public resetFavicons(): Promise<any> {
    method resetFavicon (line 1320) | public resetFavicon(url: string): Promise<any> {
    method getBackupRawData (line 1324) | public getBackupRawData(): Promise<any> {
    method testBackupServerConnectivity (line 1328) | public testBackupServerConnectivity(options: any): Promise<any> {
    method createSearchResultSnapshot (line 1332) | public createSearchResultSnapshot(options: any): Promise<any> {
    method getSearchResultSnapshot (line 1336) | public getSearchResultSnapshot(id: string): Promise<any> {
    method loadSearchResultSnapshot (line 1340) | public loadSearchResultSnapshot(): Promise<any> {
    method removeSearchResultSnapshot (line 1344) | public removeSearchResultSnapshot(options: any): Promise<any> {
    method clearSearchResultSnapshot (line 1348) | public clearSearchResultSnapshot(): Promise<any> {
    method resetSearchResultSnapshot (line 1352) | public resetSearchResultSnapshot(datas: any): Promise<any> {
    method createKeepUploadTask (line 1356) | public createKeepUploadTask(options: any): Promise<any> {
    method getKeepUploadTask (line 1360) | public getKeepUploadTask(id: string): Promise<any> {
    method loadKeepUploadTask (line 1364) | public loadKeepUploadTask(): Promise<any> {
    method removeKeepUploadTask (line 1368) | public removeKeepUploadTask(options: any): Promise<any> {
    method clearKeepUploadTask (line 1372) | public clearKeepUploadTask(): Promise<any> {
    method resetKeepUploadTask (line 1376) | public resetKeepUploadTask(datas: any): Promise<any> {
    method updateKeepUploadTask (line 1380) | public updateKeepUploadTask(options: any): Promise<any> {
    method updateDebuggerTabId (line 1384) | public updateDebuggerTabId(id: number): Promise<any> {
    method pushDebugMsg (line 1394) | public pushDebugMsg(msg: any): Promise<any> {
    method getTopSearches (line 1416) | public getTopSearches(count: number = 9): Promise<any> {

FILE: src/background/downloadHistory.ts
  class DownloadHistory (line 7) | class DownloadHistory {
    method constructor (line 11) | constructor() {
    method load (line 18) | public load(): Promise<any> {
    method add (line 33) | public add(
    method remove (line 66) | public remove(items: any[]): Promise<any> {
    method clear (line 87) | public clear(): Promise<any> {
    method reset (line 99) | public reset(datas: any[]): Promise<any> {
    method updateData (line 117) | private updateData() {

FILE: src/background/downloadQuene.ts
  type Service (line 4) | type Service = PTPlugin;
  class DownloadQuene (line 6) | class DownloadQuene {
    method constructor (line 13) | constructor(public service: Service) {}
    method add (line 19) | public add(options: DownloadOptions | DownloadOptions[]) {
    method run (line 32) | public run() {

FILE: src/background/i18n.ts
  class i18nService (line 6) | class i18nService {
    method constructor (line 9) | constructor(public service: PTPlugin) {}
    method init (line 10) | public init() {
    method reset (line 24) | public reset(langCode: string): Promise<any> {
    method t (line 63) | public t(key: any, options?: any): string {
    method add (line 67) | public add(resource: i18nResource): Promise<any> {
    method replace (line 95) | public replace(resource: i18nResource): Promise<any> {

FILE: src/background/infoParser.ts
  class InfoParser (line 9) | class InfoParser {
    method constructor (line 10) | constructor(public service?: any) { }
    method getResult (line 17) | getResult(content: any, rule: any): Dictionary<any> {
    method debug (line 36) | private debug(...msg: any[]) {
    method getFieldData (line 50) | getFieldData(content: any, config: any, rule: any) {
    method getTotalSize (line 165) | getTotalSize(datas: string[]) {
    method formatIMDbId (line 213) | formatIMDbId(imdbId: string) {

FILE: src/background/keepUploadTask.ts
  type IDetail (line 5) | interface IDetail extends IKeepUploadTask {}
  class KeepUploadTask (line 9) | class KeepUploadTask {
    method constructor (line 15) | constructor() {
    method load (line 22) | public load(): Promise<any> {
    method add (line 48) | public add(newItem: IDetail): Promise<any> {
    method update (line 68) | public update(item: IDetail): Promise<any> {
    method updateData (line 84) | private updateData() {
    method get (line 94) | public get(id: string): Promise<any> {
    method delete (line 113) | public delete(item: IDetail): Promise<any> {
    method remove (line 121) | public remove(items: IDetail[]): Promise<any> {
    method clear (line 142) | public clear(): Promise<any> {
    method reset (line 154) | public reset(datas: any[]): Promise<any> {

FILE: src/background/omnibox.ts
  type Service (line 4) | type Service = PTPlugin;
  class OmniBox (line 9) | class OmniBox {
    method constructor (line 11) | constructor(public service: Service) {
    method init (line 15) | public init() {}
    method initEvents (line 17) | private initEvents() {

FILE: src/background/pageParser.ts
  class PageParser (line 16) | class PageParser {
    method constructor (line 30) | constructor(
    method getCache (line 85) | private getCache() {
    method setCache (line 106) | private setCache() {
    method getInfos (line 119) | public getInfos(): Promise<any> {
    method runParser (line 185) | public runParser(resolve?: any, reject?: any): boolean {

FILE: src/background/plugins/OWSS.ts
  class OWSS (line 8) | class OWSS {
    method constructor (line 10) | constructor(public options: IBackupServer) {
    method request (line 23) | private request(
    method create (line 54) | public create(): Promise<any> {
    method add (line 74) | public add(formData: FormData): Promise<any> {
    method get (line 99) | public get(path: string): Promise<any> {
    method delete (line 124) | public delete(path: string): Promise<any> {
    method list (line 147) | public list(options: any = {}): Promise<any> {
    method ping (line 166) | public ping(): Promise<any> {

FILE: src/background/plugins/WebDAV.ts
  class WebDAV (line 8) | class WebDAV {
    method constructor (line 10) | constructor(public options: IBackupServer) {
    method initServer (line 17) | private initServer() {
    method list (line 29) | public list(options: any = {}): Promise<any> {
    method get (line 88) | public get(path: string): Promise<any> {
    method add (line 105) | public add(formData: FormData): Promise<any> {
    method delete (line 126) | public delete(path: string): Promise<any> {
    method ping (line 146) | public ping(): Promise<any> {

FILE: src/background/searchResultSnapshot.ts
  class SearchResultSnapshot (line 8) | class SearchResultSnapshot {
    method constructor (line 14) | constructor() {
    method load (line 21) | public load(): Promise<any> {
    method add (line 47) | public add(newItem: ISearchResultSnapshot): Promise<any> {
    method updateData (line 63) | private updateData() {
    method get (line 73) | public get(id: string): Promise<any> {
    method delete (line 92) | public delete(item: ISearchResultSnapshot): Promise<any> {
    method remove (line 100) | public remove(items: ISearchResultSnapshot[]): Promise<any> {
    method clear (line 121) | public clear(): Promise<any> {
    method reset (line 133) | public reset(datas: any[]): Promise<any> {

FILE: src/background/searcher.ts
  type SearchConfig (line 28) | type SearchConfig = {
  type ESearchResultParseStatus (line 38) | enum ESearchResultParseStatus {
  class Searcher (line 53) | class Searcher {
    method constructor (line 65) | constructor(public service: PTPlugin) { }
    method searchTorrent (line 73) | public searchTorrent(
    method getSearchResult (line 466) | public getSearchResult(
    method addSearchRequestQueue (line 526) | public addSearchRequestQueue(
    method getErrorMessage (line 776) | public getErrorMessage(
    method abortSearch (line 795) | public abortSearch(site: Site, key: string = ""): Promise<any> {
    method getSiteSchema (line 869) | getSiteSchema(site: Site): SiteSchema {
    method replaceKeys (line 892) | replaceKeys(
    method getFieldValue (line 919) | public getFieldValue(
    method getCategoryById (line 949) | public getCategoryById(site: Site, page: string, id: string) {
    method cfDecodeEmail (line 974) | public cfDecodeEmail(encodedString: string) {
    method getRowTags (line 991) | public getRowTags(site: Site, row: JQuery<HTMLElement>) {

FILE: src/background/service.ts
  class PTPlugin (line 30) | class PTPlugin {
    method constructor (line 63) | constructor(localMode: boolean = false) {
    method requestMessage (line 81) | public requestMessage(request: Request, sender?: any): Promise<any> {
    method initConfig (line 301) | private initConfig() {
    method initI18n (line 321) | private initI18n() {
    method readConfig (line 335) | private readConfig(): Promise<any> {
    method saveConfig (line 351) | public saveConfig() {
    method initUserData (line 358) | private initUserData() {
    method resetAutoRefreshUserDataTimer (line 367) | private resetAutoRefreshUserDataTimer(isInit: boolean = false) {
    method getNextTime (line 462) | private getNextTime(addDays: number = 1) {
    method saveUserData (line 474) | public saveUserData() {
    method init (line 482) | public init() {
    method debug (line 493) | public debug(...msgs: any[]) {
    method writeLog (line 499) | public writeLog(msg: LogItem) {
    method writeErrorLog (line 503) | public writeErrorLog(msg: any) {
    method initBrowserEvent (line 514) | private initBrowserEvent() {
    method upgrade (line 558) | public upgrade() {
    method getSiteParser (line 571) | public getSiteParser(host: string, name: string): string {
    method getSiteSelector (line 603) | public getSiteSelector(
    method clone (line 654) | public clone(source: any) {
    method checkPermissions (line 662) | public checkPermissions(permissions: string[]): Promise<any> {
    method requestPermissions (line 670) | public requestPermissions(permissions: string[]): Promise<any> {

FILE: src/background/site.ts
  class SiteService (line 4) | class SiteService {
    method constructor (line 9) | constructor(public options: Site, public systemOptions: Options) {
    method schema (line 13) | public get schema(): SiteSchema {
    method mergeOptions (line 32) | private mergeOptions() {

FILE: src/background/syncStorage.ts
  class SyncStorage (line 4) | class SyncStorage {
    method isArray (line 6) | private isArray(o: any) {
    method clear (line 13) | public clear(): Promise<any> {
    method _set (line 36) | private _set(
    method _get (line 97) | private _get(
    method set (line 173) | public set(key: string, value: any): Promise<any> {
    method get (line 201) | public get(key: string): Promise<any> {

FILE: src/background/user.ts
  type Service (line 15) | type Service = PTPlugin;
  class User (line 17) | class User {
    method constructor (line 25) | constructor(public service: Service) {}
    method refreshUserData (line 31) | public refreshUserData(failedOnly: boolean = false): Promise<any> {
    method updateStatus (line 60) | updateStatus(site: Site, userInfo: UserInfo) {
    method getSiteURL (line 65) | private getSiteURL(site: Site) {
    method getUserInfo (line 78) | public getUserInfo(site: Site, returnResolve: boolean = false): Promis...
    method getMoreInfos (line 204) | public getMoreInfos(site: Site, userInfo: UserInfo): Promise<any> {
    method getInfos (line 267) | public getInfos(
    method runParser (line 377) | public runParser(
    method addQueue (line 421) | public addQueue(host: string, url: string, request: any) {
    method checkQueue (line 427) | public checkQueue(host: string, url: string): boolean {
    method removeQueue (line 432) | public removeQueue(host: string, url: string) {
    method abortGetUserInfo (line 445) | public abortGetUserInfo(site: Site): Promise<any> {
    method getCookie (line 483) | public getCookie(site: Site, needle: String): Promise<any> {

FILE: src/background/userData.ts
  class UserData (line 12) | class UserData {
    method constructor (line 17) | constructor(public service: PTPlugin) {
    method load (line 24) | public load(): Promise<any> {
    method get (line 39) | public get(host: string, range: EUserDataRange = EUserDataRange.latest) {
    method update (line 67) | public update(site: Site, data: UserInfo) {
    method clear (line 98) | public clear(): Promise<any> {
    method upgrade (line 111) | public upgrade(): Promise<any> {
    method reset (line 159) | public reset(datas: any) {

FILE: src/content/index.ts
  type Window (line 27) | interface Window {
  class PTPContent (line 35) | class PTPContent {
    method constructor (line 87) | constructor() {
    method readConfig (line 95) | private readConfig() {
    method init (line 102) | private init() {
    method initI18n (line 115) | private initI18n() {
    method getSiteFromHost (line 142) | public getSiteFromHost(host: string) {
    method initPages (line 168) | private initPages() {
    method initSiteConfig (line 179) | private initSiteConfig(): Promise<any> {
    method initPlugins (line 227) | private initPlugins() {
    method call (line 389) | public call(action: EAction, data?: any): Promise<any> {
    method initButtonBar (line 425) | private initButtonBar() {
    method initButtonBarPosition (line 465) | private initButtonBarPosition() {
    method resetButtonBarPosition (line 497) | private resetButtonBarPosition() {
    method saveButtonBarPosition (line 508) | private saveButtonBarPosition(position: any) {
    method addButton (line 520) | public addButton(options: ButtonOption) {
    method removeButton (line 613) | public removeButton(key: string) {
    method recalculateButtonBarPosition (line 635) | public recalculateButtonBarPosition() {
    method showNotice (line 654) | public showNotice(options: NoticeOptions | string) {
    method hideMessage (line 693) | public hideMessage(id: string) {
    method getSiteDefaultPath (line 704) | public getSiteDefaultPath(clientId: string = ""): string {
    method getClientOptions (line 731) | public getClientOptions(clientId: string = "") {
    method initDroper (line 747) | public initDroper() {
    method addDroper (line 867) | public addDroper(
    method hideDroper (line 929) | private hideDroper() {
    method showDroper (line 933) | private showDroper() {
    method initBrowserEvent (line 937) | private initBrowserEvent() {
    method checkLocationURL (line 966) | public checkLocationURL() {
    method initPageSelector (line 976) | private initPageSelector(): Promise<any> {
    method getFieldValue (line 1010) | public getFieldValue(fieldName: string = "", content: any = $("body")) {

FILE: src/debugger/index.ts
  class Debugger (line 11) | class Debugger {
    method constructor (line 14) | constructor() {
    method initEvents (line 26) | private initEvents() {
    method add (line 43) | private add(msg: any) {

FILE: src/interface/common.ts
  type ContextMenuRules (line 22) | interface ContextMenuRules {
  type DownloadClient (line 28) | interface DownloadClient {
  type ButtonOption (line 44) | interface ButtonOption {
  type SystemOptions (line 54) | interface SystemOptions {
  type Dictionary (line 61) | type Dictionary<T> = { [key: string]: T };
  type SearchOptions (line 62) | interface SearchOptions {
  type IApiKey (line 70) | interface IApiKey {
  type IBackupServer (line 78) | interface IBackupServer {
  type Options (line 93) | interface Options {
  type BeforeSearching (line 172) | interface BeforeSearching {
  type Plugin (line 181) | interface Plugin {
  type SiteSchema (line 192) | interface SiteSchema {
  type SiteCategory (line 210) | interface SiteCategory {
  type SiteCategories (line 215) | interface SiteCategories {
  type Site (line 225) | interface Site {
  type LevelRequirement (line 277) | interface LevelRequirement {
  type Request (line 316) | interface Request {
  type IRequest (line 321) | interface IRequest extends Request {}
  type NoticeOptions (line 323) | interface NoticeOptions {
  type CacheType (line 332) | interface CacheType {
  type DownloadOptions (line 339) | interface DownloadOptions {
  type DataResult (line 355) | interface DataResult {
  type LogItem (line 366) | interface LogItem {
  type SearchResultItemTag (line 375) | interface SearchResultItemTag {
  type SearchResultItemCategory (line 380) | interface SearchResultItemCategory {
  type SearchResultItem (line 388) | interface SearchResultItem {
  type SearchEntryConfigArea (line 418) | interface SearchEntryConfigArea {
  type ISearchFieldIndex (line 429) | interface ISearchFieldIndex {
  type IPageSelector (line 455) | interface IPageSelector {
  type SearchEntryConfig (line 481) | interface SearchEntryConfig {
  type SearchEntry (line 521) | interface SearchEntry extends SearchEntryConfig {
  type UIOptions (line 540) | interface UIOptions {
  type SearchSolution (line 546) | interface SearchSolution {
  type SearchSolutionRange (line 552) | interface SearchSolutionRange {
  type UserInfo (line 561) | interface UserInfo {
  type i18nResource (line 633) | type i18nResource = {
  type ISearchPayload (line 641) | interface ISearchPayload {
  type IHashData (line 649) | interface IHashData {
  type IManifest (line 655) | interface IManifest {
  type ICollection (line 666) | interface ICollection {
  type ICollectionGroup (line 694) | interface ICollectionGroup {
  constant BASE_COLORS (line 704) | const BASE_COLORS = [
  constant BASE_TAG_COLORS (line 730) | const BASE_TAG_COLORS: Dictionary<any> = {
  type ICookies (line 757) | interface ICookies {
  type IURL (line 763) | interface IURL {
  type IWorkingStatusItem (line 776) | interface IWorkingStatusItem {
  type ISearchResultSnapshot (line 782) | interface ISearchResultSnapshot {
  type IBackupRawData (line 790) | interface IBackupRawData {
  type IKeepUploadTask (line 800) | interface IKeepUploadTask {
  type ISiteIcon (line 808) | interface ISiteIcon {

FILE: src/interface/enum.ts
  type ESizeUnit (line 4) | enum ESizeUnit {
  type ERequestType (line 17) | enum ERequestType {
  type ERequestResultType (line 25) | enum ERequestResultType {
  type EDownloadClientType (line 35) | enum EDownloadClientType {
  type EButtonType (line 47) | enum EButtonType {
  type ERequestMethod (line 57) | enum ERequestMethod {
  type EAction (line 65) | enum EAction {
  type EStorageType (line 263) | enum EStorageType {
  type EConfigKey (line 271) | enum EConfigKey {
  type EDataResultType (line 286) | enum EDataResultType {
  type EModule (line 297) | enum EModule {
  type ELogEvent (line 308) | enum ELogEvent {
  type EPaginationKey (line 313) | enum EPaginationKey {
  type EViewKey (line 318) | enum EViewKey {
  type EUserDataRange (line 327) | enum EUserDataRange {
  type EUserDataRequestStatus (line 336) | enum EUserDataRequestStatus {
  type ECommonKey (line 346) | enum ECommonKey {
  type EInstallType (line 355) | enum EInstallType {
  type EBeforeSearchingItemSearchMode (line 364) | enum EBeforeSearchingItemSearchMode {
  type ETorrentStatus (line 370) | enum ETorrentStatus {
  type EBackupServerType (line 384) | enum EBackupServerType {
  type EPluginPosition (line 392) | enum EPluginPosition {
  type EWikiLink (line 400) | enum EWikiLink {
  type ERestoreContent (line 407) | enum ERestoreContent {
  type EBrowserType (line 418) | enum EBrowserType {
  type EWorkingStatus (line 423) | enum EWorkingStatus {
  type EResourceOrderBy (line 429) | enum EResourceOrderBy {
  type EResourceOrderMode (line 435) | enum EResourceOrderMode {
  type EEncryptMode (line 441) | enum EEncryptMode {
  type ERestoreError (line 445) | enum ERestoreError {

FILE: src/options/i18n.ts
  class i18nService (line 13) | class i18nService {
    method constructor (line 28) | constructor() {
    method init (line 38) | public init(langCode: string): Promise<any> {
    method loadConfig (line 62) | public loadConfig(): Promise<any> {
    method loadLangResource (line 79) | public loadLangResource(langCode: string): Promise<any> {
    method push (line 96) | public push(resource: i18nResource) {
    method change (line 109) | public change(langCode: string) {
    method reset (line 121) | public reset(langCode: string): Promise<any> {
    method add (line 151) | public add(resource: i18nResource): Promise<any> {
    method replace (line 173) | public replace(resource: i18nResource): Promise<any> {
    method exists (line 189) | public exists(code: string): boolean {

FILE: src/options/main.ts
  class Main (line 11) | class Main {
    method constructor (line 22) | constructor() {
    method initVueConfig (line 32) | private initVueConfig() {
    method initI18n (line 137) | private initI18n() {
    method initMainVM (line 172) | private initMainVM() {
    method init (line 182) | public init() {
  type Window (line 225) | interface Window {

FILE: src/options/plugins/vuetify.ts
  class VuetifyService (line 8) | class VuetifyService {
    method init (line 9) | public init(lang: string = "zh-Hans") {

FILE: src/options/shims-tsx.d.ts
  type Element (line 6) | interface Element extends VNode {}
  type ElementClass (line 8) | interface ElementClass extends Vue {}
  type IntrinsicElements (line 9) | interface IntrinsicElements {

FILE: src/options/store.ts
  class ExtensionWorker (line 18) | class ExtensionWorker extends Extension {
    method constructor (line 19) | constructor() {
    method save (line 23) | save(options: Options) {
  method readConfig (line 50) | readConfig(state) {
  method resetConfig (line 56) | resetConfig(state, options) {
  method updateConfig (line 63) | updateConfig(state, options) {
  method addSite (line 73) | addSite(state, site) {
  method updateSite (line 84) | updateSite(state, site) {
  method removeSite (line 100) | removeSite(state, site) {
  method addClient (line 116) | addClient(state, item) {
  method updateClient (line 128) | updateClient(state, item) {
  method removeClient (line 144) | removeClient(state, item) {
  method clearClients (line 159) | clearClients(state) {
  method updatePathsOfClient (line 170) | updatePathsOfClient(state, options) {
  method removePathsOfClient (line 195) | removePathsOfClient(state, options) {
  method addPlugin (line 216) | addPlugin(state, options) {
  method updatePlugin (line 236) | updatePlugin(state, options) {
  method removePlugin (line 257) | removePlugin(state, options) {
  method updateOptions (line 272) | updateOptions(state, options: Options) {
  method updateUIOptions (line 276) | updateUIOptions(state, options: UIOptions) {
  method updateSearchStatus (line 280) | updateSearchStatus(state, searching: boolean) {
  method resetRunTimeOptions (line 289) | resetRunTimeOptions({ commit, state }, options: Options) {
  method readConfig (line 300) | readConfig({ commit, state }): Promise<any> {
  method saveConfig (line 346) | saveConfig({ commit, state }, options: Options) {
  method readUIOptions (line 360) | readUIOptions({ commit }): Promise<any> {
  method saveUIOptions (line 375) | saveUIOptions({ commit, state }, options: UIOptions) {
  method updatePagination (line 386) | updatePagination({ commit, state }, data: any) {
  method updateViewOptions (line 404) | updateViewOptions({ commit, state }, data: any) {
  method updateSearchSolution (line 422) | updateSearchSolution({ commit, state }, options: SearchSolution) {
  method removeSearchSolution (line 455) | removeSearchSolution({ commit, state }, options) {
  method addSiteSearchEntry (line 488) | addSiteSearchEntry({ commit, state }, options) {
  method updateSiteSearchEntry (line 512) | updateSiteSearchEntry({ commit, state }, options) {
  method removeSiteSearchEntry (line 537) | removeSiteSearchEntry({ commit, state }, options) {
  method addLanguage (line 555) | addLanguage({ commit, state }, options) {
  method replaceLanguage (line 562) | replaceLanguage({ commit, state }, options) {
  method addBackupServer (line 574) | addBackupServer({ commit, state }, server: IBackupServer) {
  method updateBackupServer (line 593) | updateBackupServer({ commit, state }, newData: IBackupServer) {
  method removeBackupServer (line 615) | removeBackupServer({ commit, state }, newData: IBackupServer) {

FILE: src/options/views/search/SearchTorrent.ts
  type searchResult (line 41) | type searchResult = {
  method data (line 59) | data() {
  method created (line 139) | created() {
  method mounted (line 161) | mounted() {
  method destroyed (line 181) | destroyed() {
  method beforeRouteUpdate (line 184) | beforeRouteUpdate(to: Route, from: Route, next: any) {
  method activated (line 197) | activated() {
  method key (line 207) | key(newValue, oldValue) {
  method host (line 212) | host(newValue, oldValue) {
  method successMsg (line 217) | successMsg() {
  method errorMsg (line 220) | errorMsg() {
  method "$store.state.options.defaultSearchSolutionId" (line 223) | "$store.state.options.defaultSearchSolutionId"(newValue, oldValue) {
  method loading (line 229) | loading() {
  method handler (line 233) | handler() {
  method currentOrderMode (line 243) | currentOrderMode() {
  method checkBox (line 247) | checkBox() {
  method writeLog (line 258) | writeLog(options: LogItem) {
  method doSearch (line 269) | doSearch(searchPayload?: ISearchPayload) {
  method reset (line 279) | reset() {
  method search (line 298) | search(searchPayload?: ISearchPayload) {
  method doSearchTorrentWithQueue (line 565) | doSearchTorrentWithQueue(sites: Site[]) {
  method sendSearchRequest (line 602) | sendSearchRequest(site: Site) {
  method abortSearch (line 713) | abortSearch(site: Site) {
  method removeQueue (line 741) | removeQueue(site: Site) {
  method createSearchResultSnapshot (line 766) | createSearchResultSnapshot() {
  method loadSearchResultSnapshot (line 789) | loadSearchResultSnapshot(id: string) {
  method addSearchResult (line 816) | addSearchResult(result: any[]) {
  method addTagResult (line 946) | addTagResult(item: SearchResultItem) {
  method addCategoryResult (line 981) | addCategoryResult(item: SearchResultItem) {
  method fileSizetoLength (line 1009) | fileSizetoLength(size: string | number): number {
  method getSiteSchema (line 1046) | getSiteSchema(site: Site): SiteSchema {
  method sendToClient (line 1064) | sendToClient(
  method updatePagination (line 1161) | updatePagination(value: any) {
  method getRandomString (line 1174) | getRandomString(length: number = 16, noSimilar: boolean = true): string {
  method resetDatas (line 1191) | resetDatas(datas: any) {
  method downloadSelected (line 1202) | downloadSelected() {
  method downloadTorrentFiles (line 1230) | downloadTorrentFiles(files: downloadFile[]) {
  method downloadTorrentFilesCompleted (line 1267) | downloadTorrentFilesCompleted(file?: FileDownloader) {
  method saveTorrentFile (line 1298) | saveTorrentFile(item: SearchResultItem) {
  method sendSelectedToClient (line 1319) | sendSelectedToClient(
  method copyLinkToClipboard (line 1363) | copyLinkToClipboard(url: string) {
  method getSelectedURLs (line 1379) | getSelectedURLs() {
  method copySelectedToClipboard (line 1389) | copySelectedToClipboard() {
  method clearMessage (line 1409) | clearMessage() {
  method getSiteContentMenus (line 1420) | getSiteContentMenus(site: Site): any[] {
  method showSiteContentMenus (line 1494) | showSiteContentMenus(options: SearchResultItem, event?: any) {
  method showAllContentMenus (line 1543) | showAllContentMenus(event: any) {
  method reSearchWithSite (line 1617) | reSearchWithSite(host: string) {
  method reSearchFailedSites (line 1649) | reSearchFailedSites() {
  method clone (line 1684) | clone(source: any) {
  method searchResultFilter (line 1693) | searchResultFilter(items: any[], search: string) {
  method getIMDbIdFromDouban (line 1715) | getIMDbIdFromDouban(doubanId: string) {
  method reDownloadFailedTorrents (line 1734) | reDownloadFailedTorrents() {
  method shiftCheck (line 1743) | shiftCheck(selected: boolean, index: number) {
  method arrayObjectSort (line 1788) | arrayObjectSort(
  method addSelectedToCollection (line 1812) | addSelectedToCollection(group: ICollectionGroup) {
  method addToCollection (line 1824) | addToCollection(item: any, group?: ICollectionGroup) {
  method deleteCollection (line 1849) | deleteCollection(item: any) {
  method loadTorrentCollections (line 1858) | loadTorrentCollections() {
  method isCollectioned (line 1865) | isCollectioned(link: string): boolean {
  method toggleAll (line 1871) | toggleAll() {
  method changeSort (line 1883) | changeSort(column: string) {
  method getHeaderClass (line 1896) | getHeaderClass(header: any) {
  method downloadSuccess (line 1916) | downloadSuccess(msg: string) {
  method downloadError (line 1920) | downloadError(msg: string) {
  method updateViewOptions (line 1924) | updateViewOptions() {
  method handleScroll (line 1933) | handleScroll() {
  method headers (line 1960) | headers(): Array<any> {
  method orderHeaders (line 2034) | orderHeaders(): Array<any> {
  method orderMode (line 2039) | orderMode(): Array<any> {
  method indeterminate (line 2051) | indeterminate(): boolean {
  method selectedSize (line 2061) | selectedSize(): number {

FILE: src/service/api.ts
  constant RESOURCE_URL (line 34) | const RESOURCE_URL = !isExtensionMode
  constant RESOURCE_API (line 38) | let RESOURCE_API = {
  constant APP (line 50) | const APP = {
  method init (line 60) | init(callback?: any) {
  method get (line 79) | get(key: string): string | null {
  method set (line 90) | set(key: string, content: string) {
  method clear (line 99) | clear() {
  method getLastUpdateTime (line 106) | getLastUpdateTime(): Promise<any> {
  method addScript (line 119) | addScript(script: any) {
  method applyScripts (line 123) | applyScripts() {
  method execScript (line 135) | execScript(script: any): Promise<any> {
  method runScript (line 200) | runScript(script: string, scope: any = window) {
  method applyStyle (line 208) | applyStyle(options: any): Promise<any> {
  method getScriptContent (line 260) | getScriptContent(path: string): JQueryXHR {
  method createErrorMessage (line 278) | createErrorMessage(msg: any): DataResult {
  method showNotifications (line 290) | showNotifications(
  method getInstallType (line 296) | getInstallType(): Promise<any> {
  constant API (line 318) | const API = RESOURCE_API;

FILE: src/service/backupFileParser.ts
  class BackupFileParser (line 16) | class BackupFileParser {
    method createHash (line 20) | public createHash(data: string): IHashData {
    method createBackupFileBlob (line 45) | public createBackupFileBlob(rawData: IBackupRawData): Promise<any> {
    method loadZipData (line 143) | public loadZipData(
    method checkData (line 255) | public checkData(manifest: IManifest, data: string): boolean {
    method encryptData (line 295) | public encryptData(data: any, secretKey: string = "") {
    method decryptData (line 307) | public decryptData(data: string, secretKey: string = "") {
    method encrypt (line 323) | public encrypt(data: string, secretKey: string = "") {
    method decrypt (line 332) | public decrypt(data: string, secretKey: string = "") {

FILE: src/service/clientController.ts
  class ClientController (line 10) | class ClientController {
    method constructor (line 21) | constructor() {}
    method init (line 23) | public init(options: Options) {
    method cleanUpClients (line 31) | public cleanUpClients() {
    method getClient (line 39) | public getClient(clientOptions: any): Promise<any> {
    method testClientConnectivity (line 96) | public testClientConnectivity(options: DownloadClient): Promise<any> {

FILE: src/service/downloader.ts
  type downloadFile (line 4) | type downloadFile = {
  type downloadOptions (line 12) | type downloadOptions = {
  class Downloader (line 21) | class Downloader {
    method constructor (line 29) | constructor(public options: downloadOptions) {
    method push (line 37) | public push(options: downloadFile) {
    method start (line 74) | public start() {}
    method onCompleted (line 76) | public onCompleted(file: FileDownloader) {
  class FileDownloader (line 83) | class FileDownloader {
    method constructor (line 107) | constructor(options: downloadFile) {
    method start (line 115) | public start() {
    method getFileName (line 203) | public getFileName(contentDisposition: string = "") {
    method downloadCompleted (line 228) | public downloadCompleted() {
    method downloadError (line 239) | public downloadError(error: any) {
    method updateProgress (line 245) | public updateProgress() {

FILE: src/service/extension.ts
  class Extension (line 4) | class Extension {
    method sendRequest (line 13) | public sendRequest(

FILE: src/service/favicon.ts
  constant NOIMAGE (line 6) | const NOIMAGE =
  class Favicon (line 9) | class Favicon {
    method constructor (line 12) | constructor(public service?: any) {
    method loadCache (line 16) | private loadCache() {
    method saveCache (line 23) | private saveCache() {
    method clear (line 27) | public clear() {
    method reset (line 32) | public reset(): Promise<any> {
    method gets (line 46) | public gets(urls: string[]): Promise<any> {
    method get (line 67) | public get(url: string, reset: boolean = false): Promise<any> {
    method cacheFavicon (line 90) | private cacheFavicon(url: string, host: string): Promise<any> {
    method set (line 124) | public set(url: string, data: string) {
    method cacheFromIndex (line 141) | private cacheFromIndex(url: string, host: string): Promise<any> {
    method transformBlob (line 207) | private transformBlob(blob: Blob, to: string): Promise<any> {
    method download (line 230) | private download(url: string): Promise<any> {
    method debug (line 256) | private debug(...msg: any[]) {

FILE: src/service/filters.ts
  type IFilter (line 1) | interface IFilter {
  method formatNumber (line 35) | formatNumber(source: number, format: string = "###,###,###,###.00"): str...
  method formatSize (line 145) | formatSize(
  method formatSizeWithNegative (line 211) | formatSizeWithNegative(
  method formatSpeed (line 233) | formatSpeed(bytes: any, zeroToEmpty: boolean = false) {
  method parseURL (line 261) | parseURL(url: string): any {
  method formatIMDbId (line 295) | formatIMDbId(imdbId: string): string {
  method timeAgoToNumber (line 310) | timeAgoToNumber(source: string): number {
  method formatInteger (line 363) | formatInteger(source: number) : string {

FILE: src/service/localStorage.ts
  class localStorage (line 2) | class localStorage {
    method constructor (line 4) | constructor() {
    method set (line 16) | public set(
    method get (line 40) | public get(

FILE: src/service/logger.ts
  class Logger (line 5) | class Logger {
    method constructor (line 11) | constructor() {
    method load (line 18) | public load(): Promise<any> {
    method add (line 31) | public add(data: LogItem): string {
    method remove (line 61) | public remove(items: any[]): Promise<any> {
    method clear (line 82) | public clear(): Promise<any> {

FILE: src/service/movieInfoService.ts
  type MovieInfoCache (line 4) | type MovieInfoCache = {
  class MovieInfoService (line 14) | class MovieInfoService {
    method getInfos (line 147) | public getInfos(key: string): Promise<any> {
    method isIMDbId (line 165) | public isIMDbId(IMDbId: string): boolean {
    method getInfoFromIMDb (line 173) | public getInfoFromIMDb(IMDbId: string): Promise<any> {
    method getInfoFromDoubanId (line 212) | public getInfoFromDoubanId(id: string): Promise<any> {
    method getRatings (line 255) | public getRatings(IMDbId: string): Promise<any> {
    method getOmdbApiKey (line 302) | public getOmdbApiKey() {
    method getDoubanApiKey (line 312) | public getDoubanApiKey() {
    method getDoubanEntApiKey (line 322) | public getDoubanEntApiKey() {
    method getIMDbIdFromDouban (line 333) | public getIMDbIdFromDouban(doubanId: string): Promise<any> {
    method queryMovieInfoFromDouban (line 376) | public queryMovieInfoFromDouban(
    method appendApiKey (line 454) | public appendApiKey(type: string = "", keys: string[]) {
    method removeApiKey (line 478) | private removeApiKey(type: string = "", key: string) {
    method verifyOmdbApiKey (line 505) | public verifyOmdbApiKey(key: string): Promise<any> {
    method verifyDoubanApiKey (line 530) | public verifyDoubanApiKey(key: string): Promise<any> {
    method getTopSearches (line 555) | public getTopSearches(count: number = 9): Promise<any> {

FILE: src/service/pathHandler.ts
  class PathHandler (line 6) | class PathHandler {
    method constructor (line 7) | constructor() {}
    method replacePathKey (line 19) | public replacePathKey(path: any, site: Site) {
    method getSavePath (line 37) | public getSavePath(sourcePath: any, site: Site): any {
    method replaceKeys (line 61) | public replaceKeys(source: string, keys: Dictionary<any>): string {

FILE: src/service/public.ts
  class HelpFunctions (line 7) | class HelpFunctions {
    method constructor (line 15) | constructor() {
    method getToDay (line 34) | public getToDay(time?: number): string {
    method updateBadge (line 53) | public updateBadge(count: number) {
    method getVersion (line 74) | public getVersion() {
    method getRandomString (line 88) | public getRandomString(
    method getNewId (line 108) | public getNewId(): string {
    method showNotifications (line 118) | public showNotifications(
    method removeDuplicateQueryString (line 148) | public removeDuplicateQueryString(url: string) {
    method removeQueryStringFromValue (line 177) | public removeQueryStringFromValue(url: string, value: string) {
    method removeQueryStringFields (line 206) | public removeQueryStringFields(url: string, fields: string[]) {
    method clone (line 230) | public clone(source: any) {
    method debug (line 238) | public debug(...msg: any) {
    method showContextMenu (line 247) | public showContextMenu(menus: any, event: any) {
    method getCleaningURL (line 260) | public getCleaningURL(url: string) {
    method checkPermissions (line 268) | public checkPermissions(permissions: string[]): Promise<any> {
    method requestPermissions (line 298) | public requestPermissions(permissions: string[]): Promise<any> {
    method usePermissions (line 328) | public usePermissions(
    method getSiteFromHost (line 364) | public getSiteFromHost(host: string, options: Options) {
    method getNewBackupFileName (line 386) | public getNewBackupFileName(): string {
    method replaceKeys (line 397) | public replaceKeys(
    method checkOptionalPermission (line 424) | public checkOptionalPermission(key: string): boolean {
    method transformTime (line 444) | public transformTime(time?: number, timezoneOffset?: string) {
  constant PPF (line 464) | const PPF = new HelpFunctions();

FILE: webpack/common.js
  function resolve (line 8) | function resolve(dir) {

FILE: webpack/dev-background.js
  function resolve (line 7) | function resolve(dir) {

FILE: webpack/prod-background.js
  function resolve (line 8) | function resolve(dir) {
  method transform (line 33) | transform (content, path) {
Condensed preview — 478 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,470K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 85,
    "preview": "{\n\"env\": {\n  \"browser\": true,\n  \"commonjs\": true,\n  \"es6\": true,\n  \"jquery\": true\n}\n}"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-cn.md",
    "chars": 553,
    "preview": "---\nname: Bug 反馈\nabout: 发起一个Bug反馈\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**请确认你已经做过并了解如下步骤,在 `[]` 中填入 `x` 选中**\n- [ ] "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "chars": 525,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n<!--\nIn order t"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request-cn.md",
    "chars": 273,
    "preview": "---\nname: 功能请求\nabout: 发起一个新功能请求\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n<!-- \n注意:不要删除模板内容,删除或更改将会被机器人自动关闭 \n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "chars": 604,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new-tracker-request-cn.md",
    "chars": 382,
    "preview": "---\nname: 新站点请求\nabout: 发起一个新站点支持请求\ntitle: ''\nlabels: 'New Tracker'\nassignees: ''\n\n---\n\n<!-- \n1、在发起请求前,请先确认是否在已支持的架构内;\n2、"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new-tracker-request.md",
    "chars": 887,
    "preview": "---\nname: New Tracker Request\nabout: Initiate a new tracker support request\ntitle: ''\nlabels: 'New Tracker'\nassignees: '"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/suggestions-or-comments.md",
    "chars": 379,
    "preview": "---\nname: Suggestions or comments\nabout: 其他建议或意见\ntitle: ''\nlabels: Suggestions\nassignees: ''\n\n---\n\n- \n\n<!-- \n👆 👆 👆 请在上面描"
  },
  {
    "path": ".github/issue-close-app.yml",
    "chars": 1166,
    "preview": "# Comment that will be sent if an issue is judged to be closed\ncomment: \"This issue is closed because it does not meet o"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 473,
    "preview": "<!-- \n感谢您提交 PR ,为了更好的进行版本迭代,请将目标分支选择为 `base:dev` ,我们会根据实际情况在后续版本中发布。\n\n## 标题请尽量按以下格式进行描述\n\n`<type>(<scope>): <subject>`\n\n#"
  },
  {
    "path": ".github/stale.yml",
    "chars": 2233,
    "preview": "# Configuration for probot-stale - https://github.com/probot/stale\n\n# Number of days of inactivity before an Issue or Pu"
  },
  {
    "path": ".github/workflows/build_action.yml",
    "chars": 5071,
    "preview": "name: Build Action Release\n\non:\n  push:\n    branches: [ dev ]\n  pull_request:\n    branches: [ dev ]\n  workflow_call:\n   "
  },
  {
    "path": ".github/workflows/build_canary.yml",
    "chars": 2602,
    "preview": "name: Build Canary Release\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 20 1,15 * *'\n\njobs:\n  action:\n    uses: "
  },
  {
    "path": ".gitignore",
    "chars": 279,
    "preview": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "16\n"
  },
  {
    "path": "LICENSE",
    "chars": 1054,
    "preview": "MIT License\n\nCopyright (c) 栽培者\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this sof"
  },
  {
    "path": "README.md",
    "chars": 4393,
    "preview": "> [!WARNING]\n> PT-Plugin-Plus 项目已经进入停止维护期(具体说明见: https://github.com/pt-plugins/PT-Plugin-Plus/issues/2235 )\n> \n> 我们推荐您使用"
  },
  {
    "path": "babel.config.js",
    "chars": 53,
    "preview": "module.exports = {\n  presets: [\n    '@vue/app'\n  ]\n}\n"
  },
  {
    "path": "debug/config/config.json",
    "chars": 56,
    "preview": "{\n\t\"port\": 8001,\n\t\"from\": \"../../resource\",\n\t\"to\": \"/\"\n}"
  },
  {
    "path": "debug/data/beforeSearching.json",
    "chars": 18892,
    "preview": "{\n  \"count\": 8,\n  \"start\": 0,\n  \"total\": 5608,\n  \"subjects\": [\n    {\n      \"rating\": {\n        \"max\": 10,\n        \"avera"
  },
  {
    "path": "debug/package.json",
    "chars": 521,
    "preview": "{\n  \"name\": \"pt-plugin-plus-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \""
  },
  {
    "path": "debug/src/App.ts",
    "chars": 2515,
    "preview": "// 导入基础库\nimport * as Express from \"express\";\nimport * as cors from \"cors\";\nimport * as BodyParser from \"body-parser\";\nim"
  },
  {
    "path": "debug/src/BuildPlugin.ts",
    "chars": 7551,
    "preview": "import * as FS from \"fs\";\nimport * as PATH from \"path\";\n\nexport type Dictionary<T> = { [key: string]: T };\n\n/**\n * 构建过程辅"
  },
  {
    "path": "debug/src/SearchData.ts",
    "chars": 2948,
    "preview": "/**\n * 生成搜索测试数据\n */\nexport class SearchData {\n  constructor(public config: any = {}) {}\n\n  public generate() {\n    let r"
  },
  {
    "path": "debug/src/buildResource.ts",
    "chars": 202,
    "preview": "import { BuildPlugin } from \"./BuildPlugin\";\n\nlet buildPlugin = new BuildPlugin();\nbuildPlugin.buildResource();\nbuildPlu"
  },
  {
    "path": "debug/src/index.ts",
    "chars": 101,
    "preview": "\n\nimport App from \"./App\";\nimport * as config from \"../config/config.json\";\nnew App(config).start();\n"
  },
  {
    "path": "debug/tsconfig.json",
    "chars": 6335,
    "preview": "// {\n//   \"compilerOptions\": {\n//     /* Basic Options */\n//     \"target\": \"es5\",\n//     /* Specify ECMAScript target ve"
  },
  {
    "path": "debug/typings.d.ts",
    "chars": 69,
    "preview": "declare module \"*.json\" {\n\tconst value: any;\n\texport default value;\n}"
  },
  {
    "path": "package.json",
    "chars": 4014,
    "preview": "{\n  \"name\": \"pt-plugin-plus\",\n  \"version\": \"1.6.1\",\n  \"packageManager\": \"yarn@1.19.1\",\n  \"author\": {\n    \"name\": \"rongga"
  },
  {
    "path": "privacy-statement.md",
    "chars": 817,
    "preview": "感谢使用PT助手(下称『助手』),为了让您能够安心的使用助手,特此向您说明助手的隐私权保护政策,以保障您的权益,请您详阅下列内容:\n\n## 一、隐私权保护政策的适用范围\n- 隐私权保护政策仅适用于助手,不适用于助手以外的相关网站;\n\n## "
  },
  {
    "path": "public/_locales/en/messages.json",
    "chars": 211,
    "preview": "{\r\n\t\"manifest_appName\": {\r\n\t\t\"message\": \"PT Plugin Plus\"\r\n\t},\r\n\t\"manifest_shortName\": {\r\n\t\t\"message\": \"PT Plugin Plus\"\r\n"
  },
  {
    "path": "public/_locales/zh_CN/messages.json",
    "chars": 194,
    "preview": "{\r\n\t\"manifest_appName\": {\r\n\t\t\"message\": \"PT Plugin Plus\"\r\n\t},\r\n\t\"manifest_shortName\": {\r\n\t\t\"message\": \"PTPP\"\r\n\t},\r\n\t\"man"
  },
  {
    "path": "public/assets/base.css",
    "chars": 3352,
    "preview": ".pt-plugin-body {\n  background-color: aliceblue;\n  border-radius: 8px;\n  padding-bottom: 5px;\n  width: 80px;\n  position:"
  },
  {
    "path": "public/assets/options.css",
    "chars": 159,
    "preview": ".btn-mini {\n  width: 20px !important;\n  height: 20px !important;\n  font-size: 12px !important;\n}\n\n.btn-mini .v-btn__cont"
  },
  {
    "path": "public/changelog.html",
    "chars": 419,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\""
  },
  {
    "path": "public/index.html",
    "chars": 639,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\""
  },
  {
    "path": "public/libs/Base64.js",
    "chars": 2937,
    "preview": "/**\n*\n*  Base64 encode / decode\n*\n*  @author haitao.tu\n*  @date   2010-04-26\n*  @email  tuhaitao@foxmail.com\n*\n*/\n \nfunc"
  },
  {
    "path": "public/libs/drag.js",
    "chars": 3747,
    "preview": "/**\n * 拖放功能\n * @see http://demo.jb51.net/js/2015/js-mxdx-draw-plug-codes/\n */\nfunction Drag() {\n  // 初始化\n  this.initiali"
  },
  {
    "path": "public/libs/materialIcons/content_style.css",
    "chars": 826,
    "preview": "/* fallback */\n@font-face {\n  font-family: 'Material Icons';\n  font-style: normal;\n  font-weight: 400;\n  src: url('chrom"
  },
  {
    "path": "public/libs/materialIcons/style.css",
    "chars": 457,
    "preview": "/* fallback */\n@font-face {\n  font-family: 'Material Icons';\n  font-style: normal;\n  font-weight: 400;\n  src: url(font.w"
  },
  {
    "path": "public/libs/notice/notice.js",
    "chars": 19053,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "public/libs/notice/noticejs.css",
    "chars": 4219,
    "preview": ".noticejs-top{top:0;width:100%!important}.noticejs-top .item{border-radius:0!important;margin:0!important}.noticejs-topR"
  },
  {
    "path": "public/libs/types.expand.js",
    "chars": 1238,
    "preview": "String.prototype.getQueryString = function(name, split) {\n  if (split == undefined) split = \"&\";\n  var rule =\n    \"(^|\" "
  },
  {
    "path": "public/manifest.json",
    "chars": 2067,
    "preview": "{\r\n\t\"name\": \"__MSG_manifest_appName__\",\r\n\t\"short_name\": \"__MSG_manifest_shortName__\",\r\n\t\"version\": \"1.6.1\",\r\n\t\"descripti"
  },
  {
    "path": "public/popup.html",
    "chars": 497,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, i"
  },
  {
    "path": "resource/clients/README.md",
    "chars": 1899,
    "preview": "# 下载客户端定义说明\n\n## 目录说明\n\n该目录存放所有支持的下载客户端,目录名为架构名称\n\n```\n--目录名\n----config.json\n----init.js\n```\n\n- 目录名为该客户端的类型\n- config.json :"
  },
  {
    "path": "resource/clients/deluge/config.json",
    "chars": 365,
    "preview": "{\n  \"name\": \"Deluge\",\n  \"type\": \"deluge\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://www.deluge-torrent.org/images/deluge-icon"
  },
  {
    "path": "resource/clients/deluge/init.js",
    "chars": 6484,
    "preview": "/**\n * @see https://deluge.readthedocs.io/en/develop/reference/index.html\n */\n(function ($) {\n  //Deluge\n  // id:1,metho"
  },
  {
    "path": "resource/clients/flood/config.json",
    "chars": 350,
    "preview": "{\n  \"name\": \"Flood\",\n  \"type\": \"flood\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://github.com/Flood-UI/flood/raw/master/flood."
  },
  {
    "path": "resource/clients/flood/init.js",
    "chars": 5868,
    "preview": "/**\n * @see https://github.com/jesec/flood/tree/master/server/routes/api\n */\n\n(function ($) {\n  // Flood\n  class Client "
  },
  {
    "path": "resource/clients/qbittorrent/config.json",
    "chars": 438,
    "preview": "{\n  \"name\": \"qBittorrent\",\n  \"type\": \"qbittorrent\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://www.qbittorrent.org/favicon.ico"
  },
  {
    "path": "resource/clients/qbittorrent/init.js",
    "chars": 6012,
    "preview": "/**\n * @see https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation\n */\n(function($) {\n  //qBittorrent\n  c"
  },
  {
    "path": "resource/clients/ruTorrent/config.json",
    "chars": 349,
    "preview": "{\n  \"name\": \"ruTorrent\",\n  \"type\": \"ruTorrent\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://raw.githubusercontent.com/Novik/ruT"
  },
  {
    "path": "resource/clients/ruTorrent/init.js",
    "chars": 4791,
    "preview": "/**\n * @see https://github.com/Novik/ruTorrent/blob/master/php/addtorrent.php\n * @see https://github.com/Rhilip/PT-Plugi"
  },
  {
    "path": "resource/clients/synologyDownloadStation/config.json",
    "chars": 403,
    "preview": "{\n  \"name\": \"Synology Download Station\",\n  \"type\": \"synologyDownloadStation\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://www.s"
  },
  {
    "path": "resource/clients/synologyDownloadStation/init.js",
    "chars": 6599,
    "preview": "/**\n * @see https://global.download.synology.com/download/Document/DeveloperGuide/Synology_Download_Station_Web_API.pdf\n"
  },
  {
    "path": "resource/clients/transmission/config.json",
    "chars": 471,
    "preview": "{\n  \"name\": \"Transmission\",\n  \"type\": \"transmission\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://raw.githubusercontent.com/tra"
  },
  {
    "path": "resource/clients/transmission/init.js",
    "chars": 7618,
    "preview": "/**\n * @see https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt\n */\n(function ($, window) {\n  c"
  },
  {
    "path": "resource/clients/utorrent/config.json",
    "chars": 491,
    "preview": "{\n  \"name\": \"µTorrent\",\n  \"type\": \"utorrent\",\n  \"ver\": \"0.0.1\",\n  \"icon\": \"https://www.utorrent.com/faviconUT.ico\",\n  \"s"
  },
  {
    "path": "resource/clients/utorrent/init.js",
    "chars": 6456,
    "preview": "/**\n * @see https://github.com/bittorrent/webui/blob/master/webui.js\n */\n(function ($, window) {\n  class uTorrent {\n    "
  },
  {
    "path": "resource/i18n/README.md",
    "chars": 542,
    "preview": "# 关于多语言\n> 多语言环境正在构建中,现阶段正在努力将中文翻译为英文,需要各位英文达人参与翻译、复核,待英文文案可用后,将作为其他语言的源文件以供翻译。\n\n## 如何参与英文翻译?\n- 您可以通过以下方式来参与\n  - 通过在线翻译的方"
  },
  {
    "path": "resource/i18n/en.json",
    "chars": 46187,
    "preview": "{\n  \"name\": \"English (Beta)\",\n  \"code\": \"en\",\n  \"authors\": [\n    \"ronggang\",\n    \"ylxb2016\",\n    \"xiongqiwei\",\n    \"jack"
  },
  {
    "path": "resource/i18n/zh-CN.json",
    "chars": 32763,
    "preview": "{\n  \"name\": \"简体中文 Chinese (Simplified)\",\n  \"code\": \"zh-CN\",\n  \"authors\": [\n    \"栽培者\",\n    \"MewX\"\n  ],\n  \"words\": {\n    \""
  },
  {
    "path": "resource/libs/album/album.js",
    "chars": 25473,
    "preview": "/**\n * 对数字进行四舍五入操作\n * @param {number} precision \n */\nNumber.prototype.toRound = function (precision) {\n  if (isNaN(preci"
  },
  {
    "path": "resource/libs/album/style.css",
    "chars": 6371,
    "preview": ".album {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tz-index: 30000;\r\n\tposition: absolute;\r\n\ttop: 0px;\r\n\tleft: 0px;\r\n\tbottom: 0px;\r"
  },
  {
    "path": "resource/publicSites/douban.com/common.js",
    "chars": 512,
    "preview": "(function ($, window) {\n  class Common {\n    init() {\n      this.initButtons && this.initButtons();\n      // 设置当前页面\n    "
  },
  {
    "path": "resource/publicSites/douban.com/config.json",
    "chars": 581,
    "preview": "{\n  \"name\": \"豆瓣\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"电影详情页\",\n    \"pages\": [\"\\/subject\\/\\\\d+\\/\"],\n    \"script"
  },
  {
    "path": "resource/publicSites/douban.com/doulist.js",
    "chars": 1493,
    "preview": "(function ($, window) {\n  class App extends window.DoubanCommon {\n    /**\n     * 初始化按钮列表\n     */\n    initButtons() {\n   "
  },
  {
    "path": "resource/publicSites/douban.com/explore.js",
    "chars": 1491,
    "preview": "(function ($, window) {\n  class App extends window.DoubanCommon {\n    constructor() {\n      super();\n      this.lastId ="
  },
  {
    "path": "resource/publicSites/douban.com/subject.js",
    "chars": 1218,
    "preview": "(function ($, window) {\n  class App extends window.DoubanCommon {\n    /**\n     * 初始化按钮列表\n     */\n    initButtons() {\n   "
  },
  {
    "path": "resource/publicSites/douban.com/top250.js",
    "chars": 991,
    "preview": "(function ($, window) {\n  class App extends window.DoubanCommon {\n    /**\n     * 初始化按钮列表\n     */\n    initButtons() {\n   "
  },
  {
    "path": "resource/publicSites/goodmovieslist.com/best-movies.js",
    "chars": 3303,
    "preview": "(function($) {\n  console.log(\"this is best-movies.js\");\n  class App extends window.DoubanCommon {\n    /**\n     * 初始化按钮列表"
  },
  {
    "path": "resource/publicSites/goodmovieslist.com/config.json",
    "chars": 292,
    "preview": "{\n  \"name\": \"Good Movies List\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"best-movies\",\n    \"pages\": [\"/best-movies"
  },
  {
    "path": "resource/publicSites/imdb.com/config.json",
    "chars": 307,
    "preview": "{\n  \"name\": \"IMDb\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"电影详情页\",\n    \"pages\": [\"^\\/title\\/tt\\\\d+\\/?$\"],\n    \"s"
  },
  {
    "path": "resource/publicSites/imdb.com/subject.js",
    "chars": 793,
    "preview": "(function ($, window) {\n  class App {\n    init() {\n      this.initButtons();\n      // 设置当前页面\n      PTService.pageApp = t"
  },
  {
    "path": "resource/publicSites/imdb.com/top.js",
    "chars": 995,
    "preview": "(function ($, window) {\n  class App {\n    init() {\n      this.initButtons();\n      // 设置当前页面\n      PTService.pageApp = t"
  },
  {
    "path": "resource/publicSites/reseed.tongyifan.me/config.json",
    "chars": 254,
    "preview": "{\n  \"name\": \"Reseed\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"辅种页面\",\n    \"pages\": [\"/\"],\n    \"scripts\": [\"/schema"
  },
  {
    "path": "resource/publicSites/reseed.tongyifan.me/reseed.js",
    "chars": 1226,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this."
  },
  {
    "path": "resource/schemas/Common/common.js",
    "chars": 32604,
    "preview": "(function($, window) {\n  class Common {\n    constructor() {\n      this.siteContentMenus = {};\n      this.clientContentMe"
  },
  {
    "path": "resource/schemas/Common/config.json",
    "chars": 220,
    "preview": "{\n  \"name\": \"Common\",\n  \"ver\": \"0.0.1\",\n  \"searchEntryConfig\": {\n    \"resultType\": \"html\",\n    \"parseScriptFile\": \"/sche"
  },
  {
    "path": "resource/schemas/Common/details.js",
    "chars": 1395,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n   "
  },
  {
    "path": "resource/schemas/Common/getSearchResult.js",
    "chars": 5569,
    "preview": "/**\n * 通用搜索解析脚本\n */\n(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n   "
  },
  {
    "path": "resource/schemas/Common/torrents.js",
    "chars": 951,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this."
  },
  {
    "path": "resource/schemas/Discuz/config.json",
    "chars": 806,
    "preview": "{\n  \"name\": \"Discuz\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子详情页面\",\n    \"pages\": [\"/forum.php?mod=viewthread\"]"
  },
  {
    "path": "resource/schemas/Discuz/details.js",
    "chars": 1558,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/schemas/Discuz/getSearchResult.js",
    "chars": 7538,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/\\/login/.tes"
  },
  {
    "path": "resource/schemas/Discuz/torrents.js",
    "chars": 1446,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this."
  },
  {
    "path": "resource/schemas/Gazelle/config.json",
    "chars": 2965,
    "preview": "{\n  \"name\": \"Gazelle\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子列表\",\n    \"pages\": [\"/torrents.php\"],\n    \"script"
  },
  {
    "path": "resource/schemas/Gazelle/getSearchResult.js",
    "chars": 6436,
    "preview": "if (!\"\".getQueryString) {\n  String.prototype.getQueryString = function(name, split) {\n    if (split == undefined) split "
  },
  {
    "path": "resource/schemas/Gazelle/torrents.js",
    "chars": 2266,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      // su"
  },
  {
    "path": "resource/schemas/GazelleJSONAPI/config.json",
    "chars": 2447,
    "preview": "{\n  \"name\": \"GazelleJSONAPI\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子列表\",\n    \"pages\": [\"/torrents.php\"],\n    "
  },
  {
    "path": "resource/schemas/GazelleJSONAPI/getSearchResult.js",
    "chars": 5208,
    "preview": "(function(options) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories = {};\n     "
  },
  {
    "path": "resource/schemas/NexusPHP/common.js",
    "chars": 32682,
    "preview": "(function($, window) {\n  class Common {\n    constructor() {\n      this.siteContentMenus = {};\n      this.clientContentMe"
  },
  {
    "path": "resource/schemas/NexusPHP/config.json",
    "chars": 7147,
    "preview": "{\n  \"name\": \"NexusPHP\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子详情页面\",\n    \"pages\": [\"/details.php\", \"/plugin_d"
  },
  {
    "path": "resource/schemas/NexusPHP/details.js",
    "chars": 2791,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n   "
  },
  {
    "path": "resource/schemas/NexusPHP/getSearchResult.js",
    "chars": 14360,
    "preview": "/**\r\n * NexusPHP 默认搜索结果解析类\r\n */\r\n(function (options, Searcher) {\r\n  class Parser {\r\n    constructor() {\r\n      this.have"
  },
  {
    "path": "resource/schemas/NexusPHP/parser/downloadURL.js",
    "chars": 714,
    "preview": "(function (options) {\n  if (options.url && options.url.query && options.url.href.getQueryString) {\n    let url = options"
  },
  {
    "path": "resource/schemas/NexusPHP/torrents.js",
    "chars": 3066,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this."
  },
  {
    "path": "resource/schemas/README.md",
    "chars": 2522,
    "preview": "# 网站架构定义\n\n> 网站架构是指当前网站使用了什么样的架构搭建的,国内大部分的 `PT` 网站都使用了 `NexusPHP` 这个架构\n\n## 目录说明\n\n该目录存放所有支持的网站架构,目录名为架构名称\n\n```\n--目录名\n----p"
  },
  {
    "path": "resource/schemas/TNode/common.js",
    "chars": 32679,
    "preview": "(function($, window) {\n  class Common {\n    constructor() {\n      this.siteContentMenus = {};\n      this.clientContentMe"
  },
  {
    "path": "resource/schemas/TNode/config.json",
    "chars": 3032,
    "preview": "{\n  \"name\": \"TNode\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子详情页面\",\n    \"pages\": [\"\\/torrent\\/info\\/\\\\d+\"],\n   "
  },
  {
    "path": "resource/schemas/TNode/details.js",
    "chars": 898,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.TNodeCommon {\n    init() {\n      "
  },
  {
    "path": "resource/schemas/TNode/getSearchResult.js",
    "chars": 3919,
    "preview": "/** \n * @typedef {object} TNodeSearchResult\n * @property {number} status\n * @property {object} data\n * @property {object"
  },
  {
    "path": "resource/schemas/TNode/torrents.js",
    "chars": 2408,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.TNodeCommon {\n    init() {\n      this.ini"
  },
  {
    "path": "resource/schemas/UNIT3D/config.json",
    "chars": 6213,
    "preview": "{\n  \"name\": \"UNIT3D\",\n  \"ver\": \"0.0.1\",\n  \"plugins\": [{\n    \"name\": \"种子详情页面\",\n    \"pages\": [\"^/torrents/(.+)$\", \"^/torre"
  },
  {
    "path": "resource/schemas/UNIT3D/details.js",
    "chars": 1375,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n   "
  },
  {
    "path": "resource/schemas/UNIT3D/getSearchResult.js",
    "chars": 7700,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/\\/login/.tes"
  },
  {
    "path": "resource/schemas/UNIT3D/torrents.js",
    "chars": 1326,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      // su"
  },
  {
    "path": "resource/schemas/UNIT3D/userTorrents.js",
    "chars": 953,
    "preview": "(function($) {\n  console.log(\"this is userTorrents.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      "
  },
  {
    "path": "resource/sites/1ptba.com/config.json",
    "chars": 3215,
    "preview": "{\n  \"name\": \"1PTBar\",\n  \"schema\": \"NexusPHP\",\n  \"url\": \"https://1ptba.com/\",\n  \"description\": \"壹PT吧,PT下载,教育视频,课件资源,发布教育类"
  },
  {
    "path": "resource/sites/52pt.site/config.json",
    "chars": 1826,
    "preview": "{\n  \"name\": \"52PT\",\n  \"timezoneOffset\": \"+0800\",\n  \"schema\": \"NexusPHP\",\n  \"url\": \"https://52pt.site/\",\n  \"description\":"
  },
  {
    "path": "resource/sites/README.md",
    "chars": 3576,
    "preview": "# 站点定义\n\n## 目录说明\n\n该目录存放所有支持的网站,目录名为网站域名\n\n```\n--目录名\n----parser\n-------xxxx.js\n----config.json\n----xxxx.js\n```\n\n- parser : "
  },
  {
    "path": "resource/sites/aidoru-online.me/config.json",
    "chars": 6159,
    "preview": "{\n  \"name\": \"Aidoru!Online\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"AO\",\n  \"icon\": \"https://aidoru-online.me/the"
  },
  {
    "path": "resource/sites/aither.cc/config.json",
    "chars": 1080,
    "preview": "{\n  \"name\": \"Aither\",\n  \"timezoneOffset\": \"+0800\",\n  \"schema\": \"UNIT3D\",\n  \"url\": \"https://aither.cc/\",\n  \"description\":"
  },
  {
    "path": "resource/sites/alpharatio.cc/config.json",
    "chars": 3493,
    "preview": "{\n  \"name\": \"AlphaRatio\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"0day\",\n  \"url\": \"https://alpharatio.cc/\",\n  \"ic"
  },
  {
    "path": "resource/sites/animebytes.tv/config.json",
    "chars": 3932,
    "preview": "{\n  \"name\": \"AB\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"动漫\",\n  \"url\": \"https://animebytes.tv/\",\n  \"icon\": \"http"
  },
  {
    "path": "resource/sites/animebytes.tv/getSearchResult.js",
    "chars": 3791,
    "preview": "if (!\"\".getQueryString) {\n  String.prototype.getQueryString = function (name, split) {\n    if (split == undefined) split"
  },
  {
    "path": "resource/sites/animebytes.tv/userTorrents.js",
    "chars": 789,
    "preview": "(function ($) {\n  console.log(\"this is userTorrents.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n     "
  },
  {
    "path": "resource/sites/anthelion.me/config.json",
    "chars": 970,
    "preview": "{\n  \"name\": \"Anthelion\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Movies\",\n  \"url\": \"https://anthelion.me//\",\n  \"i"
  },
  {
    "path": "resource/sites/anthelion.me/getSearchResult.js",
    "chars": 4959,
    "preview": "(function(options) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories = {};\n     "
  },
  {
    "path": "resource/sites/asiancinema.me/config.json",
    "chars": 1282,
    "preview": "{\n  \"name\": \"AsianCinema\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"综合\",\n  \"url\": \"https://asiancinema.me/\",\n  \"ic"
  },
  {
    "path": "resource/sites/asiancinema.me/getSearchResult.js",
    "chars": 8965,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/\\/login/.tes"
  },
  {
    "path": "resource/sites/audiences.me/config.json",
    "chars": 2694,
    "preview": "{\n  \"name\": \"Audiences\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"观众\",\n  \"url\": \"https://audiences.me/\",\n  \"icon\":"
  },
  {
    "path": "resource/sites/azusa.wiki/config.json",
    "chars": 3922,
    "preview": "{\n  \"name\": \"Azusa\",\n  \"description\": \"梓喵\",\n  \"schema\": \"NexusPHP\",\n  \"timezoneOffset\": \"+0800\",\n  \"icon\": \"https://azus"
  },
  {
    "path": "resource/sites/baconbits.org/config.json",
    "chars": 2260,
    "preview": "{\n  \"name\": \"bB\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"\",\n  \"url\": \"https://baconbits.org/\",\n  \"icon\": \"https:"
  },
  {
    "path": "resource/sites/bemaniso.ws/config.json",
    "chars": 482,
    "preview": "{\n  \"name\": \"Bemaniso\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Game,music\",\n  \"url\": \"https://bemaniso.ws/\",\n  \""
  },
  {
    "path": "resource/sites/beyond-hd.me/config.json",
    "chars": 5633,
    "preview": "{\r\n  \"name\": \"BeyondHD\",\r\n  \"timezoneOffset\": \"+0000\",\r\n  \"description\": \"Beyond Your Imagination,BeyondHD is a communit"
  },
  {
    "path": "resource/sites/beyond-hd.me/getSearchResult.js",
    "chars": 7947,
    "preview": "(function(options, Searcher) {\r\n  class Parser {\r\n    constructor() {\r\n      this.haveData = false;\r\n      if (/\\/login/"
  },
  {
    "path": "resource/sites/bibliotik.me/config.json",
    "chars": 4781,
    "preview": "{\n  \"name\": \"Bibliotik\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Bibliotik\",\n  \"url\": \"https://bibliotik.me/\",\n  "
  },
  {
    "path": "resource/sites/bibliotik.me/getUserSeedingTorrents.js",
    "chars": 2305,
    "preview": "(function(options, User) {\n  class Parser {\n    constructor(options, dataURL) {\n      this.options = options;\n      this"
  },
  {
    "path": "resource/sites/bitbr/config.json",
    "chars": 3247,
    "preview": "{\n  \"name\": \"bitbr\",\n  \"timezoneOffset\": \"+0800\",\n  \"schema\": \"NexusPHP\",\n  \"url\": \"https://bitbr.cc/\",\n  \"icon\": \"https"
  },
  {
    "path": "resource/sites/bitpt.cn/config.json",
    "chars": 5355,
    "preview": "{\n    \"name\": \"极速之星PT\",\n    \"description\": \"极速之星IPV6资源交流平台\",\n    \"url\": \"https://bitpt.cn/\",\n    \"icon\": \"https://bitpt."
  },
  {
    "path": "resource/sites/bitpt.cn/details.js",
    "chars": 1242,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/sites/bitpt.cn/getSearchResult.js",
    "chars": 6126,
    "preview": "/**\n * 通用搜索解析脚本\n */\n(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n   "
  },
  {
    "path": "resource/sites/bitpt.cn/torrents.js",
    "chars": 1210,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this."
  },
  {
    "path": "resource/sites/blutopia.cc/config.json",
    "chars": 1473,
    "preview": "{\n  \"name\": \"Blutopia\",\n  \"timezoneOffset\": \"+0000\",\n  \"schema\": \"UNIT3D\",\n  \"url\": \"https://blutopia.cc/\",\n  \"icon\": \"h"
  },
  {
    "path": "resource/sites/broadcasthe.net/config.json",
    "chars": 3521,
    "preview": "{\n  \"name\": \"BTN\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"著名剧集站点,又被戏称为鼻涕妞\",\n  \"url\": \"https://broadcasthe.net/\","
  },
  {
    "path": "resource/sites/broadcasthe.net/getSearchResult.js",
    "chars": 6097,
    "preview": "(function(options) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories = {};\n     "
  },
  {
    "path": "resource/sites/brokenstones.is/config.json",
    "chars": 347,
    "preview": "{\n  \"name\": \"BRKS\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Mac Apps\",\n  \"url\": \"https://brokenstones.is\",\n  \"tag"
  },
  {
    "path": "resource/sites/bt.neu6.edu.cn/config.json",
    "chars": 2247,
    "preview": "{\n  \"name\": \"六维空间\",\n  \"description\": \"东北大学ipv6资源分享平台\",\n  \"url\": \"http://bt.neu6.edu.cn/\",\n  \"icon\": \"http://bt.neu6.edu."
  },
  {
    "path": "resource/sites/bwtorrents.tv/config.json",
    "chars": 4631,
    "preview": "{\n  \"name\": \"BWT\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"bwtorrents\",\n  \"url\": \"https://bwtorrents.tv/\",\n  \"ico"
  },
  {
    "path": "resource/sites/byr.pt/config.json",
    "chars": 5429,
    "preview": "{\n  \"name\": \"BYRBT\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"著名教育网PT站点(仅支持ipv6访问与下载),有10大类资源,资源更新快,保种好。\",\n  \"url\""
  },
  {
    "path": "resource/sites/carpt.net/config.json",
    "chars": 3935,
    "preview": "{\n    \"name\": \"CarPT\",\n    \"timezoneOffset\": \"+0800\",\n    \"description\": \"CarPT\",\n    \"url\": \"https://carpt.net/\",\n    \""
  },
  {
    "path": "resource/sites/ccfbits.org/browse.js",
    "chars": 2552,
    "preview": "(function($) {\n  console.log(\"this is browse.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this.i"
  },
  {
    "path": "resource/sites/ccfbits.org/config.json",
    "chars": 4315,
    "preview": "{\n  \"name\": \"CCFBits\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"\",\n  \"url\": \"https://ccfbits.org/\",\n  \"icon\": \"htt"
  },
  {
    "path": "resource/sites/ccfbits.org/details.js",
    "chars": 1513,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/sites/ccfbits.org/getSearchResult.js",
    "chars": 4469,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/loginform/.t"
  },
  {
    "path": "resource/sites/chdbits.co/config.json",
    "chars": 5987,
    "preview": "{\n  \"name\": \"CHDBits\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"CHDBits\",\n  \"url\": \"https://chdbits.co/\",\n  \"icon\""
  },
  {
    "path": "resource/sites/cinemageddon.net/browse.js",
    "chars": 1376,
    "preview": "(function($) {\n  console.log(\"this is browse.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      this.i"
  },
  {
    "path": "resource/sites/cinemageddon.net/config.json",
    "chars": 3333,
    "preview": "{\n  \"name\": \"CinemaGeddon\",\n  \"timezoneOffset\": \"+0000\",\n  \"schema\": \"CinemaGeddon\",\n  \"url\": \"https://cinemageddon.net/"
  },
  {
    "path": "resource/sites/cinemageddon.net/details.js",
    "chars": 1144,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n   "
  },
  {
    "path": "resource/sites/cinemageddon.net/getSearchResult.js",
    "chars": 5470,
    "preview": "if (!\"\".getQueryString) {\n  String.prototype.getQueryString = function(name, split) {\n    if (split == undefined) split "
  },
  {
    "path": "resource/sites/club.hares.top/config.json",
    "chars": 8776,
    "preview": "{\n  \"name\": \"HaresClub\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"2160p/4k 及以上的高清资源站点\",\n  \"url\": \"https://club.har"
  },
  {
    "path": "resource/sites/cnlang.org/config.json",
    "chars": 3288,
    "preview": "{\n    \"name\": \"国语视界\",\n    \"timezoneOffset\": \"+0800\",\n    \"schema\": \"Discuz\",\n    \"supportedFeatures\": {\n        \"search\""
  },
  {
    "path": "resource/sites/cnlang.org/getUserSeedingTorrents.js",
    "chars": 2595,
    "preview": "if (\"\".getQueryString === undefined) {\n  String.prototype.getQueryString = function(name, split) {\n    if (split == unde"
  },
  {
    "path": "resource/sites/concertos.live/config.json",
    "chars": 4564,
    "preview": "{\n  \"name\": \"Concertos\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Concertos\",\n  \"url\": \"https://concertos.live/\",\n"
  },
  {
    "path": "resource/sites/cyanbug.net/config.json",
    "chars": 8321,
    "preview": "{\r\n  \"name\": \"CyanBug\",\r\n  \"description\": \"大青虫们在此聚集\",\r\n  \"timezoneOffset\": \"+0800\",\r\n  \"schema\": \"NexusPHP\",\r\n  \"host\": "
  },
  {
    "path": "resource/sites/dajiao.cyou/config.json",
    "chars": 3316,
    "preview": "{\n    \"name\": \"打胶 \",\n    \"timezoneOffset\": \"+0800\",\n    \"description\": \"打胶\",\n    \"url\": \"https://dajiao.cyou/\",\n    \"ico"
  },
  {
    "path": "resource/sites/dicmusic.com/config.json",
    "chars": 3385,
    "preview": "{\n  \"name\": \"DIC\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"music\",\n  \"url\": \"https://dicmusic.com/\",\n  \"icon\": \"h"
  },
  {
    "path": "resource/sites/discfan.net/config.json",
    "chars": 2388,
    "preview": "{\n  \"name\": \"DiscFan\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"DiscFan\",\n  \"url\": \"https://discfan.net/\",\n  \"icon"
  },
  {
    "path": "resource/sites/et8.org/config.json",
    "chars": 4945,
    "preview": "{\n  \"name\": \"TorrentCCF\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"兼有学习资源和软件资源的影视PT站点\",\n  \"url\": \"https://et8.org/"
  },
  {
    "path": "resource/sites/extremlymtorrents.ws/config.json",
    "chars": 4639,
    "preview": "{\n  \"name\": \"XTR\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"extremlymtorrents\",\n  \"url\": \"https://extremlymtorrent"
  },
  {
    "path": "resource/sites/femdomcult.org/config.json",
    "chars": 2133,
    "preview": "{\n  \"name\": \"Femdomcult\",\n  \"timezoneOffset\": \"-1100\",\n  \"description\": \"成人\",\n  \"url\": \"https://femdomcult.org/\",\n  \"ico"
  },
  {
    "path": "resource/sites/femdomcult.org/getSearchResult.js",
    "chars": 5164,
    "preview": "(function(options) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories = {};\n     "
  },
  {
    "path": "resource/sites/filelist.io/browse.js",
    "chars": 2582,
    "preview": "(function ($) {\n  console.log(\"this is browse.js\");\n  class App extends window.NexusPHPCommon {\n      init() {\n        t"
  },
  {
    "path": "resource/sites/filelist.io/config.json",
    "chars": 4303,
    "preview": "{\n  \"name\": \"FileList\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"FileList\",\n  \"url\": \"https://filelist.io/\",\n  \"ic"
  },
  {
    "path": "resource/sites/filelist.io/details.js",
    "chars": 1612,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/sites/filelist.io/getSearchResult.js",
    "chars": 4548,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/takelogin\\.p"
  },
  {
    "path": "resource/sites/fsm.name/config.json",
    "chars": 5080,
    "preview": "{\n  \"name\": \"FSM\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"飞天拉面神教 - FSM\",\n  \"url\": \"https://fsm.name/\",\n  \"tags\":"
  },
  {
    "path": "resource/sites/gainbound.net/config.json",
    "chars": 3654,
    "preview": "{\n    \"name\": \"丐帮\",\n    \"description\": \"降龙掌青竹杖 美酒佳肴来作伴\",\n    \"url\": \"https://gainbound.net/\",\n    \"icon\": \"https://gainb"
  },
  {
    "path": "resource/sites/gay-torrents.org/config.json",
    "chars": 5383,
    "preview": "{\n  \"name\": \"GTorg\",\n  \"url\": \"https://gay-torrents.org/\",\n  \"cdn\": [\"https://gay-torrents.se/\", \"https://gay-area.org/\""
  },
  {
    "path": "resource/sites/gay-torrents.org/getSearchResult.js",
    "chars": 3128,
    "preview": "(function (options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories "
  },
  {
    "path": "resource/sites/gazellegames.net/config.json",
    "chars": 3492,
    "preview": "{\n  \"name\": \"GGN\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"Game\",\n  \"url\": \"https://gazellegames.net/\",\n  \"icon\":"
  },
  {
    "path": "resource/sites/gazellegames.net/getSearchResult.js",
    "chars": 6427,
    "preview": "if (!\"\".getQueryString) {\n  String.prototype.getQueryString = function(name, split) {\n    if (split == undefined) split "
  },
  {
    "path": "resource/sites/gfxpeers.net/config.json",
    "chars": 309,
    "preview": "{\n  \"name\": \"GFXPeers\",\n  \"timezoneOffset\": \"+0000\",\n  \"icon\": \"https://gfxpeers.net/favicon.ico\",\n  \"url\": \"https://gfx"
  },
  {
    "path": "resource/sites/greatposterwall.com/config.json",
    "chars": 3264,
    "preview": "{\n  \"name\": \"GPW\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"movie\",\n  \"url\": \"https://greatposterwall.com/\",\n  \"ic"
  },
  {
    "path": "resource/sites/greatposterwall.com/getSearchResult.js",
    "chars": 5164,
    "preview": "(function(options) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories = {};\n     "
  },
  {
    "path": "resource/sites/hawke.uno/config.json",
    "chars": 1224,
    "preview": "{\n    \"name\": \"HUNO\",\n    \"timezoneOffset\": \"+0000\",\n    \"schema\": \"UNIT3D\",\n    \"url\": \"https://hawke.uno/\",\n    \"icon\""
  },
  {
    "path": "resource/sites/hd-space.org/config.json",
    "chars": 4587,
    "preview": "{\n  \"name\": \"HD-Space\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"HD-Space\",\n  \"url\": \"https://hd-space.org/\",\n  \"i"
  },
  {
    "path": "resource/sites/hd-space.org/details.js",
    "chars": 2664,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n   if(/\\?page\\=torrent-details/.test(window.location.search"
  },
  {
    "path": "resource/sites/hd-torrents.org/config.json",
    "chars": 6520,
    "preview": "{\n  \"name\": \"HD-Torrents\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"HD-Torrents.org\",\n  \"url\": \"https://hd-torrent"
  },
  {
    "path": "resource/sites/hd-torrents.org/details.js",
    "chars": 1018,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/sites/hd-torrents.org/getSearchResult.js",
    "chars": 3714,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      if (/login\\.php/."
  },
  {
    "path": "resource/sites/hd-torrents.org/getUserSeedingTorrents.js",
    "chars": 2235,
    "preview": "(function(options, User) {\n  class Parser {\n    constructor(options, dataURL) {\n      this.options = options;\n      this"
  },
  {
    "path": "resource/sites/hd-torrents.org/torrents.js",
    "chars": 2474,
    "preview": "(function($) {\n  console.log(\"this is torrent.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n      // su"
  },
  {
    "path": "resource/sites/hdatmos.club/config.json",
    "chars": 2688,
    "preview": "{\n  \"name\": \"HDATMOS\",\n  \"timezoneOffset\": \"+0800\",\n  \"icon\": \"https://hdatmos.club/favicon.ico\",\n  \"schema\": \"NexusPHP\""
  },
  {
    "path": "resource/sites/hdbits.org/browse.js",
    "chars": 3361,
    "preview": "(function ($) {\n  console.log(\"this is browse.js\");\n  class App extends window.NexusPHPCommon {\n      init() {\n        t"
  },
  {
    "path": "resource/sites/hdbits.org/config.json",
    "chars": 5891,
    "preview": "{\n  \"name\": \"HDB\",\n  \"timezoneOffset\": \"+0000\",\n  \"description\": \"HDB\",\n  \"url\": \"https://hdbits.org/\",\n  \"icon\": \"https"
  },
  {
    "path": "resource/sites/hdbits.org/details.js",
    "chars": 1401,
    "preview": "(function ($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n  "
  },
  {
    "path": "resource/sites/hdbits.org/getSearchResult.js",
    "chars": 4869,
    "preview": "(function(options, Searcher) {\n  class Parser {\n    constructor() {\n      this.haveData = false;\n      this.categories ="
  },
  {
    "path": "resource/sites/hdchina.org/config.json",
    "chars": 8626,
    "preview": "{\n  \"name\": \"HDChina\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"高清影音人士分享乐园\",\n  \"url\": \"https://hdchina.org/\",\n  \"i"
  },
  {
    "path": "resource/sites/hdcity.city/config.json",
    "chars": 7702,
    "preview": "{\n  \"name\": \"HDCity\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"无\",\n  \"url\": \"https://hdcity.city/\",\n  \"icon\": \"htt"
  },
  {
    "path": "resource/sites/hddolby.com/config.json",
    "chars": 4908,
    "preview": "{\n  \"name\": \"HD Dolby\",\n  \"timezoneOffset\": \"+0800\",\n  \"schema\": \"NexusPHP\",\n  \"url\": \"https://www.hddolby.com/\",\n  \"des"
  },
  {
    "path": "resource/sites/hdf.world/config.json",
    "chars": 2093,
    "preview": "{\n  \"name\": \"HD-Forever\",\n  \"timezoneOffset\": \"+0100\",\n  \"description\": \"HD-F\",\n  \"icon\": \"https://hdf.world/favicon.ico"
  },
  {
    "path": "resource/sites/hdfans.org/config.json",
    "chars": 2972,
    "preview": "{\n  \"name\": \"HDFans\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"与志同道合之人前行 分享更多值得珍藏的资源\",\n  \"url\": \"https://hdfans.or"
  },
  {
    "path": "resource/sites/hdhome.org/config.json",
    "chars": 8964,
    "preview": "{\n  \"name\": \"HDHome\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"HDHome\",\n  \"url\": \"https://hdhome.org/\",\n  \"icon\": "
  },
  {
    "path": "resource/sites/hdmayi.com/config.json",
    "chars": 3372,
    "preview": "{\n    \"name\": \"HDmayi\",\n    \"timezoneOffset\": \"+0800\",\n    \"description\": \"HDmayi\",\n    \"url\": \"http://hdmayi.com/\",\n   "
  },
  {
    "path": "resource/sites/hdpt.xyz/config.json",
    "chars": 5920,
    "preview": "{\n    \"name\": \"明教\",\n    \"timezoneOffset\": \"+0800\",\n    \"description\": \"综合性的PT论坛    欢迎您的加入!\",\n    \"url\": \"https://hdpt.xy"
  },
  {
    "path": "resource/sites/hdroute.org/config.json",
    "chars": 3470,
    "preview": "{\n  \"name\": \"HDRoute\",\n  \"timezoneOffset\": \"+0800\",\n  \"description\": \"HDRoute\",\n  \"url\": \"http://hdroute.org/\",\n  \"icon\""
  },
  {
    "path": "resource/sites/hdroute.org/details.js",
    "chars": 796,
    "preview": "(function($, window) {\n  console.log(\"this is details.js\");\n  class App extends window.NexusPHPCommon {\n    init() {\n   "
  }
]

// ... and 278 more files (download for full content)

About this extraction

This page contains the full source code of the pt-plugins/PT-Plugin-Plus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 478 files (2.2 MB), approximately 590.9k tokens, and a symbol index with 1581 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!