├── .wepyignore ├── .eslintignore ├── src ├── asset │ ├── sass │ │ └── index.scss │ └── images │ │ ├── add.png │ │ ├── me.png │ │ ├── aria2.png │ │ ├── clear.png │ │ ├── close.png │ │ ├── logo.png │ │ ├── pause.png │ │ ├── pull.png │ │ ├── ratio.png │ │ ├── share.png │ │ ├── avatar.png │ │ ├── delete.png │ │ ├── expand.png │ │ ├── filter.png │ │ ├── help-1.jpg │ │ ├── help-2.jpg │ │ ├── help-3.jpg │ │ ├── help-4.jpg │ │ ├── help-5.jpg │ │ ├── help-6.png │ │ ├── resume.png │ │ ├── server.png │ │ ├── torrent.png │ │ ├── uploaded.png │ │ ├── add-server.png │ │ ├── downloaded.png │ │ ├── free-space.png │ │ ├── me-select.png │ │ ├── no-server.png │ │ ├── no-torrent.png │ │ ├── test-fail.png │ │ ├── add-torrent.png │ │ ├── arrow-right.png │ │ ├── file-upload.png │ │ ├── server-select.png │ │ ├── test-success.png │ │ ├── upload-speed.png │ │ ├── upload-type.png │ │ ├── download-speed.png │ │ └── torrent-select.png ├── components │ ├── slideview │ │ ├── slideview.json │ │ ├── slideview.wxml │ │ ├── slideview.wxss │ │ ├── slideview.js │ │ └── slideview.wxs │ ├── modal.wpy │ ├── picker-view.wpy │ └── action-sheet.wpy ├── pages │ ├── networkSetting.wpy │ ├── help.wpy │ ├── me.wpy │ └── setting.wpy ├── utils │ ├── log.js │ └── index.js ├── server │ ├── index.js │ └── clients │ │ ├── downloadStation.js │ │ ├── transmission.js │ │ ├── utorrent.js │ │ ├── qbittorrent.js │ │ └── deluge.js ├── app.wpy └── filter │ └── index.wxs ├── .gitignore ├── functions ├── build-xml │ ├── config.json │ ├── package.json │ └── index.js ├── set-default │ ├── config.json │ ├── package.json │ └── index.js ├── get-client-data │ ├── config.json │ ├── .npmrc │ ├── package.json │ └── index.js ├── get-torrent-info │ ├── config.json │ ├── .npmrc │ ├── package.json │ └── index.js ├── is-alias-exsit │ ├── config.json │ ├── package.json │ └── index.js ├── torrent-manage │ ├── config.json │ ├── .npmrc │ ├── package.json │ └── index.js ├── customer-service │ ├── config.json │ ├── package.json │ └── index.js ├── add-torrent │ ├── .npmrc │ ├── package.json │ └── index.js ├── pause-torrent │ ├── .npmrc │ ├── package.json │ └── index.js ├── test-server │ ├── .npmrc │ ├── package.json │ └── index.js ├── delete-torrent │ ├── .npmrc │ ├── package.json │ └── index.js ├── get-client-info │ ├── .npmrc │ ├── package.json │ └── index.js ├── get-default-path │ ├── .npmrc │ ├── package.json │ └── index.js ├── get-torrent-list │ ├── .npmrc │ ├── package.json │ └── index.js ├── resume-torrent │ ├── .npmrc │ ├── package.json │ └── index.js ├── upload-temp-image │ ├── config.json │ ├── package.json │ └── index.js ├── edit-server │ ├── package.json │ └── index.js ├── delete-server │ ├── package.json │ └── index.js ├── get-user-info │ ├── package.json │ └── index.js ├── save-user-info │ ├── package.json │ └── index.js ├── get-server-info │ ├── package.json │ └── index.js ├── get-server-list │ ├── package.json │ └── index.js └── login │ ├── package.json │ └── index.js ├── .editorconfig ├── .eslintrc.js ├── wepy.config.js ├── package.json └── project.config.json /.wepyignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | *.wpy___jb_tmp___ 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | src/components/slideview/* 3 | src/xmlbuilder/* 4 | -------------------------------------------------------------------------------- /src/asset/sass/index.scss: -------------------------------------------------------------------------------- 1 | @function rpx($px) { 2 | @return $px*2 + rpx; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | weapp 4 | .DS_Store 5 | .wepycache 6 | .vscode 7 | .idea 8 | -------------------------------------------------------------------------------- /functions/build-xml/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /functions/set-default/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /src/asset/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/add.png -------------------------------------------------------------------------------- /src/asset/images/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/me.png -------------------------------------------------------------------------------- /src/components/slideview/slideview.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /functions/get-client-data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /functions/get-torrent-info/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /functions/is-alias-exsit/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /functions/torrent-manage/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /src/asset/images/aria2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/aria2.png -------------------------------------------------------------------------------- /src/asset/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/clear.png -------------------------------------------------------------------------------- /src/asset/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/close.png -------------------------------------------------------------------------------- /src/asset/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/logo.png -------------------------------------------------------------------------------- /src/asset/images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/pause.png -------------------------------------------------------------------------------- /src/asset/images/pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/pull.png -------------------------------------------------------------------------------- /src/asset/images/ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/ratio.png -------------------------------------------------------------------------------- /src/asset/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/share.png -------------------------------------------------------------------------------- /src/asset/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/avatar.png -------------------------------------------------------------------------------- /src/asset/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/delete.png -------------------------------------------------------------------------------- /src/asset/images/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/expand.png -------------------------------------------------------------------------------- /src/asset/images/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/filter.png -------------------------------------------------------------------------------- /src/asset/images/help-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-1.jpg -------------------------------------------------------------------------------- /src/asset/images/help-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-2.jpg -------------------------------------------------------------------------------- /src/asset/images/help-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-3.jpg -------------------------------------------------------------------------------- /src/asset/images/help-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-4.jpg -------------------------------------------------------------------------------- /src/asset/images/help-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-5.jpg -------------------------------------------------------------------------------- /src/asset/images/help-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/help-6.png -------------------------------------------------------------------------------- /src/asset/images/resume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/resume.png -------------------------------------------------------------------------------- /src/asset/images/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/server.png -------------------------------------------------------------------------------- /src/asset/images/torrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/torrent.png -------------------------------------------------------------------------------- /src/asset/images/uploaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/uploaded.png -------------------------------------------------------------------------------- /src/asset/images/add-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/add-server.png -------------------------------------------------------------------------------- /src/asset/images/downloaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/downloaded.png -------------------------------------------------------------------------------- /src/asset/images/free-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/free-space.png -------------------------------------------------------------------------------- /src/asset/images/me-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/me-select.png -------------------------------------------------------------------------------- /src/asset/images/no-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/no-server.png -------------------------------------------------------------------------------- /src/asset/images/no-torrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/no-torrent.png -------------------------------------------------------------------------------- /src/asset/images/test-fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/test-fail.png -------------------------------------------------------------------------------- /src/asset/images/add-torrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/add-torrent.png -------------------------------------------------------------------------------- /src/asset/images/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/arrow-right.png -------------------------------------------------------------------------------- /src/asset/images/file-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/file-upload.png -------------------------------------------------------------------------------- /src/asset/images/server-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/server-select.png -------------------------------------------------------------------------------- /src/asset/images/test-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/test-success.png -------------------------------------------------------------------------------- /src/asset/images/upload-speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/upload-speed.png -------------------------------------------------------------------------------- /src/asset/images/upload-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/upload-type.png -------------------------------------------------------------------------------- /src/asset/images/download-speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/download-speed.png -------------------------------------------------------------------------------- /src/asset/images/torrent-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xheiop/trans-client/HEAD/src/asset/images/torrent-select.png -------------------------------------------------------------------------------- /functions/customer-service/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | "customerServiceMessage.send" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /functions/add-torrent/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/pause-torrent/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/test-server/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/delete-torrent/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/get-client-data/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/get-client-info/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/get-default-path/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/get-torrent-info/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/get-torrent-list/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=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 -------------------------------------------------------------------------------- /functions/resume-torrent/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/torrent-manage/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://npm.pkg.github.com/techmovie 2 | //npm.pkg.github.com/:_authToken=5d832a8b557b230de72a78d0d9bbb43cb3b34091 -------------------------------------------------------------------------------- /functions/upload-temp-image/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | "customerServiceMessage.uploadTempMedia" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /functions/edit-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edit-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/set-default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "set-default", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/delete-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/get-user-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-user-info", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/is-alias-exsit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-alias-exsit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/save-user-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "save-user-info", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/customer-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customerservice", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/get-server-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-server-info", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/get-server-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-server-list", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/upload-temp-image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upload-temp-image", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/build-xml/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-xml", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~2.3.2", 13 | "xmlbuilder": "^15.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/networkSetting.wpy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /functions/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "crypto": "^1.0.1", 13 | "request-promise": "^4.2.5", 14 | "wx-server-sdk": "~1.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /functions/test-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.0", 13 | "wx-server-sdk": "~1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/delete-torrent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-torrent", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.0", 13 | "wx-server-sdk": "^1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/pause-torrent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pause-torrent", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.0", 13 | "wx-server-sdk": "~1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/resume-torrent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resume-torrent", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.0", 13 | "wx-server-sdk": "~1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/torrent-manage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "torrent-manage", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.4.3", 13 | "wx-server-sdk": "~2.3.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/get-client-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-client-data", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.4.0-2", 13 | "wx-server-sdk": "~2.1.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/get-client-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-client-info", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.4.0-2", 13 | "wx-server-sdk": "^1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/get-default-path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-default-path", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.0", 13 | "wx-server-sdk": "~1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/get-torrent-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-torrent-info", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.4.0", 13 | "wx-server-sdk": "~2.3.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/get-torrent-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-torrent-list", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.4.4", 13 | "wx-server-sdk": "^1.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/add-torrent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "add-torrent", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@techmovie/trans-client-server": "^1.3.7", 13 | "request-promise": "^4.2.5", 14 | "wx-server-sdk": "^1.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /functions/build-xml/index.js: -------------------------------------------------------------------------------- 1 | const cloud = require('wx-server-sdk') 2 | const builder = require('xmlbuilder') 3 | cloud.init({ 4 | env: cloud.DYNAMIC_CURRENT_ENV, 5 | traceUser: true 6 | }) 7 | 8 | // 云函数入口函数 9 | exports.main = async (event, context) => { 10 | const wxContext = cloud.getWXContext() 11 | const {params} = event 12 | cloud.updateConfig({ 13 | env: wxContext.ENV 14 | }) 15 | try { 16 | const data = builder.create(params, { encoding: 'UTF-8' }).end() 17 | return { 18 | data, 19 | code: 1 20 | } 21 | } catch (error) { 22 | return { 23 | msg: error.message, 24 | code: -1 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/log.js: -------------------------------------------------------------------------------- 1 | const log = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null 2 | const logger = { 3 | info() { 4 | if (!log) return 5 | log.info(arguments) 6 | }, 7 | warn() { 8 | if (!log) return 9 | log.warn(arguments) 10 | }, 11 | error() { 12 | if (!log) return 13 | log.error(arguments) 14 | }, 15 | setFilterMsg(msg) { // 从基础库2.7.3开始支持 16 | if (!log || !log.setFilterMsg) return 17 | if (typeof msg !== 'string') return 18 | log.setFilterMsg(msg) 19 | }, 20 | addFilterMsg(msg) { // 从基础库2.8.1开始支持 21 | if (!log || !log.addFilterMsg) return 22 | if (typeof msg !== 'string') return 23 | log.addFilterMsg(msg) 24 | } 25 | } 26 | export default logger 27 | -------------------------------------------------------------------------------- /functions/upload-temp-image/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | 9 | // 云函数入口函数 10 | exports.main = async (event, context) => { 11 | const wxContext = cloud.getWXContext() 12 | const {fileId} = event 13 | cloud.updateConfig({ 14 | env: wxContext.ENV 15 | }) 16 | const fileData = await cloud.downloadFile({ 17 | fileID: fileId 18 | }) 19 | const buffer = fileData.fileContent 20 | const res = await cloud.openapi.customerServiceMessage.uploadTempMedia({ 21 | type: 'image', 22 | media: { 23 | contentType: 'image/jpg', 24 | value: buffer 25 | } 26 | }) 27 | return res 28 | } 29 | -------------------------------------------------------------------------------- /functions/delete-server/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | cloud.init({ 4 | env: cloud.DYNAMIC_CURRENT_ENV, 5 | traceUser: true 6 | }) 7 | let db = null 8 | 9 | // 云函数入口函数 10 | exports.main = async (event, context) => { 11 | const wxContext = cloud.getWXContext() 12 | cloud.updateConfig({ 13 | env: wxContext.ENV 14 | }) 15 | db = cloud.database() 16 | try { 17 | const res = await db.collection('serverList') 18 | .where({ 19 | openid: wxContext.OPENID, 20 | _id: event.id 21 | }).remove() 22 | return { 23 | code: 1, 24 | msg: '删除成功', 25 | data: res.stats 26 | } 27 | } catch (error) { 28 | console.log(error.message) 29 | return { 30 | code: -1, 31 | msg: '删除失败,请重试', 32 | data: null 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | globals: { wx: true }, 4 | parser: "babel-eslint", 5 | parserOptions: { 6 | sourceType: "module", 7 | }, 8 | env: { 9 | browser: true, 10 | node: true, 11 | }, 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | }, 15 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 16 | extends: "standard", 17 | // required to lint *.wpy files 18 | plugins: ["html"], 19 | settings: { 20 | "html/html-extensions": [".html", ".wpy"], 21 | }, 22 | // add your custom rules here 23 | rules: { 24 | // allow paren-less arrow functions 25 | "arrow-parens": 0, 26 | // allow async-await 27 | "generator-star-spacing": 0, 28 | // allow debugger during development 29 | "no-debugger": process.env.NODE_ENV === "production" ? 2 : 0, 30 | "space-before-function-paren": 0, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /functions/is-alias-exsit/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | let db = null 9 | exports.main = async (event, context) => { 10 | const wxContext = cloud.getWXContext() 11 | cloud.updateConfig({ 12 | env: wxContext.ENV 13 | }) 14 | db = cloud.database() 15 | try { 16 | const {alias, id} = event 17 | const res = await db.collection('serverList').where({ 18 | openid: wxContext.OPENID, 19 | alias 20 | }).get() 21 | if (res.data.length > 0) { 22 | const server = res.data[0] 23 | if (server._id !== id) { 24 | throw new Error('已有重复别名') 25 | } 26 | } 27 | return { 28 | code: 1, 29 | msg: '请求成功', 30 | data: null 31 | } 32 | } catch (error) { 33 | return { 34 | code: -1, 35 | msg: error.message, 36 | data: null 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /functions/set-default/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | let db = null 9 | 10 | // 云函数入口函数 11 | exports.main = async (event, context) => { 12 | const wxContext = cloud.getWXContext() 13 | cloud.updateConfig({ 14 | env: wxContext.ENV 15 | }) 16 | db = cloud.database() 17 | const {id, isDefault} = event 18 | try { 19 | await db.collection('serverList').where({ 20 | openid: wxContext.OPENID, 21 | isDefault: true 22 | }).update({ 23 | data: { 24 | isDefault: false 25 | } 26 | }) 27 | await db.collection('serverList').where({ 28 | openid: wxContext.OPENID, 29 | _id: id 30 | }).update({ 31 | data: { 32 | isDefault 33 | } 34 | }) 35 | return { 36 | code: 1, 37 | msg: '编辑成功' 38 | } 39 | } catch (e) { 40 | console.log(e) 41 | return { 42 | code: -1, 43 | msg: '编辑失败' 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /functions/get-user-info/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | let db = null 9 | 10 | // 云函数入口函数 11 | exports.main = async (event, context) => { 12 | const wxContext = cloud.getWXContext() 13 | cloud.updateConfig({ 14 | env: wxContext.ENV 15 | }) 16 | db = cloud.database() 17 | try { 18 | const res = await db.collection('user').where({ 19 | openid: wxContext.OPENID 20 | }).limit(1).get() 21 | if (res.data.length < 1) { 22 | throw new Error('用户不存在') 23 | } 24 | const {unit, speedRefresh, nickName, avatarUrl, showTotalData} = res.data[0] 25 | return { 26 | code: 1, 27 | data: { 28 | unit, 29 | speedRefresh, 30 | nickName, 31 | avatarUrl, 32 | showTotalData 33 | }, 34 | msg: '' 35 | } 36 | } catch (error) { 37 | console.log(error.message) 38 | return { 39 | code: -1, 40 | msg: error.message, 41 | data: null 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /functions/get-server-info/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const APP_SECRET = '替换为自己的APP_SECRET' 4 | const crypto = require('crypto') 5 | cloud.init({ 6 | env: cloud.DYNAMIC_CURRENT_ENV, 7 | traceUser: true 8 | }) 9 | 10 | const decrypt = (key, raw) => { 11 | try { 12 | raw = Buffer.from(raw, 'base64') 13 | 14 | const iv = raw.slice(0, 16) 15 | let rawData = raw.slice(16, raw.length) 16 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 17 | 18 | let decoded = decipher.update(rawData, 'binary', 'utf8') 19 | decoded += decipher.final('utf8') 20 | 21 | return decoded 22 | } catch (error) { 23 | throw new Error(error.message) 24 | } 25 | } 26 | // 云函数入口函数 27 | exports.main = async(event) => { 28 | const wxContext = cloud.getWXContext() 29 | cloud.updateConfig({ 30 | env: wxContext.ENV 31 | }) 32 | try { 33 | const {encryptedData} = event 34 | const rawData = decrypt(APP_SECRET, encryptedData) 35 | return JSON.parse(rawData) 36 | } catch (e) { 37 | throw new Error(e.message) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /functions/save-user-info/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | let db = null 9 | const saveUserInfo = async (data) => { 10 | try { 11 | await db.collection('user').where({ 12 | openid: data.openid 13 | }).update({ 14 | data: { 15 | ...data.userinfo 16 | } 17 | }) 18 | } catch (error) { 19 | throw new Error(error) 20 | } 21 | } 22 | // 云函数入口函数 23 | exports.main = async (event, context) => { 24 | const wxContext = cloud.getWXContext() 25 | cloud.updateConfig({ 26 | env: wxContext.ENV 27 | }) 28 | db = cloud.database() 29 | try { 30 | const userData = event.userinfo.data 31 | const userinfo = userData || event.userinfo 32 | await saveUserInfo({ 33 | openid: wxContext.OPENID, 34 | userinfo 35 | }) 36 | return { 37 | code: 1, 38 | msg: '', 39 | data: null 40 | } 41 | } catch (error) { 42 | console.log(error.message) 43 | return { 44 | code: -1, 45 | msg: '服务器异常', 46 | data: null 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | import Qbittorrent from './clients/qbittorrent' 2 | import Transmission from './clients/transmission' 3 | import Deluge from './clients/deluge' 4 | import Utorrent from './clients/utorrent' 5 | import Rutorrent from './clients/rutorrent' 6 | import Ds from './clients/downloadStation' 7 | const defaultOption = { 8 | baseUrl: '', 9 | username: '', 10 | password: '', 11 | timeout: 5000, 12 | type: 'qbittorrent' // transmission,deluge 13 | } 14 | 15 | export default class TransClient { 16 | constructor (options) { 17 | this.config = { 18 | ...defaultOption, 19 | ...options 20 | } 21 | } 22 | 23 | init () { 24 | let client = null 25 | const { type } = this.config 26 | if (type === 'qbitorrent') { 27 | client = new Qbittorrent(this.config) 28 | } else if (type === 'transmission') { 29 | client = new Transmission(this.config) 30 | } else if (type === 'deluge') { 31 | client = new Deluge(this.config) 32 | } else if (type === 'utorrent') { 33 | client = new Utorrent(this.config) 34 | } else if (type === 'rutorrent') { 35 | client = new Rutorrent(this.config) 36 | } else if (type === 'downloadStation') { 37 | client = new Ds(this.config) 38 | } 39 | return client 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /wepy.config.js: -------------------------------------------------------------------------------- 1 | var prod = process.env.NODE_ENV === 'production' 2 | 3 | module.exports = { 4 | wpyExt: '.wpy', 5 | eslint: true, 6 | cliLogs: true, 7 | compilers: { 8 | sass: { 9 | outputStyle: 'compressed' 10 | }, 11 | babel: { 12 | sourceMap: true, 13 | presets: [ 14 | 'env' 15 | ], 16 | plugins: [ 17 | 'babel-plugin-transform-class-properties', 18 | 'transform-export-extensions', 19 | 'syntax-export-extensions', 20 | 'babel-plugin-transform-object-rest-spread' 21 | 22 | ] 23 | } 24 | }, 25 | plugins: { 26 | 27 | }, 28 | appConfig: { 29 | noPromiseAPI: ['createSelectorQuery'] 30 | } 31 | } 32 | 33 | if (prod) { 34 | module.exports.cliLogs = false 35 | 36 | delete module.exports.compilers.babel.sourcesMap 37 | // 压缩sass 38 | module.exports.compilers['sass'] = {outputStyle: 'compressed'} 39 | 40 | // 压缩js 41 | module.exports.plugins = { 42 | uglifyjs: { 43 | filter: /\.js$/, 44 | config: { 45 | } 46 | }, 47 | imagemin: { 48 | filter: /\.(jpg|png|jpeg)$/, 49 | config: { 50 | jpg: { 51 | quality: 80 52 | }, 53 | png: { 54 | quality: 80 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /functions/get-server-list/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | 9 | let db = null 10 | 11 | /* 12 | * 服务器列表 13 | * @param {any} 14 | * @return 15 | * */ 16 | const getServerList = async (data) => { 17 | try { 18 | const res = await db.collection('serverList') 19 | .where({ 20 | openid: data.openid 21 | }) 22 | .orderBy('createTime', 'desc') 23 | .get() 24 | const servers = res.data 25 | servers.forEach((server, index) => { 26 | if (server.isDefault) { 27 | servers.splice(index, 1) 28 | servers.unshift(server) 29 | } 30 | }) 31 | return { 32 | code: 1, 33 | data: servers || [], 34 | msg: '' 35 | } 36 | } catch (e) { 37 | throw new Error(e) 38 | } 39 | } 40 | // 云函数入口函数 41 | exports.main = async (event, context) => { 42 | const wxContext = cloud.getWXContext() 43 | cloud.updateConfig({ 44 | env: wxContext.ENV 45 | }) 46 | db = cloud.database() 47 | try { 48 | const res = await getServerList({ 49 | openid: wxContext.OPENID 50 | }) 51 | return res 52 | } catch (error) { 53 | console.log(error.message) 54 | return { 55 | code: -1, 56 | msg: error.message, 57 | data: null 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trans-client", 3 | "version": "0.0.1", 4 | "description": "PT 工具", 5 | "main": "dist/app.js", 6 | "scripts": { 7 | "dev": "wepy build --watch", 8 | "build": "cross-env NODE_ENV=production wepy build --no-cache", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "techmovie", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dayjs": "^1.8.28", 15 | "wepy": "^1.7.2", 16 | "wepy-async-function": "^1.4.7", 17 | "wx-server-sdk": "^1.8.2" 18 | }, 19 | "devDependencies": { 20 | "babel-eslint": "^7.2.1", 21 | "babel-plugin-transform-class-properties": "^6.24.1", 22 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 23 | "babel-plugin-transform-export-extensions": "^6.22.0", 24 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 25 | "babel-preset-env": "^1.6.1", 26 | "cross-env": "^5.1.3", 27 | "eslint": "^3.19.0", 28 | "eslint-config-standard": "^7.1.0", 29 | "eslint-friendly-formatter": "^2.0.7", 30 | "eslint-plugin-html": "^2.0.3", 31 | "eslint-plugin-promise": "^3.5.0", 32 | "eslint-plugin-standard": "^2.0.1", 33 | "less": "^3.8.1", 34 | "wepy-compiler-babel": "^1.5.1", 35 | "wepy-compiler-less": "^1.3.14", 36 | "wepy-compiler-sass": "^1.3.12", 37 | "wepy-compiler-typescript": "^1.5.9", 38 | "wepy-eslint": "^1.5.4", 39 | "wepy-plugin-imagemin": "^1.5.3", 40 | "wepy-plugin-replace": "^1.5.10", 41 | "wepy-plugin-uglifyjs": "^1.3.7" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/slideview/slideview.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{item.text}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/slideview/slideview.wxss: -------------------------------------------------------------------------------- 1 | :host{width:100%}.weui-slideview{overflow:hidden;position:relative}.weui-slideview{position:relative}.weui-slideview__left{position:relative;z-index:10}.weui-slideview__right{position:absolute;z-index:1;left:100%;top:0;height:100%}.weui-slideview__btn__wrp{position:absolute;left:0;bottom:0;text-align:center;min-width:69px;height:100%;white-space:nowrap}.weui-slideview__btn{color:#FFFFFF;padding:0 17px}.weui-slideview__btn-group_default .weui-slideview__btn{background:#C7C7CC}.weui-slideview__btn-group_default~.weui-slideview__btn-group_default:before{content:" ";position:absolute;left:0;top:0;width:1px;bottom:0;border-left:1rpx solid #FFFFFF;color:#FFFFFF}.weui-slideview__btn-group_default:first-child:before{display:none}.weui-slideview__btn-group_warn .weui-slideview__btn{background:#FE3B30}.weui-slideview__btn-group_warn~.weui-slideview__btn-group_warn:before{content:" ";position:absolute;left:0;top:0;width:1px;bottom:0;border-left:1rpx solid #FFFFFF;color:#FFFFFF}.weui-slideview__btn-group_warn:first-child:before{display:none}.weui-slideview_icon .weui-slideview__btn__wrp{background:transparent;font-size:0}.weui-slideview_icon .weui-slideview__btn__wrp:after{content:"";width:0;height:100%;vertical-align:middle;display:inline-block}.weui-slideview_icon .weui-slideview__btn__wrp:first-child{padding-left:16px}.weui-slideview_icon .weui-slideview__btn__wrp:last-child{padding-right:8px}.weui-slideview_icon .weui-slideview__btn{width:48px;height:48px;line-height:48px;padding:0;display:inline-block;vertical-align:middle;border-radius:50%;background-color:#FFFFFF}.weui-slideview_icon .weui-slideview__btn__icon{display:inline-block;vertical-align:middle;width:22px;height:22px} -------------------------------------------------------------------------------- /functions/get-default-path/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | const TransClient = require('@techmovie/trans-client-server').default 5 | 6 | cloud.init({ 7 | env: cloud.DYNAMIC_CURRENT_ENV, 8 | traceUser: true 9 | }) 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | // 云函数入口函数 29 | exports.main = async (event, context) => { 30 | const wxContext = cloud.getWXContext() 31 | cloud.updateConfig({ 32 | env: wxContext.ENV 33 | }) 34 | try { 35 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 36 | const {type, urlAlias, userName, password} = serverInfo 37 | const serverData = { 38 | type, 39 | url: urlAlias, 40 | username: userName, 41 | password 42 | } 43 | if (serverInfo.isLocalNetwork) { 44 | return { 45 | action: 'getDefaultSavePath', 46 | data: serverData 47 | } 48 | } 49 | let client = new TransClient(serverData) 50 | client = client.init() 51 | const res = await client.getDefaultSavePath() 52 | return res 53 | } catch (error) { 54 | console.log(error.message) 55 | return { 56 | code: -1, 57 | msg: error.message, 58 | data: null 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /functions/get-torrent-info/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | 5 | cloud.init({ 6 | env: cloud.DYNAMIC_CURRENT_ENV, 7 | traceUser: true 8 | }) 9 | const TransClient = require('@techmovie/trans-client-server').default 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | 29 | // 云函数入口函数 30 | exports.main = async (event, context) => { 31 | const wxContext = cloud.getWXContext() 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const { id } = event 37 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 38 | const { type, urlAlias, userName, password } = serverInfo 39 | const serverData = { 40 | type, 41 | url: urlAlias, 42 | username: userName, 43 | password 44 | } 45 | if (serverInfo.isLocalNetwork) { 46 | return { 47 | action: 'getTorrentInfo', 48 | data: serverData, 49 | params: id 50 | } 51 | } 52 | let client = new TransClient(serverData) 53 | client = client.init() 54 | const res = await client.getTorrentInfo(id) 55 | return res 56 | } catch (error) { 57 | console.log(error.message) 58 | return { 59 | code: -1, 60 | msg: '操作失败,请重试', 61 | data: null 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /functions/get-client-info/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | const TransClient = require('@techmovie/trans-client-server').default 5 | 6 | cloud.init({ 7 | env: cloud.DYNAMIC_CURRENT_ENV, 8 | traceUser: true 9 | }) 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | // 云函数入口函数 29 | exports.main = async (event, context) => { 30 | const wxContext = cloud.getWXContext() 31 | const {forSpeed = false} = event 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 37 | const serverData = { 38 | type: serverInfo.type, 39 | url: serverInfo.urlAlias, 40 | username: serverInfo.userName, 41 | password: serverInfo.password || '' 42 | } 43 | if (serverInfo.isLocalNetwork) { 44 | return { 45 | action: 'getClientInfo', 46 | params: {forSpeed}, 47 | data: serverData 48 | } 49 | } else { 50 | let client = new TransClient(serverData) 51 | client = client.init() 52 | const res = await client.getClientInfo({forSpeed}) 53 | return res 54 | } 55 | } catch (error) { 56 | console.log(error.message) 57 | return { 58 | code: -1, 59 | msg: error.message, 60 | data: null 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /functions/pause-torrent/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | 5 | cloud.init({ 6 | env: cloud.DYNAMIC_CURRENT_ENV, 7 | traceUser: true 8 | }) 9 | 10 | const TransClient = require('@techmovie/trans-client-server').default 11 | const APP_SECRET = '替换为自己的APP_SECRET' 12 | 13 | const decrypt = (key, raw) => { 14 | try { 15 | raw = Buffer.from(raw, 'base64') 16 | 17 | const iv = raw.slice(0, 16) 18 | let rawData = raw.slice(16, raw.length) 19 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 20 | 21 | let decoded = decipher.update(rawData, 'binary', 'utf8') 22 | decoded += decipher.final('utf8') 23 | 24 | return JSON.parse(decoded) 25 | } catch (error) { 26 | throw new Error(error.message) 27 | } 28 | } 29 | 30 | // 云函数入口函数 31 | exports.main = async (event, context) => { 32 | const wxContext = cloud.getWXContext() 33 | cloud.updateConfig({ 34 | env: wxContext.ENV 35 | }) 36 | try { 37 | const {id} = event 38 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 39 | const {type, urlAlias, userName, password} = serverInfo 40 | const serverData = { 41 | type, 42 | url: urlAlias, 43 | username: userName, 44 | password 45 | } 46 | if (serverInfo.isLocalNetwork) { 47 | return { 48 | action: 'pauseTorrent', 49 | params: { 50 | id 51 | }, 52 | data: serverData 53 | } 54 | } 55 | let client = new TransClient(serverData) 56 | client = client.init() 57 | const res = await client.pauseTorrent({ 58 | id 59 | }) 60 | return res 61 | } catch (error) { 62 | console.log(error.message) 63 | return { 64 | code: -1, 65 | msg: error.message, 66 | data: null 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /functions/torrent-manage/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | 5 | cloud.init({ 6 | env: cloud.DYNAMIC_CURRENT_ENV, 7 | traceUser: true 8 | }) 9 | const TransClient = require('@techmovie/trans-client-server').default 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | 29 | // 云函数入口函数 30 | exports.main = async (event, context) => { 31 | const wxContext = cloud.getWXContext() 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const { action, params } = event 37 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 38 | const { type, urlAlias, userName, password } = serverInfo 39 | const serverData = { 40 | type, 41 | url: urlAlias, 42 | username: userName, 43 | password 44 | } 45 | if (serverInfo.isLocalNetwork) { 46 | return { 47 | action, 48 | params: { 49 | ...params 50 | }, 51 | data: serverData 52 | } 53 | } 54 | let client = new TransClient(serverData) 55 | client = client.init() 56 | const res = await client[action]({ 57 | ...params 58 | }) 59 | return res 60 | } catch (error) { 61 | console.log(error.message) 62 | return { 63 | code: -1, 64 | msg: '操作失败,请重试', 65 | data: null 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /functions/resume-torrent/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | 5 | cloud.init({ 6 | env: cloud.DYNAMIC_CURRENT_ENV, 7 | traceUser: true 8 | }) 9 | const TransClient = require('@techmovie/trans-client-server').default 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | 29 | // 云函数入口函数 30 | exports.main = async (event, context) => { 31 | const wxContext = cloud.getWXContext() 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const {id} = event 37 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 38 | const {type, urlAlias, userName, password} = serverInfo 39 | const serverData = { 40 | type, 41 | url: urlAlias, 42 | username: userName, 43 | password 44 | } 45 | if (serverInfo.isLocalNetwork) { 46 | return { 47 | action: 'resumeTorrent', 48 | params: { 49 | id 50 | }, 51 | data: serverData 52 | } 53 | } 54 | let client = new TransClient(serverData) 55 | client = client.init() 56 | const res = await client.resumeTorrent( 57 | { 58 | id 59 | } 60 | ) 61 | return res 62 | } catch (error) { 63 | console.log(error.message) 64 | return { 65 | code: -1, 66 | msg: '操作失败,请重试', 67 | data: null 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /functions/get-client-data/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | const TransClient = require('@techmovie/trans-client-server').default 5 | 6 | cloud.init({ 7 | env: cloud.DYNAMIC_CURRENT_ENV, 8 | traceUser: true 9 | }) 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | // 云函数入口函数 29 | exports.main = async (event, context) => { 30 | const wxContext = cloud.getWXContext() 31 | const {forSpeed = false, rid = -1, auth = '', authType = ''} = event 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 37 | const serverData = { 38 | type: serverInfo.type, 39 | url: serverInfo.urlAlias, 40 | username: serverInfo.userName, 41 | password: serverInfo.password || '' 42 | } 43 | if (serverInfo.isLocalNetwork) { 44 | return { 45 | action: 'getClientData', 46 | params: {forSpeed, rid, auth, authType}, 47 | data: serverData 48 | } 49 | } else { 50 | let client = new TransClient(serverData) 51 | client = client.init() 52 | const res = await client.getClientData({forSpeed, rid, auth, authType}) 53 | console.log(res) 54 | return res 55 | } 56 | } catch (error) { 57 | console.log(error.message) 58 | return { 59 | code: -1, 60 | msg: error.message, 61 | data: null 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /functions/delete-torrent/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | 9 | const TransClient = require('@techmovie/trans-client-server').default 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | 29 | // 云函数入口函数 30 | exports.main = async (event, context) => { 31 | const wxContext = cloud.getWXContext() 32 | cloud.updateConfig({ 33 | env: wxContext.ENV 34 | }) 35 | try { 36 | const {id, auth, deleteFile} = event 37 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 38 | const {type, urlAlias, userName, password} = serverInfo 39 | const serverData = { 40 | type, 41 | url: urlAlias, 42 | username: userName, 43 | password 44 | } 45 | if (serverInfo.isLocalNetwork) { 46 | return { 47 | action: 'deleteTorrent', 48 | params: { 49 | id, 50 | deleteFile, 51 | auth 52 | }, 53 | data: serverData 54 | } 55 | } 56 | let client = new TransClient(serverData) 57 | client = client.init() 58 | const res = await client.deleteTorrent({ 59 | id, 60 | deleteFile, 61 | auth 62 | }) 63 | return res 64 | } catch (error) { 65 | console.log(error.message) 66 | return { 67 | code: -1, 68 | msg: error.message, 69 | data: null 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /functions/get-torrent-list/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const TransClient = require('@techmovie/trans-client-server').default 4 | const crypto = require('crypto') 5 | 6 | cloud.init({ 7 | env: cloud.DYNAMIC_CURRENT_ENV, 8 | traceUser: true 9 | }) 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | 12 | const decrypt = (key, raw) => { 13 | try { 14 | raw = Buffer.from(raw, 'base64') 15 | 16 | const iv = raw.slice(0, 16) 17 | let rawData = raw.slice(16, raw.length) 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 19 | 20 | let decoded = decipher.update(rawData, 'binary', 'utf8') 21 | decoded += decipher.final('utf8') 22 | 23 | return JSON.parse(decoded) 24 | } catch (error) { 25 | throw new Error(error.message) 26 | } 27 | } 28 | // 云函数入口函数 29 | exports.main = async (event, context) => { 30 | const wxContext = cloud.getWXContext() 31 | cloud.updateConfig({ 32 | env: wxContext.ENV 33 | }) 34 | try { 35 | const {page = 1, reverse = false, filter = 'all', sort = 'added_on'} = event 36 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 37 | const {type, urlAlias, userName, password} = serverInfo 38 | const serverData = { 39 | type, 40 | url: urlAlias, 41 | username: userName, 42 | password 43 | } 44 | if (serverInfo.isLocalNetwork) { 45 | return { 46 | action: 'getTorrentList', 47 | params: { 48 | page, 49 | reverse, 50 | filter, 51 | sort 52 | }, 53 | data: serverData 54 | } 55 | } else { 56 | let client = new TransClient(serverData) 57 | client = client.init() 58 | const res = await client.getTorrentList({ 59 | page, 60 | reverse, 61 | filter 62 | }) 63 | return res 64 | } 65 | } catch (error) { 66 | console.log(error.message) 67 | return { 68 | code: -1, 69 | msg: error.message, 70 | data: null 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "PT 工具", 3 | "setting": { 4 | "urlCheck": false, 5 | "es6": false, 6 | "postcss": false, 7 | "preloadBackgroundData": false, 8 | "minified": false, 9 | "newFeature": true, 10 | "coverView": true, 11 | "nodeModules": true, 12 | "autoAudits": false, 13 | "showShadowRootInWxmlPanel": false, 14 | "scopeDataCheck": false, 15 | "uglifyFileName": true, 16 | "checkInvalidKey": true, 17 | "checkSiteMap": true, 18 | "uploadWithSourceMap": true, 19 | "compileHotReLoad": false, 20 | "useMultiFrameRuntime": true, 21 | "useApiHook": true, 22 | "babelSetting": { 23 | "ignore": [], 24 | "disablePlugins": [], 25 | "outputPath": "" 26 | }, 27 | "bundle": false, 28 | "useIsolateContext": true, 29 | "useCompilerModule": false, 30 | "userConfirmedUseCompilerModuleSwitch": false, 31 | "userConfirmedBundleSwitch": false, 32 | "packNpmManually": false, 33 | "packNpmRelationList": [], 34 | "minifyWXSS": true, 35 | "useApiHostProcess": true 36 | }, 37 | "babel": { 38 | "presets": [ 39 | "env" 40 | ], 41 | "plugins": [ 42 | "transform-export-extensions", 43 | "syntax-export-extensions" 44 | ] 45 | }, 46 | "compileType": "miniprogram", 47 | "appid": "wx75929fba3c9f82e6", 48 | "projectname": "trans-client", 49 | "miniprogramRoot": "dist/", 50 | "cloudfunctionRoot": "functions/", 51 | "simulatorType": "wechat", 52 | "simulatorPluginLibVersion": {}, 53 | "cloudfunctionTemplateRoot": "cloudfunctionTemplate", 54 | "libVersion": "2.17.3", 55 | "condition": { 56 | "search": { 57 | "list": [] 58 | }, 59 | "conversation": { 60 | "list": [] 61 | }, 62 | "plugin": { 63 | "list": [] 64 | }, 65 | "game": { 66 | "list": [] 67 | }, 68 | "gamePlugin": { 69 | "list": [] 70 | }, 71 | "miniprogram": { 72 | "list": [ 73 | { 74 | "id": 0, 75 | "name": "模拟版本更新", 76 | "pathName": "pages/index", 77 | "query": "", 78 | "scene": null 79 | } 80 | ] 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /functions/customer-service/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init({ 5 | env: cloud.DYNAMIC_CURRENT_ENV, 6 | traceUser: true 7 | }) 8 | const getMediaId = async (fileId) => { 9 | const res = await cloud.callFunction({ 10 | name: 'upload-temp-image', 11 | data: { 12 | fileId 13 | } 14 | }) 15 | return res.result 16 | } 17 | // 云函数入口函数 18 | exports.main = async (event, context) => { 19 | const {Content} = event 20 | const wxContext = cloud.getWXContext() 21 | cloud.updateConfig({ 22 | env: wxContext.ENV 23 | }) 24 | 25 | console.log(event) 26 | try { 27 | const msg = Content ? `${Content}`.toLowerCase() : '' 28 | if (msg === 'transclient' || msg === '公众号' || msg === '2') { 29 | const res = await getMediaId('cloud://product-1gapq5os57c5165b.7072-product-1gapq5os57c5165b-1301060991/qrcode_for_gh_da481ad14724_344.jpg') 30 | await cloud.openapi.customerServiceMessage.send({ 31 | touser: wxContext.OPENID, 32 | msgtype: 'image', 33 | image: { 34 | mediaId: res.mediaId 35 | } 36 | }) 37 | console.log('公众号') 38 | } else if (msg === '1') { 39 | await cloud.openapi.customerServiceMessage.send({ 40 | touser: wxContext.OPENID, 41 | msgtype: 'link', 42 | link: { 43 | title: '加入TG群组', 44 | description: '反馈问题,获取最近更新', 45 | thumb_url: 'https://i.loli.net/2020/04/25/f2jwGndvxDyUMTQ.png', 46 | url: 'https://t.me/joinchat/ILYS-BvdHjxEiYC21-Tt4A' 47 | } 48 | }) 49 | console.log('TG群组') 50 | } else if (msg === '0') { 51 | return { 52 | MsgType: 'transfer_customer_service', 53 | ToUserName: wxContext.OPENID, 54 | FromUserName: 'gh_1be3c7972ccd', 55 | CreateTime: parseInt(+new Date() / 1000) 56 | } 57 | } else if (msg === '3') { 58 | const res = await getMediaId('cloud://product-1gapq5os57c5165b.7072-product-1gapq5os57c5165b-1301060991/IMG_0080.jpeg') 59 | await cloud.openapi.customerServiceMessage.send({ 60 | touser: wxContext.OPENID, 61 | msgtype: 'image', 62 | image: { 63 | mediaId: res.mediaId 64 | } 65 | }) 66 | console.log('微信群') 67 | } else { 68 | await cloud.openapi.customerServiceMessage.send({ 69 | touser: wxContext.OPENID, 70 | msgtype: 'text', 71 | text: { 72 | content: '您好,欢迎使用TransClient。 \n\n 回复0:接入人工客服 \n 回复1:获取Telegram群组链接 \n 回复2:获取公众号二维码 \n 回复3:加入微信群' 73 | } 74 | }) 75 | } 76 | 77 | return 'success' 78 | } catch (error) { 79 | console.log(error) 80 | return error 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /functions/login/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | cloud.init({ 4 | env: cloud.DYNAMIC_CURRENT_ENV, 5 | traceUser: true 6 | }) 7 | let db = null 8 | const crypto = require('crypto') 9 | const rp = require('request-promise') 10 | const APP_SECRET = '替换为自己的APP_SECRET' 11 | const APP_ID = 'wx75929fba3c9f82e6' 12 | 13 | const encryptSha1 = (data) => { 14 | return crypto.createHash('sha1').update(data, 'utf8').digest('hex') 15 | } 16 | 17 | /* 18 | * 判断用户是否已注册 19 | * @param {any} 20 | * @return 21 | * */ 22 | const isRegister = async (openid) => { 23 | const res = await db.collection('user').where({ 24 | openid 25 | }).get() 26 | console.log('isRegister:', res) 27 | return res.data.length > 0 28 | } 29 | 30 | /* 31 | * 更新数据库 32 | * @param {any} 33 | * @return 34 | * */ 35 | const addUser = async (openid) => { 36 | try { 37 | console.log('adding-user:', openid) 38 | return await db.collection('user').add({ 39 | data: { 40 | openid, 41 | createTime: db.serverDate() 42 | } 43 | }) 44 | } catch (e) { 45 | console.log('adduser failed', e) 46 | throw new Error(e) 47 | } 48 | } 49 | /* 50 | * 未注册增加user 51 | * @param {any} 52 | * @return 53 | * */ 54 | const updateUser = async({openid}) => { 55 | const isRegistered = await isRegister(openid) 56 | console.log('isRegistered:', isRegistered) 57 | if (!isRegistered) { 58 | const res = await addUser(openid) 59 | console.log('added-user:', res) 60 | } 61 | } 62 | cloud.init() 63 | 64 | // 云函数入口函数 65 | exports.main = async (event, context) => { 66 | const wxContext = cloud.getWXContext() 67 | cloud.updateConfig({ 68 | env: wxContext.ENV 69 | }) 70 | db = cloud.database() 71 | const code = event.code 72 | try { 73 | await updateUser({ 74 | openid: wxContext.OPENID, 75 | unionid: wxContext.UNIONID}) 76 | const res = await rp({ 77 | url: 'https://api.weixin.qq.com/sns/jscode2session', 78 | qs: { 79 | appid: APP_ID, 80 | secret: APP_SECRET, 81 | js_code: code, 82 | grant_type: 'authorization_code' 83 | }, 84 | json: true 85 | }) 86 | if (res.errcode) { 87 | throw new Error(res.errmsg) 88 | } 89 | console.log('session:', res.session_key) 90 | return { 91 | code: 1, 92 | msg: '登录成功', 93 | data: { 94 | skey: encryptSha1(res.session_key) 95 | } 96 | } 97 | } catch (error) { 98 | console.log(error) 99 | return { 100 | code: -1, 101 | msg: '登录失败', 102 | data: null 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/components/modal.wpy: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 取消 14 | 15 | 17 | 18 | 确定 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 54 | 111 | -------------------------------------------------------------------------------- /functions/edit-server/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const crypto = require('crypto') 4 | const APP_SECRET = '替换为自己的APP_SECRET' 5 | 6 | cloud.init({ 7 | env: cloud.DYNAMIC_CURRENT_ENV, 8 | traceUser: true 9 | }) 10 | let db = null 11 | 12 | const encrypt = (key, raw) => { 13 | var iv = crypto.randomBytes(16) 14 | key = Buffer.from(key) 15 | 16 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv) 17 | 18 | let crypted = cipher.update(raw, 'utf8', 'binary') 19 | crypted += cipher.final('binary') 20 | crypted = Buffer.from(crypted, 'binary') 21 | 22 | // 拼接iv串 23 | let enc = Buffer.concat([iv, Buffer.from(crypted, 'base64')]) 24 | enc = enc.toString('base64') 25 | 26 | return enc 27 | } 28 | const editServer = async (data) => { 29 | const {openid, id} = data 30 | try { 31 | delete data.openid 32 | delete data.id 33 | return await db.collection('serverList').where({ 34 | openid, 35 | _id: id 36 | }).update({ 37 | data: { 38 | ...data 39 | } 40 | }) 41 | } catch (e) { 42 | throw new Error(e) 43 | } 44 | } 45 | const getServerUrl = (data) => { 46 | let protocol = data.isHttps ? 'https://' : 'http://' 47 | let {url, port} = data 48 | port = port ? ':' + port : '' 49 | if (url.endsWith('/')) { 50 | url = url.substr(0, url.length - 1) 51 | console.log(url) 52 | } 53 | if (/^(http|https):\/\/\w+/.test(url)) { 54 | protocol = '' 55 | } 56 | if (/.+(:\d+)$/.test(url)) { 57 | port = '' 58 | } 59 | console.log(url) 60 | if (/.+(\/)$/.test(url)) { 61 | url = url.substring(0, url.length - 1) 62 | } 63 | console.log(`${protocol}${url}${port}`) 64 | return `${protocol}${url}${port}` 65 | } 66 | // 云函数入口函数 67 | exports.main = async (event, context) => { 68 | const wxContext = cloud.getWXContext() 69 | cloud.updateConfig({ 70 | env: wxContext.ENV 71 | }) 72 | db = cloud.database() 73 | const {userName, password, alias, id, port, url, isHttps, isLocalNetwork} = event 74 | const type = event.type === 'qbittorrent' ? 'qbitorrent' : event.type 75 | let isDefault = event.isDefault 76 | try { 77 | const encryptedData = encrypt(APP_SECRET, JSON.stringify({ 78 | type, 79 | userName, 80 | password, 81 | port, 82 | url, 83 | isLocalNetwork, 84 | isHttps, 85 | urlAlias: getServerUrl(event) 86 | })) 87 | if (isDefault) { 88 | await db.collection('serverList').where({ 89 | openid: wxContext.OPENID, 90 | isDefault: true 91 | }).update({ 92 | data: { 93 | isDefault: false 94 | } 95 | }) 96 | } 97 | const res = await editServer({ 98 | encryptedData, 99 | isDefault, 100 | alias, 101 | type, 102 | id, 103 | isLocalNetwork, 104 | openid: wxContext.OPENID 105 | }) 106 | return { 107 | code: 1, 108 | msg: '编辑成功', 109 | data: { 110 | ...res, 111 | encryptedData 112 | } 113 | } 114 | } catch (e) { 115 | console.log(e.message) 116 | return { 117 | code: -1, 118 | msg: '编辑失败' 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/components/picker-view.wpy: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 取消 9 | 确定 10 | 11 | 15 | 16 | 19 | {{item}} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 79 | 124 | -------------------------------------------------------------------------------- /src/components/action-sheet.wpy: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | {{title}} 8 | 10 | 11 | 12 | 13 | 14 | {{item.label}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 64 | 143 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import logger from './log' 2 | import TransClient from '../server' 3 | const api = (funcName, data, options = {}) => { 4 | const defaults = { 5 | needAuth: false, 6 | hideLoading: false 7 | } 8 | const option = { 9 | ...defaults, 10 | ...options 11 | } 12 | if (!option.hideLoading) { 13 | wx.showLoading({mask: true}) 14 | } 15 | return new Promise((resolve, reject) => { 16 | const params = { 17 | ...data 18 | } 19 | if (option.needAuth) { 20 | if (!params.encryptedData) { 21 | reject(new Error('缺少服务器信息')) 22 | } 23 | } 24 | wx.cloud.callFunction({ 25 | name: funcName, 26 | data: params 27 | }).then(res => { 28 | const result = res.result 29 | if (result.code === -1 || result.code >= 400) { 30 | throw new Error(result.msg) 31 | } 32 | 33 | if (!option.hideLoading && !result.action) { 34 | wx.hideLoading() 35 | } 36 | // 局域网返回字段中包含action 37 | if (result.action) { 38 | sendLocalRequest(result, resolve, reject) 39 | } else { 40 | resolve(result) 41 | } 42 | }).catch(error => { 43 | if (!option.hideLoading) { 44 | wx.hideLoading() 45 | } 46 | logger.error(error) 47 | if (error.errMsg && (error.errMsg.includes('timeout') || error.errMsg.includes('timed out'))) { 48 | reject(new Error('连接超时')) 49 | } 50 | const msg = error.errMsg || error.message 51 | const message = msg.length > 40 ? '服务器异常' : msg 52 | reject(new Error(message)) 53 | }) 54 | }) 55 | } 56 | const sendLocalRequest = async (result, resolve, reject) => { 57 | let client = new TransClient(result.data) 58 | client = client.init() 59 | try { 60 | const res = await client[result.action](result.params || {}) 61 | if (res.code === -1 || res.code >= 400) { 62 | throw new Error(res.msg) 63 | } 64 | console.log(res) 65 | resolve(res) 66 | } catch (error) { 67 | console.log('sendLocalRequest', error) 68 | const msg = error.errMsg || error.message 69 | if (msg && (msg.includes('timed out') || msg.includes('timeout'))) { 70 | reject(new Error('连接超时')) 71 | } 72 | reject(new Error(msg)) 73 | } finally { 74 | wx.hideLoading() 75 | } 76 | } 77 | const cookieParse = (cookie) => { 78 | const cookieData = cookie.split(';')[0].split('=') 79 | return { 80 | key: cookieData[0], 81 | value: cookieData[1] 82 | } 83 | } 84 | 85 | const base64Encode = (string) => { 86 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 87 | let result = '' 88 | 89 | var i = 0 90 | do { 91 | let a = string.charCodeAt(i++) 92 | let b = string.charCodeAt(i++) 93 | let c = string.charCodeAt(i++) 94 | 95 | a = a || 0 96 | b = b || 0 97 | c = c || 0 98 | 99 | let b1 = (a >> 2) & 0x3F 100 | let b2 = ((a & 0x3) << 4) | ((b >> 4) & 0xF) 101 | let b3 = ((b & 0xF) << 2) | ((c >> 6) & 0x3) 102 | let b4 = c & 0x3F 103 | 104 | if (!b) { 105 | b3 = b4 = 64 106 | } else if (!c) { 107 | b4 = 64 108 | } 109 | 110 | result += characters.charAt(b1) + characters.charAt(b2) + characters.charAt(b3) + characters.charAt(b4) 111 | } while (i < string.length) 112 | 113 | return result 114 | } 115 | const makeFormdata = (obj = {}) => { 116 | let result = '' 117 | for (let name of Object.keys(obj)) { 118 | let value = obj[name] 119 | result += 120 | '\r\n--transclient' + 121 | '\r\nContent-Disposition: form-data; name="' + name + '"' + 122 | '\r\n' + 123 | '\r\n' + value 124 | } 125 | return result + '\r\n--transclient--' 126 | } 127 | 128 | export { 129 | api, 130 | cookieParse, 131 | makeFormdata, 132 | base64Encode 133 | } 134 | -------------------------------------------------------------------------------- /src/app.wpy: -------------------------------------------------------------------------------- 1 | 9 | 10 | 167 | -------------------------------------------------------------------------------- /functions/add-torrent/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const rp = require('request-promise') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const TransClient = require('@techmovie/trans-client-server').default 7 | const crypto = require('crypto') 8 | cloud.init({ 9 | env: cloud.DYNAMIC_CURRENT_ENV, 10 | traceUser: true 11 | }) 12 | 13 | const APP_SECRET = '替换为自己的APP_SECRET' 14 | 15 | const decrypt = (key, raw) => { 16 | try { 17 | raw = Buffer.from(raw, 'base64') 18 | 19 | const iv = raw.slice(0, 16) 20 | let rawData = raw.slice(16, raw.length) 21 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 22 | 23 | let decoded = decipher.update(rawData, 'binary', 'utf8') 24 | decoded += decipher.final('utf8') 25 | 26 | return JSON.parse(decoded) 27 | } catch (error) { 28 | throw new Error(error.message) 29 | } 30 | } 31 | const getFileBufferFromUrl = (url) => { 32 | return new Promise((resolve, reject) => { 33 | try { 34 | const httpStream = rp({ 35 | method: 'GET', 36 | url 37 | }) 38 | const fileName = `${new Date().getTime()}.torrent` 39 | const writeStream = fs.createWriteStream(path.join('/tmp', fileName)) 40 | httpStream.pipe(writeStream) 41 | const fileBuffer = [] 42 | httpStream.on('data', (chunk) => { 43 | fileBuffer.push(chunk) 44 | }) 45 | writeStream.on('close', async () => { 46 | resolve({ 47 | file: Buffer.concat(fileBuffer), 48 | fileName 49 | }) 50 | }) 51 | } catch (error) { 52 | reject(error) 53 | } 54 | }) 55 | } 56 | // 云函数入口函数 57 | exports.main = async (event, context) => { 58 | const wxContext = cloud.getWXContext() 59 | cloud.updateConfig({ 60 | env: wxContext.ENV 61 | }) 62 | try { 63 | const {type, url, paused, savePath, upLimit, dlLimit, fileId, skipCheck = false, rootFolder = false} = event 64 | const serverInfo = decrypt(APP_SECRET, event.encryptedData) 65 | const {type: serverType, urlAlias, userName, password} = serverInfo 66 | const serverData = { 67 | type: serverType, 68 | url: urlAlias, 69 | username: userName, 70 | password 71 | } 72 | let client = new TransClient(serverData) 73 | client = client.init() 74 | if (type === 'url') { 75 | if (serverType === 'utorrent') { 76 | console.log('ut url上传') 77 | const {file, fileName} = await getFileBufferFromUrl(url) 78 | if (serverInfo.isLocalNetwork) { 79 | return { 80 | action: 'addTorrentFile', 81 | params: { 82 | torrent: file.toString('base64'), 83 | paused, 84 | savepath: savePath 85 | }, 86 | data: serverData 87 | } 88 | } 89 | await client.addTorrentFile({ 90 | torrent: file, 91 | paused, 92 | savepath: savePath 93 | }) 94 | fs.unlinkSync(path.join('/tmp', fileName)) 95 | } else { 96 | console.log('url上传') 97 | if (serverInfo.isLocalNetwork) { 98 | return { 99 | action: 'addTorrentsUrl', 100 | params: { 101 | urls: url, 102 | paused, 103 | skipCheck, 104 | rootFolder, 105 | savepath: savePath, 106 | upLimit: upLimit > 0 ? upLimit * 1000 : -1, 107 | dlLimit: dlLimit > 0 ? dlLimit * 1000 : -1 108 | }, 109 | data: serverData 110 | } 111 | } 112 | await client.addTorrentsUrl({ 113 | urls: url, 114 | paused, 115 | skipCheck, 116 | rootFolder, 117 | savepath: savePath, 118 | upLimit: upLimit > 0 ? upLimit * 1000 : -1, 119 | dlLimit: dlLimit > 0 ? dlLimit * 1000 : -1 120 | }) 121 | } 122 | } else { 123 | if (serverInfo.isLocalNetwork) { 124 | return { 125 | action: 'addTorrentFile', 126 | params: { 127 | fileId: fileId, 128 | paused, 129 | skipCheck, 130 | rootFolder, 131 | savepath: savePath, 132 | upLimit: upLimit > 0 ? upLimit * 1000 : -1, 133 | dlLimit: dlLimit > 0 ? dlLimit * 1000 : -1 134 | }, 135 | data: serverData 136 | } 137 | } 138 | const fileData = await cloud.downloadFile({ 139 | fileID: fileId 140 | }) 141 | console.log('文件上传:', fileId) 142 | const buffer = fileData.fileContent 143 | await cloud.deleteFile({ 144 | fileList: [fileId] 145 | }) 146 | await client.addTorrentFile({ 147 | torrent: Buffer.from(buffer, 'base64'), 148 | paused, 149 | skipCheck, 150 | rootFolder, 151 | savepath: savePath, 152 | upLimit: upLimit > 0 ? upLimit * 1000 : -1, 153 | dlLimit: dlLimit > 0 ? dlLimit * 1000 : -1 154 | }) 155 | } 156 | return { 157 | code: 1, 158 | msg: '添加成功' 159 | } 160 | } catch (error) { 161 | console.log(error.message) 162 | return { 163 | code: -1, 164 | msg: error.message, 165 | data: null 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /functions/test-server/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | cloud.init({ 4 | env: cloud.DYNAMIC_CURRENT_ENV, 5 | traceUser: true 6 | }) 7 | const TransClient = require('@techmovie/trans-client-server').default 8 | const crypto = require('crypto') 9 | const APP_SECRET = '替换为自己的APP_SECRET' 10 | let db = null 11 | const encrypt = (key, raw) => { 12 | var iv = crypto.randomBytes(16) 13 | key = Buffer.from(key) 14 | 15 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv) 16 | 17 | let crypted = cipher.update(raw, 'utf8', 'binary') 18 | crypted += cipher.final('binary') 19 | crypted = Buffer.from(crypted, 'binary') 20 | 21 | // 拼接iv串 22 | let enc = Buffer.concat([iv, Buffer.from(crypted, 'base64')]) 23 | enc = enc.toString('base64') 24 | 25 | return enc 26 | } 27 | const getServerList = async (openid) => { 28 | try { 29 | const res = await db.collection('serverList').where({ 30 | openid, 31 | isDefault: true 32 | }).get() 33 | return res.data 34 | } catch (e) { 35 | throw new Error(e) 36 | } 37 | } 38 | const editServer = async (data) => { 39 | const {openid, id} = data 40 | try { 41 | delete data.openid 42 | delete data.id 43 | return await db.collection('serverList').where({ 44 | openid, 45 | _id: id 46 | }).update({ 47 | data: { 48 | ...data 49 | } 50 | }) 51 | } catch (e) { 52 | throw new Error(e) 53 | } 54 | } 55 | const addServer = async (data) => { 56 | try { 57 | return await db.collection('serverList').add({ 58 | data 59 | }) 60 | } catch (e) { 61 | throw new Error(e) 62 | } 63 | } 64 | const getServerUrl = (data) => { 65 | let protocol = data.isHttps ? 'https://' : 'http://' 66 | let {url, port} = data 67 | port = port ? ':' + port : '' 68 | if (url.endsWith('/')) { 69 | url = url.substr(0, url.length - 1) 70 | console.log(url) 71 | } 72 | if (/^(http|https):\/\/\w+/.test(url)) { 73 | protocol = '' 74 | } 75 | if (/.+(:\d+)$/.test(url)) { 76 | port = '' 77 | } 78 | console.log(url) 79 | if (/.+(\/)$/.test(url)) { 80 | url = url.substring(0, url.length - 1) 81 | } 82 | console.log(`${protocol}${url}${port}`) 83 | return `${protocol}${url}${port}` 84 | } 85 | const confirmAddServer = async (event, openid) => { 86 | const {userName, password, isDefault, alias, port, url, isHttps, isLocalNetwork} = event 87 | const type = event.type === 'qbittorrent' ? 'qbitorrent' : event.type 88 | delete event.confirm 89 | const encryptedData = encrypt(APP_SECRET, JSON.stringify({ 90 | type, 91 | userName, 92 | password, 93 | port, 94 | url, 95 | isHttps, 96 | isLocalNetwork, 97 | urlAlias: getServerUrl(event) 98 | })) 99 | const serverList = await getServerList(openid)// 是否有已设置为默认的服务器 100 | if (serverList.length > 0 && isDefault) { 101 | const server = serverList[0] 102 | await editServer({ 103 | id: server._id, 104 | openid: openid, 105 | isDefault: false 106 | }) 107 | } 108 | const res = await addServer({ 109 | encryptedData, 110 | isDefault: isDefault, 111 | alias, 112 | type, 113 | isLocalNetwork, 114 | openid: openid, 115 | createTime: db.serverDate() 116 | }) 117 | return { 118 | code: 1, 119 | msg: '添加成功', 120 | data: { 121 | ...res, 122 | encryptedData 123 | } 124 | } 125 | } 126 | // 云函数入口函数 127 | exports.main = async (event) => { 128 | const wxContext = cloud.getWXContext() 129 | cloud.updateConfig({ 130 | env: wxContext.ENV 131 | }) 132 | db = cloud.database() 133 | const {userName, password, confirm = false} = event 134 | const type = event.type === 'qbittorrent' ? 'qbitorrent' : event.type 135 | try { 136 | // 添加服务器到数据库 137 | if (confirm) { 138 | return confirmAddServer(event, wxContext.OPENID) 139 | } 140 | if (type === 'qbitorrent' && (!userName || !password)) { 141 | throw new Error('请填写用户名密码') 142 | } 143 | if (type === 'deluge' && (!password)) { 144 | throw new Error('请填写密码') 145 | } 146 | const serverData = { 147 | type, 148 | url: getServerUrl(event), 149 | username: userName, 150 | password 151 | } 152 | if (event.isLocalNetwork) { 153 | return { 154 | action: 'testServer', 155 | data: serverData 156 | } 157 | } 158 | let client = new TransClient(serverData) 159 | client = client.init() 160 | const res = await client.testServer() 161 | console.log(res) 162 | console.log('openid:', wxContext.OPENID) 163 | console.log('type:', type) 164 | console.log('test-server:', res) 165 | if (res.code === 401 || res.code === 403) { 166 | throw new Error('请检查用户名或密码') 167 | } 168 | if (res.msg.includes('openssl')) { 169 | throw new Error('HTTPS连接错误') 170 | } 171 | if (res.code === -1 && res.msg.includes('ENOTFOUND')) { 172 | throw new Error('URL地址不存在') 173 | } 174 | if (res.code === 301) { 175 | throw new Error('请检查URL或端口号') 176 | } 177 | return res 178 | } catch (error) { 179 | let msg = error.errorMessage || error.message 180 | console.log(error) 181 | if (msg.includes('timed out')) { 182 | msg = '连接超时' 183 | } 184 | return { 185 | code: -1, 186 | msg 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/filter/index.wxs: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | var getIconType = function (type) { 3 | return type !== 'downloadStation' ? type.substring(0, 2).toUpperCase() : 'DS' 4 | } 5 | var getIconColor = function (type) { 6 | var typeObj = { 7 | qbitorrent: '#346EBE', 8 | transmission: '#960000', 9 | deluge: '#719BC9', 10 | utorrent: '#03994B', 11 | rutorrent: '#3E3EE6', 12 | downloadStation: '#FA8B00' 13 | } 14 | return typeObj[type] 15 | } 16 | var toTruncFixed = function (num, len) { 17 | num = num || 0 18 | var times = Math.pow(10, len) 19 | var result = num * times + 0.5 20 | result = parseInt(result, 10) / times 21 | return result 22 | } 23 | var getSize = function (bytes, unitSetting) { 24 | var size_K = unitSetting || 1000 25 | var size_K_str = size_K === 1000 ? 'KB' : 'KiB' 26 | var size_M_str = size_K === 1000 ? 'MB' : 'MiB' 27 | var size_G_str = size_K === 1000 ? 'GB' : 'GiB' 28 | var size_T_str = size_K === 1000 ? 'TB' : 'TiB' 29 | if (bytes < 0) { 30 | return '未知' 31 | } 32 | if (bytes < size_K || !bytes) { 33 | return 0 + size_K_str 34 | } 35 | var convertedSize 36 | var unit 37 | 38 | if (bytes < Math.pow(size_K, 2)) { 39 | convertedSize = bytes / size_K 40 | unit = size_K_str 41 | } else if (bytes < Math.pow(size_K, 3)) { 42 | convertedSize = bytes / Math.pow(size_K, 2) 43 | unit = size_M_str 44 | } else if (bytes < Math.pow(size_K, 4)) { 45 | convertedSize = bytes / Math.pow(size_K, 3) 46 | unit = size_G_str 47 | } else { 48 | convertedSize = bytes / Math.pow(size_K, 4) 49 | unit = size_T_str 50 | } 51 | return convertedSize <= 9.995 52 | ? [toTruncFixed(convertedSize, 2), unit].join(' ') 53 | : [toTruncFixed(convertedSize, 1), unit].join(' ') 54 | } 55 | var getSpeed = function (KBps, unitSetting) { 56 | var speed_K = unitSetting || 1000 57 | var speed_K_str = speed_K === 1000 ? 'KB/s' : 'KiB/s' 58 | var speed_M_str = speed_K === 1000 ? 'MB/s' : 'MiB/s' 59 | var speed_G_str = speed_K === 1000 ? 'GB/s' : 'GiB/s' 60 | var speed = KBps / speed_K 61 | if (!KBps) { 62 | return 0 + ' ' + speed_K_str 63 | } 64 | if (KBps < 0) { 65 | return '∞' 66 | } 67 | if (speed <= 999.95) { 68 | // 0 KBps to 999 K 69 | return [toTruncFixed(speed, 0), speed_K_str].join(' ') 70 | } 71 | speed /= speed_K 72 | 73 | if (speed <= 99.995) { 74 | // 1 M to 99.99 M 75 | return [toTruncFixed(speed, 1), speed_M_str].join(' ') 76 | } 77 | if (speed <= 999.95) { 78 | // 100 M to 999.9 M 79 | return [toTruncFixed(speed, 0), speed_M_str].join(' ') 80 | } 81 | speed /= speed_K 82 | return [toTruncFixed(speed, 2), speed_G_str].join(' ') 83 | } 84 | var getTorrentColor = function (state) { 85 | var statusObj = { 86 | seeding: '#7ED321', 87 | downloading: '#7ED321', 88 | paused: '#CDCDCD;', 89 | error: '#FF0000', 90 | checking: '#7ED321', 91 | queued: '#CDCDCD' 92 | } 93 | return statusObj[state] 94 | } 95 | var getRatio = function (ratio) { 96 | return ratio < 0 ? 0 : toTruncFixed(ratio, 2) 97 | } 98 | var addZero = function (num) { 99 | return num >= 10 ? num : '0' + num 100 | } 101 | var getEta = function (seconds) { 102 | if (seconds >= 8640000 || seconds < 0) { 103 | return '∞' 104 | } 105 | var day = parseInt(seconds / (24 * 3600)) 106 | var hour = parseInt((seconds / 3600) % 24) 107 | var minute = parseInt((seconds / 60) % 60) 108 | var second = parseInt(seconds % 60) 109 | if (day > 0) { 110 | return day + '天' + hour + '小时' 111 | } else if (hour > 0) { 112 | return hour + '小时' + minute + '分钟' 113 | } else if (minute > 0) { 114 | return minute + '分钟' + second + '秒' 115 | } 116 | return second + '秒' 117 | } 118 | var formatDate = function (timestring) { 119 | if (!timestring) { 120 | return '' 121 | } 122 | // eslint-disable-next-line no-undef 123 | var date = getDate(timestring) 124 | var Y = date.getFullYear() + '-' 125 | var M = addZero(date.getMonth() + 1) + '-' 126 | var D = addZero(date.getDate()) + ' ' 127 | var h = addZero(date.getHours()) + ':' 128 | var m = addZero(date.getMinutes()) + ':' 129 | var s = addZero(date.getSeconds()) 130 | return Y + M + D + h + m + s 131 | } 132 | var getBtnPermission = function (type, action) { 133 | if (!type) { 134 | return false 135 | } 136 | var btnPermission = { 137 | qbitorrent: [ 138 | 'setTorrentName', 139 | 'setLocation', 140 | 'setDlLimit', 141 | 'setUpLimit', 142 | 'setTag', 143 | 'updateTracker', 144 | 'recheck' 145 | ], 146 | transmission: [ 147 | 'setLocation', 148 | 'setDlLimit', 149 | 'setUpLimit', 150 | 'setTag', 151 | 'updateTracker', 152 | 'recheck' 153 | ], 154 | deluge: [ 155 | 'setLocation', 156 | 'setDlLimit', 157 | 'setUpLimit', 158 | 'updateTracker', 159 | 'recheck' 160 | ], 161 | rutorrent: ['setLocation', 'updateTracker', 'recheck', 'setTag'], 162 | downloadStation: ['setLocation'], 163 | utorrent: ['recheck', 'setDlLimit', 'setUpLimit', 'setTag'] 164 | } 165 | return btnPermission[type].indexOf(action) > -1 166 | } 167 | module.exports = { 168 | getIconType: getIconType, 169 | getIconColor: getIconColor, 170 | getSize: getSize, 171 | getSpeed: getSpeed, 172 | toTruncFixed: toTruncFixed, 173 | getTorrentColor: getTorrentColor, 174 | getRatio: getRatio, 175 | getEta: getEta, 176 | formatDate: formatDate, 177 | getBtnPermission: getBtnPermission 178 | } 179 | -------------------------------------------------------------------------------- /src/pages/help.wpy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 服务器地址 6 | 7 | 8 | 安装有下载客户端的 NAS 或者购买的盒子会有对应的 IP 或者域名,TransClient 目前同时支持局域网和广域网。在添加局域网服务器时,请确保手机和下载服务器在用一个局域网内。 9 | 10 | 11 | 有关公网的设置可以参考: 13 | 公网设置 14 | 15 | 16 | 17 | 示例 18 | 19 | 20 | 假如 NAS 的访问地址是 http://nas.example.com ,NAS 里安装的 QB 的端口号是 1000,则需按如下填写,需要注意的是的如果不支持 HTTPS,则是否为 HTTPS 不要勾选,否则会报错。 21 | 如果 Transmission 的访问地址为 http://nas.example.com:9091/transmission/web, 服务器地址只需填入 「nas.example.com」即可,/transmission/web 无需填写。 22 | 23 | 27 | 28 | 31 | 32 | 33 | 如果想要添加购买的盒子,则有几种情况需要注意。 34 | 35 | 36 | 如果是购买的独立 IP 的盒子,服务器地址一栏填入服务器IP就可以。 37 | 38 | 39 | 如果是购买的共享盒子,则 QB 或者 TR 的访问地址会是盒子商提供的域名。 40 | 41 | 42 | 以 seedhost 为例,Deluge的地址为 https://cream.seedhost.eu/username/deluge,需按如下填写,由于 HTTPS 没有指定端口号,端口号默认为 443,可以不填。是否为 HTTPS 必须勾选。 43 | 46 | 47 | 48 | Transmission 的设置比较特殊,seedhost 的 Transmission 地址为 https://cream.seedhost.eu/username/transission/web,在填写服务器地址时只需填入 「cream.seedhost.eu/username」 即可。 49 | 52 | 53 | 54 | HTTPS 55 | 56 | 57 | 首先需要确认添加的服务器地址是否开启 HTTPS 访问,如果没有则不需要勾选,否则会连接失败。 58 | 如果 HTTPS 服务器地址指定了端口号,则需要填写对应的端口号,如果服务器地址不带端口就可以访问,则不需要填写,HTTPS 默认端口号为 443。 59 | 60 | 61 | 端口号 62 | 63 | 64 | 这里需要说明的是 Deluge 的端口号不是 Daemon port,而是访问 Deluge 客户端需要在浏览器输入的地址后面的端口号。另外两个客户端也是一样。 65 | 68 | 69 | 70 | 别名 71 | 72 | 73 | 别名是在 TransClient 添加服务器后对服务器增加的标识,在小程序中更容易区分各个客户端,可以随意填写。 74 | 75 | 76 | 用户名密码 77 | 78 | 79 | 这里的用户名密码指的都是访问客户端Web用户界面的用户名和密码。QB 和 UT 的用户名和密码和必填的,否则不会添加成功。TR 如果没有设置过用户名和密码,可以不填。Deluge 只需填入密码即可,Deluge 的密码为访问Web用户界面时需要输入的密码。 80 | 83 | 84 | 85 | 如果 QB 密码和用户名都输入无误,但依旧不能连接成功,需要在选项设置里将以下两项取消勾选。 86 | 89 | 90 | 91 | 客户端类型 92 | 93 | 94 | 在填完以上信息后,客户端类型必须要选择正确,否则也没办法连接成功。如果以上信息都输入无误但依然没办法连接成功,需要检查客户端类型是否选择正确。 95 | 目前支持 qBittorrent、Transmmison、Deluge、µTorrent、rTorrent、downloadStation 6款下载客户端。 96 | 97 | 98 | 是否为默认 99 | 100 | 101 | 如果这一项没有勾选,则最新添加的服务器自动设为默认。设为默认后在种子列表页就会展示默认服务器里的种子列表。如果想要修改默认服务器,可以在服务器列表页左滑对应的服务器后点击「设为默认」进行修改。 102 | 103 | 104 | 依然无法连接成功 105 | 106 | 107 | 点击联系作者按钮跟我反馈问题。 108 | 109 | 联系作者 113 | 114 | 115 | 116 | 117 | 134 | 182 | -------------------------------------------------------------------------------- /src/components/slideview/slideview.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 41 | /******/ } 42 | /******/ }; 43 | /******/ 44 | /******/ // define __esModule on exports 45 | /******/ __webpack_require__.r = function(exports) { 46 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 47 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 48 | /******/ } 49 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 50 | /******/ }; 51 | /******/ 52 | /******/ // create a fake namespace object 53 | /******/ // mode & 1: value is a module id, require it 54 | /******/ // mode & 2: merge all properties of value into the ns 55 | /******/ // mode & 4: return value when already ns object 56 | /******/ // mode & 8|1: behave like require 57 | /******/ __webpack_require__.t = function(value, mode) { 58 | /******/ if(mode & 1) value = __webpack_require__(value); 59 | /******/ if(mode & 8) return value; 60 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 61 | /******/ var ns = Object.create(null); 62 | /******/ __webpack_require__.r(ns); 63 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 64 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 65 | /******/ return ns; 66 | /******/ }; 67 | /******/ 68 | /******/ // getDefaultExport function for compatibility with non-harmony modules 69 | /******/ __webpack_require__.n = function(module) { 70 | /******/ var getter = module && module.__esModule ? 71 | /******/ function getDefault() { return module['default']; } : 72 | /******/ function getModuleExports() { return module; }; 73 | /******/ __webpack_require__.d(getter, 'a', getter); 74 | /******/ return getter; 75 | /******/ }; 76 | /******/ 77 | /******/ // Object.prototype.hasOwnProperty.call 78 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 79 | /******/ 80 | /******/ // __webpack_public_path__ 81 | /******/ __webpack_require__.p = ""; 82 | /******/ 83 | /******/ 84 | /******/ // Load entry module and return exports 85 | /******/ return __webpack_require__(__webpack_require__.s = 18); 86 | /******/ }) 87 | /************************************************************************/ 88 | /******/ ({ 89 | 90 | /***/ 18: 91 | /***/ (function(module, exports, __webpack_require__) { 92 | 93 | "use strict"; 94 | 95 | 96 | Component({ 97 | options: { 98 | addGlobalClass: true, 99 | multipleSlots: true 100 | }, 101 | properties: { 102 | extClass: { 103 | type: String, 104 | value: '' 105 | }, 106 | buttons: { 107 | type: Array, 108 | value: [], 109 | observer: function observer(newVal) { 110 | this.addClassNameForButton(); 111 | } 112 | }, 113 | disable: { 114 | type: Boolean, 115 | value: false 116 | }, 117 | icon: { 118 | type: Boolean, 119 | value: false 120 | }, 121 | show: { 122 | type: Boolean, 123 | value: false 124 | }, 125 | duration: { 126 | type: Number, 127 | value: 350 128 | }, 129 | throttle: { 130 | type: Number, 131 | value: 40 132 | }, 133 | rebounce: { 134 | type: Number, 135 | value: 0 136 | } 137 | }, 138 | data: { 139 | size: null 140 | }, 141 | ready: function ready() { 142 | this.updateRight(); 143 | this.addClassNameForButton(); 144 | }, 145 | 146 | methods: { 147 | updateRight: function updateRight() { 148 | var _this = this; 149 | 150 | var data = this.data; 151 | var query = wx.createSelectorQuery().in(this); 152 | query.select('.left').boundingClientRect(function (res) { 153 | console.log('right res', res); 154 | var btnQuery = wx.createSelectorQuery().in(_this); 155 | btnQuery.selectAll('.btn').boundingClientRect(function (rects) { 156 | console.log('btn rects', rects); 157 | _this.setData({ 158 | size: { 159 | buttons: rects, 160 | button: res, 161 | show: data.show, 162 | disable: data.disable, 163 | throttle: data.throttle, 164 | rebounce: data.rebounce 165 | } 166 | }); 167 | }).exec(); 168 | }).exec(); 169 | }, 170 | addClassNameForButton: function addClassNameForButton() { 171 | var _data = this.data, 172 | buttons = _data.buttons, 173 | icon = _data.icon; 174 | 175 | buttons.forEach(function (btn) { 176 | if (icon) { 177 | btn.className = ''; 178 | } else if (btn.type === 'warn') { 179 | btn.className = 'weui-slideview__btn-group_warn'; 180 | } else { 181 | btn.className = 'weui-slideview__btn-group_default'; 182 | } 183 | }); 184 | this.setData({ 185 | buttons: buttons 186 | }); 187 | }, 188 | buttonTapByWxs: function buttonTapByWxs(data) { 189 | this.triggerEvent('buttontap', data, {}); 190 | }, 191 | hide: function hide() { 192 | this.triggerEvent('hide', {}, {}); 193 | }, 194 | show: function show() { 195 | this.triggerEvent('show', {}, {}); 196 | }, 197 | transitionEnd: function transitionEnd() { 198 | console.log('transitiion end'); 199 | } 200 | } 201 | }); 202 | 203 | /***/ }) 204 | 205 | /******/ }); -------------------------------------------------------------------------------- /src/pages/me.wpy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 扫码加入组织🌝 19 | 20 | 电影|PT|NAS交流 21 | 22 | 23 | 24 | 26 | 打赏作者☕ 27 | 28 | 29 | 30 | 31 | 32 | 33 | 推荐给好友👬 35 | 36 | 37 | 38 | 系统设置 40 | 41 | 42 | 43 | 客服咨询 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 56 | 58 | 在会话窗口发送数字「2」, 关注作者公众号 59 | 60 | 发送口令 64 | 65 | 66 | 67 | 68 | 69 | 148 | 289 | -------------------------------------------------------------------------------- /src/components/slideview/slideview.wxs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var touchstart = function(event, ownerInstance) { 3 | var ins = event.instance 4 | var st = ins.getState() 5 | if (st.disable) return // disable的逻辑 6 | // console.log('touchstart st', JSON.stringify(st)) 7 | if (!st.size) return 8 | // console.log('touchstart', JSON.stringify(event)) 9 | st.isMoving = true 10 | st.startX = event.touches[0].pageX 11 | st.startY = event.touches[0].pageY 12 | st.firstAngle = 0 13 | } 14 | var touchmove = function(event, ownerInstance) { 15 | var ins = event.instance 16 | var st = ins.getState() 17 | if (!st.size || !st.isMoving) return 18 | // console.log('touchmove', JSON.stringify(event)) 19 | var pagex = event.touches[0].pageX - st.startX 20 | var pagey = event.touches[0].pageY - st.startY 21 | // 左侧45度角为界限,大于45度则允许水平滑动 22 | if (st.firstAngle === 0) { 23 | st.firstAngle = Math.abs(pagex) - Math.abs(pagey) 24 | } 25 | if (st.firstAngle < 0) { 26 | return 27 | } 28 | var movex = pagex > 0 ? Math.min(st.max, pagex) : Math.max(-st.max, pagex) 29 | // 往回滑动的情况 30 | if (st.out) { 31 | // 已经是划出来了,还要往左滑动,忽略 32 | if (movex < 0) return 33 | ins.setStyle({ 34 | 'transform': 'translateX(' + (st.transformx + movex) + 'px)', 35 | 'transition': '' 36 | }) 37 | var btns = ownerInstance.selectAllComponents('.btn') 38 | var transformTotal = 0 39 | var len = btns.length 40 | var i = len - 1; 41 | for (;i >= 0; i--) { 42 | var transform = st.size.buttons[i].width / st.max * movex 43 | var transformx = st.size.buttons[i].max - Math.min(st.size.buttons[i].max, transform + transformTotal) 44 | btns[i].setStyle({ 45 | 'transform': 'translateX(' + (-transformx) + 'px)', 46 | 'transition': '' 47 | }) 48 | transformTotal += transform 49 | } 50 | return false 51 | } 52 | if (movex > 0) movex = 0 53 | ins.setStyle({ 54 | 'transform': 'translateX(' + movex + 'px)', 55 | 'transition': '' 56 | }) 57 | st.transformx = movex 58 | var btns = ownerInstance.selectAllComponents('.btn') 59 | var transformTotal = 0 60 | var len = btns.length 61 | var i = len - 1; 62 | for (;i >= 0; i--) { 63 | var transform = st.size.buttons[i].width / st.max * movex 64 | var transformx = Math.max(-st.size.buttons[i].max, transform + transformTotal) 65 | btns[i].setStyle({ 66 | 'transform': 'translateX(' + transformx + 'px)', 67 | 'transition': '' 68 | }) 69 | st.size.buttons[i].transformx = transformx 70 | transformTotal += transform 71 | } 72 | return false // 禁止垂直方向的滑动 73 | } 74 | var touchend = function(event, ownerInstance) { 75 | var ins = event.instance 76 | var st = ins.getState() 77 | if (!st.size || !st.isMoving) return 78 | // 左侧45度角为界限,大于45度则允许水平滑动 79 | if (st.firstAngle < 0) { 80 | return 81 | } 82 | var duration = st.duration / 1000 83 | st.isMoving = false 84 | // console.log('touchend', JSON.stringify(event)) 85 | var btns = ownerInstance.selectAllComponents('.btn') 86 | var len = btns.length 87 | var i = len - 1 88 | // console.log('len size', len) 89 | if (Math.abs(event.changedTouches[0].pageX - st.startX) < st.throttle || event.changedTouches[0].pageX - st.startX > 0) { // 方向也要控制 90 | st.out = false 91 | ins.setStyle({ 92 | 'transform': 'translate3d(0px, 0, 0)', 93 | 'transition': 'transform ' + (duration) + 's' 94 | }) 95 | for (;i >= 0; i--) { 96 | btns[i].setStyle({ 97 | 'transform': 'translate3d(0px, 0, 0)', 98 | 'transition': 'transform ' + (duration) + 's' 99 | }) 100 | } 101 | ownerInstance.callMethod('hide') 102 | return 103 | } 104 | showButtons(ins, ownerInstance, duration) 105 | ownerInstance.callMethod('show') 106 | } 107 | var REBOUNCE_TIME = 0.2 108 | var showButtons = function(ins, ownerInstance, withDuration) { 109 | var st = ins.getState() 110 | if (!st.size) return 111 | var rebounceTime = st.rebounce ? REBOUNCE_TIME : 0 112 | var movex = st.max 113 | st.out = true 114 | var btns = ownerInstance.selectAllComponents('.btn') 115 | var rebounce = st.rebounce || 0 116 | var len = btns.length 117 | var i = len - 1 118 | ins.setStyle({ 119 | 'transform': 'translate3d(' + (-movex - rebounce) + 'px, 0, 0)', 120 | 'transition': 'transform ' + (withDuration) + 's' 121 | }) 122 | st.transformx = -movex 123 | var transformTotal = 0 124 | for (;i >= 0; i--) { 125 | var transform = st.size.buttons[i].width / st.max * movex 126 | var transformx = (-(transform + transformTotal)) 127 | btns[i].setStyle({ 128 | 'transform': 'translate3d(' + transformx + 'px, 0, 0)', 129 | 'transition': 'transform ' + (withDuration ? withDuration + rebounceTime : withDuration) + 's' 130 | }) 131 | st.size.buttons[i].transformx = transformx 132 | transformTotal += transform 133 | } 134 | } 135 | var innerHideButton = function(ownerInstance) { 136 | var ins = ownerInstance.selectComponent('.left') 137 | var st = ins.getState() 138 | if (!st.size) return 139 | var duration = st.duration ? st.duration / 1000 : 0 140 | var btns = ownerInstance.selectAllComponents('.btn') 141 | var len = btns.length 142 | var i = len - 1 143 | ins.setStyle({ 144 | 'transform': 'translate3d(0px, 0, 0)', 145 | 'transition': 'transform ' + (duration) + 's' 146 | }) 147 | st.transformx = 0 148 | for (;i >= 0; i--) { 149 | btns[i].setStyle({ 150 | 'transform': 'translate3d(0px, 0, 0)', 151 | 'transition': 'transform ' + (duration) + 's' 152 | }) 153 | st.size.buttons[i].transformx = 0 154 | } 155 | } 156 | var hideButton = function(event, ownerInstance) { 157 | innerHideButton(ownerInstance) 158 | ownerInstance.callMethod('buttonTapByWxs', {index: event.currentTarget.dataset.index, data: event.currentTarget.dataset.data}) 159 | return false 160 | } 161 | var sizeReady = function(newVal, oldVal, ownerInstance, ins) { 162 | var st = ins.getState() 163 | // st.disable = newVal && newVal.disable 164 | if (newVal && newVal.button && newVal.buttons) { 165 | st.size = newVal 166 | st.transformx = 0 167 | // var min = newVal.button.width 168 | var max = 0 169 | var len = newVal.buttons.length 170 | var i = newVal.buttons.length - 1; 171 | var total = 0 172 | for (; i >= 0; i--) { 173 | max += newVal.buttons[i].width 174 | // if (min > newVal.buttons[i]) { 175 | // min = newVal.buttons[i].width 176 | // } 177 | total += newVal.buttons[i].width 178 | newVal.buttons[i].max = total 179 | newVal.buttons[i].transformx = 0 180 | } 181 | st.throttle = st.size.throttle || 40 // 固定值 182 | st.rebounce = st.size.rebounce 183 | st.max = max 184 | ownerInstance.selectComponent('.right').setStyle({ 185 | 'line-height': newVal.button.height + 'px', 186 | left: (newVal.button.width) + 'px', 187 | width: max + 'px' 188 | }) 189 | // console.log('st size', JSON.stringify(newVal)) 190 | if (!st.size.disable && st.size.show) { 191 | showButtons(ins, ownerInstance) 192 | } 193 | } 194 | } 195 | var disableChange = function(newVal, oldVal, ownerInstance, ins) { 196 | var st = ins.getState() 197 | st.disable = newVal 198 | } 199 | var durationChange = function(newVal, oldVal, ownerInstance, ins) { 200 | var st = ins.getState() 201 | st.duration = newVal || 400 202 | } 203 | var showChange = function(newVal, oldVal, ownerInstance, ins) { 204 | var st = ins.getState() 205 | st.show = newVal 206 | if (st.disable) return 207 | // console.log('show change') 208 | if (st.show) { 209 | showButtons(ins, ownerInstance, st.duration) 210 | } else { 211 | innerHideButton(ownerInstance) 212 | } 213 | } 214 | var rebounceChange = function(newVal, oldVal, ownerInstance, ins) { 215 | var st = ins.getState() 216 | // console.log('rebounce', st.rebounce) 217 | st.rebounce = newVal 218 | } 219 | var transitionEnd = function(event, ownerInstance) { 220 | // console.log('transition') 221 | var ins = event.instance 222 | var st = ins.getState() 223 | // 回弹效果 224 | if (st.out && st.rebounce) { 225 | console.log('transition rebounce', st.rebounce) 226 | ins.setStyle({ 227 | 'transform': 'translate3d(' + (-st.max) + 'px, 0, 0)', 228 | 'transition': 'transform ' + REBOUNCE_TIME +'s' 229 | }) 230 | } 231 | } 232 | module.exports = { 233 | touchstart: touchstart, 234 | touchmove: touchmove, 235 | touchend: touchend, 236 | hideButton: hideButton, 237 | sizeReady: sizeReady, 238 | disableChange: disableChange, 239 | durationChange: durationChange, 240 | showChange: showChange, 241 | rebounceChange: rebounceChange, 242 | transitionEnd: transitionEnd 243 | } -------------------------------------------------------------------------------- /src/pages/setting.wpy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 服务器列表展示历史上传总数据 20 | 23 | 24 | 26 | 目前仅支持Deluge、qBittorrent和transmission 27 | 28 | 29 | 30 | 32 | 数据换算单位 33 | {{formData.unit}} 34 | 35 | 36 | 例:1000切换为1024后对应的数据单位由GB切换为GiB 37 | 38 | 39 | 40 | 43 | 观看视频 44 | 45 | 保存 49 | 50 | 51 | 54 | 56 | 57 | 取消 58 | 确定 59 | 60 | 64 | 65 | 68 | {{item}} 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 221 | 372 | -------------------------------------------------------------------------------- /src/server/clients/downloadStation.js: -------------------------------------------------------------------------------- 1 | import wepy from 'wepy' 2 | 3 | export default class Ds { 4 | constructor (options) { 5 | this.config = { 6 | ...options, 7 | path: '/webapi' 8 | } 9 | this.auth = '' 10 | this.authType = '' 11 | } 12 | 13 | async request (api, params = {}, method = 'GET', options = {}) { 14 | const { url, path } = this.config 15 | const auth = this.auth 16 | try { 17 | if (!auth) { 18 | const data = await this.testServer() 19 | if (data.code === -1) { 20 | throw new Error(data.msg) 21 | } 22 | } 23 | const requestOptions = { 24 | method: method, 25 | url: url + path + api, 26 | data: params, 27 | timeout: 20000, 28 | header: { 29 | Cookie: `id=${this.auth}` 30 | } 31 | } 32 | const res = await wepy.request(requestOptions) 33 | let resBody = res.data 34 | if (!resBody.success) { 35 | console.log(resBody) 36 | throw new Error(resBody.error.code) 37 | } 38 | return { 39 | code: res.statusCode || 1, 40 | msg: '请求成功', 41 | data: resBody.data 42 | } 43 | } catch (err) { 44 | console.log(err.message) 45 | return { 46 | code: err.statusCode || -1, 47 | msg: err.errMsg || err.message, 48 | data: null 49 | } 50 | } 51 | } 52 | 53 | async testServer () { 54 | const { url, path, username, password } = this.config 55 | const requestUrl = url + path + '/auth.cgi' 56 | try { 57 | const requestOptions = { 58 | method: 'GET', 59 | url: requestUrl, 60 | timeout: 13000, 61 | data: { 62 | api: 'SYNO.API.Auth', 63 | version: 2, 64 | method: 'login', 65 | account: username, 66 | passwd: password, 67 | session: 'DownloadStation', 68 | format: 'Cookie' 69 | }, 70 | header: { 71 | 'content-type': 'application/json' 72 | } 73 | } 74 | let res = await wepy.request(requestOptions) 75 | const resBody = res.data 76 | if (!resBody.data.sid) { 77 | throw new Error('身份验证失败') 78 | } 79 | this.auth = resBody.data.sid 80 | console.log('auth:', this.auth) 81 | return { 82 | code: 0, 83 | msg: '授权成功', 84 | data: { 85 | auth: this.auth 86 | } 87 | } 88 | } catch (err) { 89 | throw new Error(err.errMsg || err.message) 90 | } 91 | } 92 | 93 | getTorrentInfo (id) { 94 | return this.getTorrentList({ id }) 95 | } 96 | 97 | async getTorrentList (params) { 98 | const { sort = 'added_on', page = 1, pageSize = 20, reverse = true, filter = 'all', id = '' } = params 99 | 100 | const res = await this.request( 101 | '/DownloadStation/task.cgi', 102 | { 103 | id: [id], 104 | api: 'SYNO.DownloadStation.Task', 105 | version: 1, 106 | method: 'list', 107 | additional: 'detail,transfer,tracker' 108 | } 109 | ) 110 | let result = null 111 | if (res.code === -1 || !res.data || res.code > 200) { 112 | result = res 113 | } else { 114 | const transformData = res.data.tasks.map(item => { 115 | return this.transformTorrentList(item) 116 | }) 117 | let orderList = null 118 | if (id && transformData.length <= 1) { 119 | orderList = transformData[0] 120 | } else { 121 | orderList = this.reOrderList(transformData, { sort, reverse, page, pageSize, filter }) 122 | } 123 | result = { 124 | code: 1, 125 | msg: '请求成功', 126 | data: orderList 127 | } 128 | } 129 | return result 130 | } 131 | 132 | transformTorrentList (torrent) { 133 | let state = '' 134 | let stateText = '' 135 | switch (torrent.status) { 136 | case 'downloading': 137 | case 'finishing': 138 | state = 'downloading' 139 | stateText = '下载' 140 | break 141 | case 'extracting': 142 | case 'filehosting_waiting': 143 | case 'waiting': 144 | state = 'queued' 145 | stateText = '等待' 146 | break 147 | case 'seeding': 148 | case 'finished': 149 | state = 'seeding' 150 | stateText = '做种' 151 | break 152 | case 'paused': 153 | state = 'paused' 154 | stateText = '暂停' 155 | break 156 | case 'hash_checking': 157 | state = 'checking' 158 | stateText = '校验' 159 | break 160 | case 'error': 161 | state = 'error' 162 | stateText = '错误' 163 | break 164 | default: 165 | break 166 | } 167 | const { detail = {}, transfer = {} } = torrent.additional 168 | const { downloaded_pieces: downloadedPieces, size_downloaded: sizeDownloaded } = transfer 169 | const { total_pieces: totalPieces, uri } = detail 170 | const progress = downloadedPieces ? downloadedPieces / totalPieces : sizeDownloaded / torrent.size 171 | const result = { 172 | state, 173 | stateText, 174 | addOn: detail.create_time * 1000, 175 | completeOn: detail.completed_time * 1000, 176 | tag: '', 177 | completedData: transfer.size_downloaded, 178 | dlSpeed: transfer.speed_download, 179 | downloadedData: transfer.size_downloaded, 180 | eta: torrent.waiting_seconds, 181 | id: torrent.id, 182 | name: torrent.title, 183 | progress, 184 | ratio: transfer.size_uploaded / transfer.size_downloaded, 185 | savePath: detail.destination, 186 | size: torrent.size, 187 | totalSize: torrent.size, 188 | tracker: uri, 189 | uploadedData: transfer.size_uploaded, 190 | upSpeed: transfer.speed_upload 191 | } 192 | return result 193 | } 194 | 195 | filterList (list, filterKey) { 196 | let result = [] 197 | if (filterKey === 'all') { 198 | result = list 199 | } else if (filterKey === 'active') { 200 | result = list.filter(item => item.dlSpeed > 0 || item.upSpeed > 0) 201 | } else { 202 | result = list.filter(item => item.state === filterKey) 203 | } 204 | return result 205 | } 206 | 207 | getTimeString (time) { 208 | return new Date(time).getTime() 209 | } 210 | 211 | reOrderList (list, options) { 212 | const { sort, reverse, page, pageSize, filter } = options 213 | // 先筛选 后排序 214 | const filterList = this.filterList(list, filter) 215 | // 默认从小到大排序 216 | filterList.sort((item, other) => { 217 | if (sort === 'added_on') { 218 | const pre = this.getTimeString(item.addOn) 219 | const last = this.getTimeString(other.addOn) 220 | if (pre > last) { 221 | return reverse ? -1 : 1 222 | } 223 | if (pre < last) { 224 | return reverse ? 1 : -1 225 | } 226 | } 227 | if (item[sort] > other[sort]) { 228 | return reverse ? -1 : 1 229 | } 230 | if (item[sort] < other[sort]) { 231 | return reverse ? 1 : -1 232 | } 233 | }) 234 | const pageList = pageSize > 0 ? filterList.slice( 235 | (page - 1) * pageSize, 236 | page * pageSize 237 | ) : filterList 238 | return pageList 239 | } 240 | 241 | async getClientInfo (params) { 242 | await this.testServer() 243 | let uploaded = 0; let downloaded = 0 244 | if (!params.forSpeed) { 245 | const listData = await this.getTorrentList({ 246 | filter: 'all', 247 | pageSize: -1 248 | }) 249 | if (listData.data && listData.data.length) { 250 | listData.data.forEach(item => { 251 | uploaded += item.uploadedData 252 | downloaded += item.downloadedData 253 | }) 254 | } else { 255 | throw new Error('请求错误') 256 | } 257 | } 258 | 259 | const transInfo = await this.request( 260 | '/DownloadStation/statistic.cgi', 261 | { 262 | api: 'SYNO.DownloadStation.Statistic', 263 | version: 1, 264 | method: 'getinfo' 265 | } 266 | ) 267 | const upSpeed = transInfo.data.speed_upload 268 | const dlSpeed = transInfo.data.speed_download 269 | 270 | return { 271 | code: 1, 272 | data: { 273 | uploaded, 274 | downloaded, 275 | upSpeed, 276 | dlSpeed, 277 | freeSpace: -1, 278 | totalDownloaded: uploaded, 279 | totalUploaded: downloaded 280 | }, 281 | msg: '请求成功' 282 | } 283 | } 284 | 285 | async getClientData (params) { 286 | this.auth = params.auth || '' 287 | let uploaded = 0; let downloaded = 0 288 | let torrentList = [] 289 | if (params.forSpeed) { 290 | const list = await this.getTorrentList({ 291 | filter: 'all', 292 | pageSize: -1 293 | }) 294 | list.data.forEach(item => { 295 | uploaded += item.uploadedData 296 | downloaded += item.downloadedData 297 | }) 298 | torrentList = list.data 299 | } 300 | const transInfo = await this.request( 301 | '/DownloadStation/statistic.cgi', 302 | { 303 | api: 'SYNO.DownloadStation.Statistic', 304 | version: 1, 305 | method: 'getinfo' 306 | } 307 | ) 308 | const upSpeed = transInfo.data.speed_upload 309 | const dlSpeed = transInfo.data.speed_download 310 | const serverState = { 311 | uploaded: uploaded, 312 | downloaded: downloaded, 313 | upSpeed, 314 | dlSpeed, 315 | freeSpace: -1, 316 | totalDownloaded: downloaded, 317 | totalUploaded: uploaded 318 | } 319 | const returnData = { 320 | rid: 0, 321 | tagList: [], 322 | serverInfo: serverState, 323 | removedTagList: [], 324 | torrents: torrentList, 325 | removedTorrents: [] 326 | } 327 | return { 328 | code: 1, 329 | msg: '请求成功', 330 | data: returnData 331 | } 332 | } 333 | 334 | async pauseTorrent (params) { 335 | const res = await this.request( 336 | '/DownloadStation/task.cgi', 337 | { 338 | api: 'SYNO.DownloadStation.Task', 339 | version: 1, 340 | method: 'pause', 341 | id: params.id 342 | } 343 | ) 344 | return res 345 | } 346 | 347 | async resumeTorrent (params) { 348 | const res = await this.request( 349 | '/DownloadStation/task.cgi', 350 | { 351 | api: 'SYNO.DownloadStation.Task', 352 | version: 1, 353 | method: 'resume', 354 | id: params.id 355 | } 356 | ) 357 | return res 358 | } 359 | 360 | async deleteTorrent ({ id, deleteFile = true }) { 361 | const res = await this.request( 362 | '/DownloadStation/task.cgi', 363 | { 364 | api: 'SYNO.DownloadStation.Task', 365 | version: 1, 366 | method: 'delete', 367 | force_complete: false, 368 | id 369 | } 370 | ) 371 | if (res.data[0].error > 0) { 372 | throw new Error('删除失败') 373 | } 374 | return res 375 | } 376 | 377 | async setLocation (params) { 378 | const res = await this.request( 379 | '/DownloadStation/task.cgi', 380 | { 381 | api: 'SYNO.DownloadStation.Task', 382 | version: 1, 383 | method: 'edit', 384 | destination: params.path, 385 | id: params.id 386 | } 387 | ) 388 | return res 389 | } 390 | 391 | async addTorrentsUrl (params) { 392 | const { username, password } = this.config 393 | const defaults = { 394 | urls: '', 395 | savepath: '' 396 | } 397 | const reqParams = { 398 | ...defaults, 399 | ...params 400 | } 401 | const res = await this.request( 402 | '/DownloadStation/task.cgi', 403 | { 404 | api: 'SYNO.DownloadStation.Task', 405 | version: 1, 406 | method: 'create', 407 | uri: reqParams.urls, 408 | username, 409 | password, 410 | destination: reqParams.savepath 411 | } 412 | ) 413 | if (!res.data.success) { 414 | throw new Error('添加失败') 415 | } 416 | return res 417 | } 418 | 419 | async addTorrentFile (params) { 420 | await this.testServer() 421 | const {username, password, url, path} = this.config 422 | const requestUrl = url + path + '/DownloadStation/task.cgi' 423 | const defaults = { 424 | savepath: '' 425 | } 426 | const reqParams = { 427 | ...defaults, 428 | ...params 429 | } 430 | if (reqParams.fileId) { 431 | delete reqParams.fileId 432 | } 433 | return new Promise((resolve, reject) => { 434 | wx.uploadFile({ 435 | url: requestUrl, 436 | filePath: params.fileId, 437 | name: 'file', 438 | formData: { 439 | api: 'SYNO.DownloadStation.Task', 440 | version: 1, 441 | method: 'create', 442 | username, 443 | password, 444 | destination: reqParams.savepath 445 | }, 446 | header: { 447 | Cookie: `id=${this.auth}` 448 | }, 449 | success(res) { 450 | const data = JSON.parse(res.data) 451 | if (!data.success) { 452 | throw new Error('添加失败') 453 | } 454 | 455 | resolve({ 456 | code: 1, 457 | msg: '上传成功' 458 | }) 459 | }, 460 | fail(error) { 461 | reject(new Error(error)) 462 | }, 463 | complete() { 464 | } 465 | }) 466 | }) 467 | } 468 | 469 | async getDefaultSavePath () { 470 | const res = await this.request( 471 | '/DownloadStation/info.cgi', 472 | { 473 | api: 'SYNO.DownloadStation.Info', 474 | version: 1, 475 | method: 'getconfig' 476 | } 477 | ) 478 | return { 479 | code: 1, 480 | data: res.data.default_destination, 481 | msg: '请求成功' 482 | } 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/server/clients/transmission.js: -------------------------------------------------------------------------------- 1 | import wepy from 'wepy' 2 | import { base64Encode } from '../../utils' 3 | // https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt 4 | export default class Transmission { 5 | constructor(options) { 6 | this.config = { 7 | path: '/transmission/rpc', 8 | ...options 9 | } 10 | } 11 | 12 | async testServer() { 13 | const res = await this.request('session-get') 14 | return res 15 | } 16 | 17 | async getTorrentInfo(id) { 18 | const res = this.getTorrentList({ ids: id }) 19 | return res 20 | } 21 | 22 | async getTorrentList(params = {}) { 23 | const ids = params.ids || '' // 筛选ids 24 | const { 25 | sort = 'added_on', 26 | page = 1, 27 | pageSize = 20, 28 | reverse = true, 29 | filter = 'all' 30 | } = params 31 | const fields = [ 32 | 'id', 33 | 'addedDate', 34 | 'doneDate', 35 | 'name', 36 | 'totalSize', 37 | 'error', 38 | 'errorString', 39 | 'eta', 40 | 'isFinished', 41 | 'isStalled', 42 | 'hashString', 43 | 'labels', 44 | 'peersGettingFromUs', 45 | 'peersSendingToUs', 46 | 'leftUntilDone', 47 | 'percentDone', 48 | 'pieceSize', 49 | 'rateDownload', 50 | 'rateUpload', 51 | 'recheckProgress', 52 | 'recheckProgress', 53 | 'sizeWhenDone', 54 | 'status', 55 | 'trackers', 56 | 'downloadDir', 57 | 'uploadedEver', 58 | 'downloadedEver', 59 | 'uploadRatio', 60 | 'uploadedEver', 61 | 'downloadLimit', 62 | 'uploadLimit', 63 | 'downloadLimited', 64 | 'uploadLimited' 65 | ] 66 | const reqParams = { 67 | fields 68 | } 69 | if (ids) { 70 | reqParams.ids = ids 71 | } 72 | 73 | const res = await this.request('torrent-get', reqParams) 74 | let result = null 75 | if (res.code === -1 || !res.data || res.code !== 200) { 76 | result = res 77 | } else { 78 | const allListData = res.data.torrents 79 | if (ids && allListData.length === 1) { 80 | return { 81 | code: 1, 82 | msg: '请求成功', 83 | data: this.transformTorrentList(allListData[0]) 84 | } 85 | } 86 | const orderList = this.reOrderList(allListData, { 87 | sort, 88 | reverse, 89 | page, 90 | pageSize, 91 | filter 92 | }) 93 | result = { 94 | code: 1, 95 | msg: '请求成功', 96 | data: orderList.map((torrent) => { 97 | return this.transformTorrentList(torrent) 98 | }) 99 | } 100 | } 101 | return result 102 | } 103 | 104 | reOrderList(list, options) { 105 | const { sort, reverse, page, pageSize, filter } = options 106 | // 先筛选 后排序 107 | const filterList = this.filterList(list, filter) 108 | 109 | const sortKey = this.getSortKey(sort) 110 | // 默认从小到大排序 111 | filterList.sort((item, other) => { 112 | if (item[sortKey] > other[sortKey]) { 113 | return reverse ? -1 : 1 114 | } 115 | if (item[sortKey] < other[sortKey]) { 116 | return reverse ? 1 : -1 117 | } 118 | }) 119 | const pageList = filterList.slice((page - 1) * pageSize, page * pageSize) 120 | return pageList 121 | } 122 | 123 | filterList(list, filterKey) { 124 | const stateData = { 125 | downloading: [4], 126 | seeding: [6], 127 | paused: [0], 128 | queued: [3, 5], 129 | error: [2, 1], 130 | checking: [2, 1] 131 | } 132 | let result = [] 133 | if (filterKey === 'all') { 134 | result = list 135 | } else if (filterKey === 'error') { 136 | result = list.filter((item) => item.error !== 0) 137 | } else if (filterKey === 'active') { 138 | result = list.filter( 139 | (item) => item.peersSendingToUs > 0 || item.peersGettingFromUs > 0 140 | ) 141 | } else { 142 | result = list.filter( 143 | (item) => stateData[filterKey].indexOf(item.status) > -1 144 | ) 145 | } 146 | return result 147 | } 148 | 149 | getSortKey(key) { 150 | const sortObject = { 151 | added_on: 'addedDate', 152 | name: 'name', 153 | size: 'totalSize', 154 | ratio: 'uploadRatio' 155 | } 156 | return sortObject[key] 157 | } 158 | 159 | async getClientData(params) { 160 | this.auth = params.auth || '' 161 | let rid = params.rid || 0 162 | const ids = rid > 0 ? 'recently-active' : rid 163 | const fields = [ 164 | 'id', 165 | 'addedDate', 166 | 'doneDate', 167 | 'name', 168 | 'totalSize', 169 | 'error', 170 | 'errorString', 171 | 'eta', 172 | 'isFinished', 173 | 'isStalled', 174 | 'hashString', 175 | 'labels', 176 | 'peersGettingFromUs', 177 | 'peersSendingToUs', 178 | 'leftUntilDone', 179 | 'percentDone', 180 | 'pieceSize', 181 | 'rateDownload', 182 | 'rateUpload', 183 | 'recheckProgress', 184 | 'recheckProgress', 185 | 'sizeWhenDone', 186 | 'status', 187 | 'trackers', 188 | 'downloadDir', 189 | 'uploadedEver', 190 | 'downloadedEver', 191 | 'uploadRatio', 192 | 'uploadedEver' 193 | ] 194 | const reqParams = { 195 | fields 196 | } 197 | if (ids) { 198 | reqParams.ids = ids 199 | } 200 | const getTorrentList = this.request('torrent-get', reqParams) 201 | const getTransInfo = this.request('session-stats') 202 | const getDefaultSavePath = this.request('session-get') 203 | const result = await Promise.all([ 204 | getTorrentList, 205 | getTransInfo, 206 | getDefaultSavePath 207 | ]) 208 | const clientData = Object.assign(result[1].data, result[2].data) 209 | const serverState = this.transformClientInfo(clientData) 210 | const torrentList = result[0].data.torrents || [] 211 | const removedTorrentList = result[0].data.removed || [] 212 | const returnData = { 213 | rid: rid++, 214 | serverInfo: serverState, 215 | torrents: this.handleTorrentList(torrentList), 216 | removedTorrents: this.handleTorrentList(removedTorrentList) 217 | } 218 | return { 219 | code: 1, 220 | msg: '请求成功', 221 | data: returnData 222 | } 223 | } 224 | 225 | handleTorrentList(list) { 226 | if (list.length === 0) { 227 | return [] 228 | } 229 | return list.map((torrent) => { 230 | return this.transformTorrentList(torrent) 231 | }) 232 | } 233 | 234 | async getClientInfo() { 235 | await this.testServer() 236 | const getTransInfo = this.request('session-stats') 237 | const getDefaultSavePath = this.request('session-get') 238 | const result = await Promise.all([getTransInfo, getDefaultSavePath]) 239 | const returnData = Object.assign(result[0].data, result[1].data) 240 | const res = { 241 | code: 1, 242 | msg: '请求成功', 243 | data: this.transformClientInfo(returnData) 244 | } 245 | return res 246 | } 247 | 248 | async getDefaultSavePath() { 249 | const res = await this.request('session-get') 250 | return { 251 | code: 1, 252 | data: res.data['download-dir'], 253 | msg: '请求成功' 254 | } 255 | } 256 | 257 | async pauseTorrent(params) { 258 | const res = await this.request('torrent-stop', { 259 | ids: params.id 260 | }) 261 | return res 262 | } 263 | 264 | async resumeTorrent(params) { 265 | const res = await this.request('torrent-start', { 266 | ids: params.id 267 | }) 268 | return res 269 | } 270 | 271 | async deleteTorrent({ id, deleteFile = true }) { 272 | const res = await this.request('torrent-remove', { 273 | ids: id, 274 | 'delete-local-data': deleteFile 275 | }) 276 | return res 277 | } 278 | 279 | async recheck(params) { 280 | const res = await this.request('torrent-verify', { 281 | ids: params.id 282 | }) 283 | return res 284 | } 285 | 286 | async updateTracker(params) { 287 | const res = await this.request('torrent-reannounce', { 288 | ids: [params.id] 289 | }) 290 | return res 291 | } 292 | 293 | async setDlLimit(params) { 294 | const res = await this.request('torrent-set', { 295 | downloadLimited: true, 296 | ids: params.id, 297 | downloadLimit: parseInt(params.limit) || 0 298 | }) 299 | return res 300 | } 301 | 302 | async setUpLimit(params) { 303 | const res = await this.request('torrent-set', { 304 | uploadLimited: true, 305 | ids: params.id, 306 | uploadLimit: parseInt(params.limit) || 0 307 | }) 308 | return res 309 | } 310 | 311 | async setLocation(params) { 312 | const res = await this.request('torrent-set-location', { 313 | ids: [params.id], 314 | location: params.path, 315 | move: true 316 | }) 317 | return res 318 | } 319 | 320 | async setTorrentName(params) { 321 | const res = await this.request('torrent-rename-path', { 322 | ids: [params.id], 323 | name: params.name 324 | }) 325 | return res 326 | } 327 | 328 | // 3.0支持 329 | async setTag(params) { 330 | const res = await this.request('torrent-set', { 331 | ids: [params.id], 332 | labels: [params.tags] 333 | }) 334 | return res 335 | } 336 | 337 | async addTorrentsUrl(params) { 338 | const defaults = { 339 | urls: '', 340 | savepath: '', 341 | category: '', 342 | paused: false 343 | } 344 | const reqParams = { 345 | ...defaults, 346 | ...params 347 | } 348 | const res = await this.request('torrent-add', { 349 | filename: reqParams.urls, 350 | 'download-dir': reqParams.savepath, 351 | paused: reqParams.paused 352 | }) 353 | return res 354 | } 355 | 356 | async addTorrentFile(params) { 357 | const defaults = { 358 | savepath: '', 359 | category: '', 360 | paused: false 361 | } 362 | const reqParams = { 363 | ...defaults, 364 | ...params 365 | } 366 | if (reqParams.fileId) { 367 | delete reqParams.fileId 368 | } 369 | const fileData = wx 370 | .getFileSystemManager() 371 | .readFileSync(params.fileId, 'base64') 372 | reqParams.metainfo = fileData 373 | const res = await this.request('torrent-add', { 374 | metainfo: reqParams.metainfo, 375 | 'download-dir': reqParams.savepath, 376 | paused: reqParams.paused 377 | }) 378 | return res 379 | } 380 | 381 | transformTorrentList(torrent) { 382 | let state = '' 383 | let stateText = '' 384 | switch (torrent.status) { 385 | case 4: 386 | state = 'downloading' 387 | stateText = '下载' 388 | break 389 | case 3: 390 | case 5: 391 | state = 'queued' 392 | stateText = '等待' 393 | break 394 | case 6: 395 | state = 'seeding' 396 | stateText = '做种' 397 | break 398 | case 0: 399 | state = 'paused' 400 | stateText = '暂停' 401 | break 402 | case 1: 403 | case 2: 404 | state = 'checking' 405 | stateText = '校验' 406 | break 407 | default: 408 | break 409 | } 410 | if (torrent.error !== 0) { 411 | state = 'error' 412 | stateText = '错误' 413 | } 414 | const result = { 415 | state, 416 | stateText, 417 | addOn: torrent.addedDate * 1000, 418 | completeOn: torrent.doneDate * 1000, 419 | tag: 420 | torrent.labels && torrent.labels.length ? torrent.labels.join(',') : '', 421 | dlSpeed: torrent.rateDownload, 422 | downloadedData: torrent.downloadedEver, 423 | eta: torrent.eta, 424 | id: torrent.id, 425 | name: torrent.name, 426 | progress: torrent.percentDone, 427 | ratio: torrent.uploadRatio, 428 | savePath: torrent.downloadDir, 429 | size: torrent.sizeWhenDone, 430 | totalSize: torrent.totalSize, 431 | tracker: torrent.trackers[0].announce, 432 | uploadedData: torrent.uploadedEver, 433 | upSpeed: torrent.rateUpload, 434 | upLimit: torrent.uploadLimited ? torrent.uploadLimit * 1000 : -1, 435 | dlLimit: torrent.downloadLimited ? torrent.downloadLimit * 1000 : -1, 436 | trackerStatus: torrent.errorString 437 | } 438 | return result 439 | } 440 | 441 | transformClientInfo(clientInfo) { 442 | const result = { 443 | uploaded: clientInfo['current-stats'].uploadedBytes, 444 | upSpeed: clientInfo.uploadSpeed, 445 | totalUploaded: clientInfo['cumulative-stats'].uploadedBytes, 446 | dlSpeed: clientInfo.downloadSpeed, 447 | downloaded: clientInfo['current-stats'].downloadedBytes, 448 | totalDownloaded: clientInfo['cumulative-stats'].downloadedBytes, 449 | freeSpace: clientInfo['download-dir-free-space'] 450 | } 451 | const ratio = result.totalUploaded / result.totalDownloaded 452 | result.globalRatio = ratio 453 | return result 454 | } 455 | 456 | async request(methodName, params = {}, method = 'POST', options = {}) { 457 | const requestUrl = this.config.url + this.config.path 458 | const { username, password } = this.config 459 | try { 460 | const header = { 461 | 'X-Transmission-Session-Id': this.auth 462 | } 463 | const auth = base64Encode(`${username || ''}:${password || ''}`) 464 | header.Authorization = 'Basic ' + auth 465 | const requestOptions = { 466 | method: method, 467 | url: requestUrl, 468 | header, 469 | timeout: 20000, 470 | data: { 471 | method: methodName, 472 | arguments: params, 473 | tag: '' 474 | }, 475 | json: true 476 | } 477 | const res = await wepy.request(requestOptions) 478 | if (res.statusCode && res.statusCode === 409) { 479 | console.log(res.header) 480 | this.auth = res.header['X-Transmission-Session-Id'] 481 | const result = this.request(methodName, params) 482 | return result 483 | } 484 | const resBody = res.data 485 | if (resBody.result === 'success') { 486 | return { 487 | code: res.statusCode || 1, 488 | msg: '请求成功', 489 | data: resBody.arguments 490 | } 491 | } else { 492 | throw new Error(resBody.result) 493 | } 494 | } catch (err) { 495 | console.log(err) 496 | return { 497 | code: err.statusCode || -1, 498 | msg: err.errMsg || err.message 499 | } 500 | } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/server/clients/utorrent.js: -------------------------------------------------------------------------------- 1 | import wepy from 'wepy' 2 | import {base64Encode, cookieParse} from '../../utils' 3 | export default class Utorrent { 4 | constructor (options) { 5 | this.config = { 6 | path: '/gui/', 7 | ...options 8 | } 9 | this.auth = '' 10 | this.cookie = '' 11 | } 12 | 13 | async request (params = {}, method = 'GET', options = {}) { 14 | const { url, username, password, path } = this.config 15 | const requestUrl = url + path 16 | const auth = this.auth 17 | try { 18 | if (!auth) { 19 | const data = await this.testServer() 20 | if (data.code === -1 || data.code > 200) { 21 | throw new Error('身份验证失败,请检查用户名或密码') 22 | } 23 | } 24 | const authorization = this.getAuthorization(username, password) 25 | const requestOptions = { 26 | method: method, 27 | url: requestUrl, 28 | timeout: 20000, 29 | header: { 30 | Authorization: 'Basic ' + authorization, 31 | Cookie: `GUID=${this.cookie || ''}` 32 | }, 33 | data: { 34 | token: this.auth, 35 | ...params 36 | } 37 | } 38 | 39 | const res = await wepy.request({ ...requestOptions }) 40 | 41 | const resBody = res.data 42 | if (resBody) { 43 | return { 44 | code: res.statusCode || 1, 45 | msg: '请求成功', 46 | data: resBody 47 | } 48 | } else { 49 | throw new Error(resBody) 50 | } 51 | } catch (error) { 52 | console.log(error) 53 | return { 54 | code: error.statusCode || -1, 55 | msg: error.errMsg || error.message, 56 | data: error 57 | } 58 | } 59 | } 60 | 61 | getAuthorization (username, password) { 62 | return base64Encode(`${username || ''}:${password || ''}`) 63 | } 64 | 65 | async testServer () { 66 | const { url, username, password, path } = this.config 67 | const requestUrl = url + path + 'token.html' 68 | const auth = this.getAuthorization(username, password) 69 | const options = { 70 | method: 'GET', 71 | timeout: 13000, 72 | url: requestUrl, 73 | header: { 74 | Authorization: 'Basic ' + auth 75 | } 76 | } 77 | try { 78 | const res = await wepy.request(options) 79 | if (!res.cookies || !res.cookies.length) { 80 | throw new Error('身份验证失败') 81 | } 82 | const cookie = cookieParse(res.cookies[0]) 83 | if (!cookie || cookie.key !== 'GUID') { 84 | throw new Error('登录失败') 85 | } 86 | this.cookie = cookie.value 87 | const match = res.data.match(/>([^<]+)) 88 | if (match) { 89 | this.auth = match[match.length - 1] 90 | return { 91 | code: 0, 92 | msg: '授权成功', 93 | data: { 94 | auth: this.auth 95 | } 96 | } 97 | } else { 98 | throw new Error('身份验证失败,请检查用户名或密码') 99 | } 100 | } catch (err) { 101 | throw new Error(err.errMsg || err.message) 102 | } 103 | } 104 | 105 | async getTorrentList (params = {}) { 106 | const { 107 | sort = 'added_on', 108 | page = 1, 109 | pageSize = 20, 110 | reverse = true, 111 | filter = 'all' 112 | } = params 113 | const res = await this.request({ 114 | list: 1 115 | }) 116 | let result = null 117 | if (res.code === -1 || !res.data || res.code > 200) { 118 | result = res 119 | } else { 120 | const allListData = res.data.torrents 121 | if (params.id) { 122 | return allListData.find(item => { 123 | return item[0] === params.id 124 | }) 125 | } 126 | const transformData = allListData.map((item) => { 127 | return this.transformTorrentList(item) 128 | }) 129 | const orderList = this.reOrderList(transformData, { 130 | sort, 131 | reverse, 132 | page, 133 | pageSize, 134 | filter 135 | }) 136 | result = { 137 | code: 1, 138 | msg: '请求成功', 139 | data: orderList 140 | } 141 | } 142 | return result 143 | } 144 | 145 | filterList (list, filterKey) { 146 | let result = [] 147 | if (filterKey === 'all') { 148 | result = list 149 | } else if (filterKey === 'active') { 150 | result = list.filter((item) => item.dlSpeed > 0 || item.upSpeed > 0) 151 | } else { 152 | result = list.filter((item) => item.state === filterKey) 153 | } 154 | return result 155 | } 156 | 157 | getTimeString (time) { 158 | return new Date(time).getTime() 159 | } 160 | 161 | reOrderList (list, options) { 162 | const { sort, reverse, page, pageSize, filter } = options 163 | // 先筛选 后排序 164 | const filterList = this.filterList(list, filter) 165 | // 默认从小到大排序 166 | filterList.sort((item, other) => { 167 | if (sort === 'added_on') { 168 | const pre = this.getTimeString(item.addOn) 169 | const last = this.getTimeString(other.addOn) 170 | if (pre > last) { 171 | return reverse ? -1 : 1 172 | } 173 | if (pre < last) { 174 | return reverse ? 1 : -1 175 | } 176 | } 177 | if (item[sort] > other[sort]) { 178 | return reverse ? -1 : 1 179 | } 180 | if (item[sort] < other[sort]) { 181 | return reverse ? 1 : -1 182 | } 183 | }) 184 | const pageList = 185 | pageSize > 0 186 | ? filterList.slice((page - 1) * pageSize, page * pageSize) 187 | : filterList 188 | return pageList 189 | } 190 | 191 | transformTorrentList (torrent) { 192 | const torrentStatus = torrent[21] 193 | let state = '' 194 | let stateText = '' 195 | if (torrentStatus.includes('Downloading')) { 196 | state = 'downloading' 197 | stateText = '下载' 198 | } 199 | if (torrentStatus.includes('Queued')) { 200 | state = 'queued' 201 | stateText = '等待' 202 | } 203 | if ( 204 | torrentStatus.includes('Paused') || 205 | torrentStatus.includes('Finished') || 206 | torrentStatus.includes('Stopped') 207 | ) { 208 | state = 'paused' 209 | stateText = '暂停' 210 | } 211 | if (torrentStatus.includes('Checked')) { 212 | state = 'checking' 213 | stateText = '校验' 214 | } 215 | if (torrentStatus.includes('Error')) { 216 | state = 'error' 217 | stateText = '错误' 218 | } 219 | if (torrentStatus.includes('Seeding')) { 220 | state = 'seeding' 221 | stateText = '做种' 222 | } 223 | const result = { 224 | state, 225 | stateText, 226 | addOn: torrent[23] * 1000, 227 | completeOn: torrent[24] * 1000, 228 | downloadDataLeft: torrent[18], 229 | tag: torrent[11], 230 | completedData: torrent[5], 231 | dlSpeed: torrent[9], 232 | downloadedData: torrent[5], 233 | uploadedData: torrent[6], 234 | upSpeed: torrent[8], 235 | eta: torrent[10], 236 | id: torrent[0], 237 | name: torrent[2], 238 | progress: torrent[4] / 1000, 239 | ratio: torrent[7] / 1000, 240 | savePath: torrent[26], 241 | size: torrent[3], 242 | totalSize: undefined, 243 | tracker: undefined 244 | } 245 | return result 246 | } 247 | 248 | async getClientInfo () { 249 | await this.testServer() 250 | const dirData = await this.request({ 251 | action: 'list-dirs' 252 | }) 253 | let listData = await this.getTorrentList({ 254 | filter: 'all', 255 | pageSize: -1 256 | }) 257 | listData = listData.data 258 | let uploadedData = 0 259 | let downloadedData = 0 260 | let upSpeed = 0 261 | let dlSpeed = 0 262 | const tagList = [] 263 | listData.forEach((item) => { 264 | upSpeed += item.upSpeed 265 | dlSpeed += item.dlSpeed 266 | uploadedData += item.uploadedData 267 | downloadedData += item.downloadedData 268 | if (item.tag && !tagList.includes(item.tag)) { 269 | tagList.push(item.tag) 270 | } 271 | }) 272 | const freeSpace = 273 | dirData.data['download-dirs'][0].available * Math.pow(10, 6) 274 | return { 275 | code: 1, 276 | data: { 277 | uploaded: uploadedData, 278 | downloaded: downloadedData, 279 | upSpeed, 280 | dlSpeed, 281 | freeSpace, 282 | totalDownloaded: downloadedData, 283 | totalUploaded: uploadedData, 284 | tags: tagList 285 | }, 286 | msg: '请求成功' 287 | } 288 | } 289 | 290 | async getClientData (params) { 291 | this.auth = params.auth || '' 292 | const dirData = await this.request({ 293 | action: 'list-dirs' 294 | }) 295 | const list = await this.request( 296 | { 297 | cid: params.cid || 0, 298 | list: 1 299 | } 300 | ) 301 | const torrentList = list.data.torrents.map(torrent => { 302 | return this.transformTorrentList(torrent) 303 | }) 304 | const transInfo = await this.request( 305 | { 306 | action: 'getxferhist' 307 | } 308 | ) 309 | const uploadedData = transInfo.data.transfer_history.daily_upload.reduce((pre, cur) => { 310 | return pre + cur 311 | }) 312 | const downloadedData = transInfo.data.transfer_history.daily_download.reduce((pre, cur) => { 313 | return pre + cur 314 | }) 315 | let upSpeed = 0; let dlSpeed = 0 316 | const tagList = [] 317 | torrentList.forEach(item => { 318 | upSpeed += item.upSpeed 319 | dlSpeed += item.dlSpeed 320 | if (item.tag && !tagList.includes(item.tag)) { 321 | tagList.push(item.tag) 322 | } 323 | }) 324 | const freeSpace = dirData.data['download-dirs'][0].available * Math.pow(10, 6) 325 | const removedTorrentList = list.data.torrentm || [] 326 | const serverState = { 327 | uploaded: uploadedData, 328 | downloaded: downloadedData, 329 | upSpeed, 330 | dlSpeed, 331 | freeSpace, 332 | totalDownloaded: downloadedData, 333 | totalUploaded: uploadedData, 334 | tags: tagList 335 | } 336 | const returnData = { 337 | rid: list.data.torrentc, 338 | tagList, 339 | serverInfo: serverState, 340 | removedTagList: [], 341 | torrents: torrentList, 342 | removedTorrents: removedTorrentList 343 | } 344 | return { 345 | code: 1, 346 | msg: '请求成功', 347 | data: returnData 348 | } 349 | } 350 | 351 | async pauseTorrent (params) { 352 | const res = await this.request({ 353 | action: 'pause', 354 | hash: params.id 355 | }) 356 | return res 357 | } 358 | 359 | async resumeTorrent (params) { 360 | const res = await this.request({ 361 | action: 'start', 362 | hash: params.id 363 | }) 364 | return res 365 | } 366 | 367 | async getDefaultSavePath (params) { 368 | return { 369 | code: 1, 370 | data: '', 371 | msg: '请求成功' 372 | } 373 | } 374 | 375 | async deleteTorrent ({ id, deleteFile = true }) { 376 | const res = await this.request({ 377 | action: deleteFile ? 'removedata' : 'remove', 378 | hash: id 379 | }) 380 | return res 381 | } 382 | 383 | async getExtraInfo (id) { 384 | const res = await this.request({ 385 | action: 'getprops', 386 | hash: id 387 | }) 388 | const info = res.data.props[0] 389 | return { 390 | tracker: info.trackers, 391 | dlLimit: info.dlrate > 0 ? info.dlrate : -1, 392 | upLimit: info.ulrate > 0 ? info.ulrate : -1 393 | } 394 | } 395 | 396 | async getTorrentInfo (id) { 397 | await this.testServer() 398 | const extraInfo = await this.getExtraInfo(id) 399 | const torrentInfo = await this.getTorrentList({ id }) 400 | return { 401 | code: 1, 402 | data: { 403 | ...extraInfo, 404 | ...this.transformTorrentList(torrentInfo) 405 | } 406 | } 407 | } 408 | 409 | async recheck (params) { 410 | const res = await this.request({ 411 | action: 'recheck', 412 | hash: params.id 413 | }) 414 | return res 415 | } 416 | 417 | async setDlLimit (params) { 418 | const res = await this.request({ 419 | action: 'setprops', 420 | hash: params.id, 421 | s: 'dlrate', 422 | v: parseInt(params.limit) * 1000 || -1 423 | }) 424 | return res 425 | } 426 | 427 | async setUpLimit (params) { 428 | const res = await this.request({ 429 | action: 'setprops', 430 | hash: params.id, 431 | s: 'ulrate', 432 | v: parseInt(params.limit) * 1000 || -1 433 | }) 434 | return res 435 | } 436 | 437 | async setTag (params) { 438 | const res = await this.request({ 439 | action: 'setprops', 440 | s: 'label', 441 | hash: params.id, 442 | v: params.tags 443 | }) 444 | return res 445 | } 446 | 447 | async addTorrentFile (params) { 448 | await this.testServer() 449 | const {username, password, url, path} = this.config 450 | const authorization = this.getAuthorization(username, password) 451 | const requestUrl = url + path 452 | const defaults = { 453 | savepath: '', 454 | category: '', 455 | paused: false, 456 | upLimit: -1, 457 | dlLimit: -1 458 | } 459 | const reqParams = { 460 | ...defaults, 461 | ...params 462 | } 463 | if (reqParams.fileId) { 464 | delete reqParams.fileId 465 | } 466 | if (reqParams.torrent) { 467 | const filePath = wx.env.USER_DATA_PATH + '/ut.torrent' 468 | wx.getFileSystemManager().writeFileSync(filePath, reqParams.torrent, 'base64') 469 | params.fileId = filePath 470 | } 471 | return new Promise((resolve, reject) => { 472 | wx.uploadFile({ 473 | url: requestUrl + `?action=add-file&path=${reqParams.savepath}&token=${this.auth}`, 474 | filePath: params.fileId, 475 | name: 'torrent_file', 476 | header: { 477 | Authorization: 'Basic ' + authorization, 478 | Cookie: `GUID=${this.cookie || ''}` 479 | }, 480 | success(res) { 481 | const data = JSON.parse(res.data) 482 | if (data.error) { 483 | throw new Error(data.error) 484 | } 485 | resolve({ 486 | code: 1, 487 | msg: '上传成功' 488 | }) 489 | }, 490 | fail(error) { 491 | console.log(JSON.stringify(error)) 492 | reject(new Error(error)) 493 | }, 494 | complete() { 495 | if (reqParams.torrent) { 496 | wx.getFileSystemManager().unlinkSync(params.fileId) 497 | } 498 | } 499 | }) 500 | }) 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/server/clients/qbittorrent.js: -------------------------------------------------------------------------------- 1 | import wepy from 'wepy' 2 | import { cookieParse } from '../../utils' 3 | 4 | // https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1) 5 | export default class Qbittorrent { 6 | constructor(options) { 7 | this.config = { 8 | path: '/api/v2', 9 | ...options 10 | } 11 | this.auth = '' 12 | this.authType = '' 13 | } 14 | async request(api, params = {}, method = 'POST', options = {}) { 15 | const { url, path } = this.config 16 | const auth = this.auth 17 | try { 18 | if (!auth) { 19 | const data = await this.testServer() 20 | if (data.code === -1) { 21 | throw new Error(data.msg) 22 | } 23 | } 24 | const requestOptions = { 25 | method: method, 26 | url: url + path + api, 27 | data: params, 28 | timeout: 20000, 29 | header: { 30 | 'content-type': 'application/x-www-form-urlencoded', 31 | Cookie: `${this.authType}=${this.auth || ''}`, 32 | ...options.headers 33 | } 34 | } 35 | const res = await wepy.request(requestOptions) 36 | const resBody = res.data 37 | return { 38 | code: res.statusCode || 1, 39 | msg: '请求成功', 40 | data: resBody 41 | } 42 | } catch (err) { 43 | return { 44 | code: err.statusCode || -1, 45 | msg: err.errMsg || err.message, 46 | data: null 47 | } 48 | } 49 | } 50 | 51 | async testServer() { 52 | const requestUrl = this.config.url + this.config.path + '/auth/login' 53 | const { username, password } = this.config 54 | const options = { 55 | method: 'POST', 56 | url: requestUrl, 57 | timeout: 13000, 58 | data: { 59 | username, 60 | password 61 | }, 62 | header: { 63 | 'content-type': 'application/x-www-form-urlencoded' 64 | } 65 | } 66 | try { 67 | const res = await wepy.request(options) 68 | if (!res.cookies || !res.cookies.length) { 69 | throw new Error('身份验证失败') 70 | } 71 | const cookie = cookieParse(res.cookies[0]) 72 | this.authType = cookie.key 73 | if (!cookie || !this.authType) { 74 | throw new Error('登录失败') 75 | } 76 | this.auth = cookie.value 77 | console.log('auth:', this.auth) 78 | return { 79 | code: 0, 80 | msg: '授权成功', 81 | data: { 82 | auth: this.auth 83 | } 84 | } 85 | } catch (err) { 86 | console.log('testServer:', err) 87 | throw new Error(err.errMsg || err.message) 88 | } 89 | } 90 | 91 | async getTorrentInfo(id) { 92 | const data = await this.getTorrentList({ hashes: id }) 93 | return data 94 | } 95 | 96 | async recheck(params) { 97 | const res = await this.request('/torrents/recheck', { 98 | hashes: params.id 99 | }) 100 | return res 101 | } 102 | 103 | async updateTracker(params) { 104 | const res = await this.request('/torrents/reannounce', { 105 | hashes: params.id 106 | }) 107 | return res 108 | } 109 | 110 | async setDlLimit(params) { 111 | const res = await this.request('/torrents/setDownloadLimit', { 112 | hashes: params.id, 113 | limit: params.limit * 1000 || -1 114 | }) 115 | return res 116 | } 117 | 118 | async setUpLimit(params) { 119 | const res = await this.request('/torrents/setUploadLimit', { 120 | hashes: params.id, 121 | limit: params.limit * 1000 || -1 122 | }) 123 | return res 124 | } 125 | 126 | async setLocation(params) { 127 | /* 128 | * 400: Save path is empty 129 | 403: User does not have write access to directory 130 | 409: Unable to create save path directory 131 | * @param {any} 132 | * @return 133 | * */ 134 | const res = await this.request('/torrents/setLocation', { 135 | hashes: params.id, 136 | location: params.path 137 | }) 138 | return res 139 | } 140 | 141 | async setTorrentName(params) { 142 | const res = await this.request('/torrents/rename', { 143 | hash: params.id, 144 | name: params.name 145 | }) 146 | return res 147 | } 148 | 149 | async setTag(params) { 150 | const res = await this.request('/torrents/addTags', { 151 | hashes: params.id, 152 | tags: params.tags 153 | }) 154 | return res 155 | } 156 | async removeTag(params) { 157 | const res = await this.request('/torrents/removeTags', { 158 | hashes: params.id, 159 | tags: params.tags 160 | }) 161 | return res 162 | } 163 | async getTorrentList(params) { 164 | const defaults = { 165 | sort: 'added_on', 166 | reverse: true, 167 | page: 1, 168 | pageSize: 20 169 | } 170 | if (params.sort) { 171 | params.sort = this.transformFilterKey(params.sort) 172 | } 173 | params.reverse = `${params.reverse}` 174 | const reqParams = { 175 | ...defaults, 176 | ...params 177 | } 178 | const filter = params.filter || 'all' 179 | if (!(filter === 'all' || filter === 'active')) { 180 | delete params.filter 181 | } 182 | const res = await this.request('/torrents/info', reqParams) 183 | let result = null 184 | if (res.code === -1 || !res.data || res.code !== 200) { 185 | result = res 186 | } else { 187 | if (params.hashes && res.data && res.data[0]) { 188 | return { 189 | code: res.code || 1, 190 | msg: res.msg || '请求成功', 191 | data: this.transformTorrentList(res.data[0]) 192 | } 193 | } 194 | const resultList = this.filterList(res.data, filter) 195 | const { page, pageSize } = reqParams 196 | const sliceData = resultList.slice( 197 | (page - 1) * pageSize, 198 | page * pageSize 199 | ) 200 | result = { 201 | code: res.code || 1, 202 | msg: res.msg || '请求成功', 203 | data: sliceData.map((torrent) => { 204 | return this.transformTorrentList(torrent) 205 | }) 206 | } 207 | } 208 | return result 209 | } 210 | 211 | filterList(list, filterKey) { 212 | const stateData = { 213 | downloading: ['stalledDL', 'forcedDL', 'metaDL', 'downloading'], 214 | seeding: ['stalledUP', 'forcedUP', 'uploading'], 215 | paused: ['pausedDL', 'pausedUP'], 216 | queued: ['allocating', 'queuedUP', 'queuedDL'], 217 | error: ['unknown', 'missingFiles', 'error'], 218 | checking: ['moving', 'checkingResumeData', 'CheckingUP', 'checkingDL'] 219 | } 220 | let result = [] 221 | if (filterKey === 'all' || filterKey === 'active') { 222 | result = list 223 | } else { 224 | result = list.filter( 225 | (item) => stateData[filterKey].indexOf(item.state) > -1 226 | ) 227 | } 228 | return result 229 | } 230 | 231 | transformFilterKey(key) { 232 | const sortObject = { 233 | added_on: 'added_on', 234 | name: 'name', 235 | size: 'total_size', 236 | ratio: 'ratio' 237 | } 238 | return sortObject[key] 239 | } 240 | 241 | async getClientData(params) { 242 | this.auth = params.auth || '' 243 | this.authType = params.authType || 'SID' 244 | const res = await this.request('/sync/maindata', { 245 | rid: params.rid || 0 246 | }) 247 | if (!res.data) { 248 | throw new Error('请求失败') 249 | } 250 | const tagList = Object.keys(res.data.categories || {}) 251 | const removedTagList = Object.keys(res.data.categories_removed || {}) 252 | const serverState = this.transformClientInfo(res.data.server_state) 253 | const torrentList = res.data.torrents || [] 254 | const removedTorrentList = res.data.torrents_removed || [] 255 | const returnData = { 256 | rid: res.data.rid, 257 | auth: this.auth, 258 | authType: this.authType, 259 | tagList, 260 | serverInfo: serverState, 261 | removedTagList, 262 | torrents: this.handleTorrentList(torrentList), 263 | removedTorrents: this.handleTorrentList(removedTorrentList) 264 | } 265 | return { 266 | code: 1, 267 | msg: '请求成功', 268 | data: returnData 269 | } 270 | } 271 | 272 | handleTorrentList(list) { 273 | if (Object.keys(list).length === 0) { 274 | return [] 275 | } 276 | return Object.keys(list).map((key) => { 277 | const torrentData = { 278 | hash: key, 279 | ...list[key] 280 | } 281 | return this.transformTorrentList(torrentData) 282 | }) 283 | } 284 | 285 | async getClientInfo(params) { 286 | const res = await this.request('/sync/maindata') 287 | if (!res.data) { 288 | throw new Error('请求失败') 289 | } 290 | const clientInfo = res.data.server_state 291 | return { 292 | code: 1, 293 | msg: '请求成功', 294 | data: this.transformClientInfo(clientInfo) 295 | } 296 | } 297 | 298 | async pauseTorrent(params) { 299 | const res = await this.request('/torrents/pause', { 300 | hashes: params.id 301 | }) 302 | return res 303 | } 304 | 305 | async resumeTorrent(params) { 306 | const res = await this.request('/torrents/resume', { 307 | hashes: params.id 308 | }) 309 | return res 310 | } 311 | 312 | async deleteTorrent({ id, deleteFile = true }) { 313 | const res = await this.request('/torrents/delete', { 314 | hashes: id, 315 | deleteFiles: `${deleteFile}` 316 | }) 317 | return res 318 | } 319 | 320 | async addTorrentsUrl(params) { 321 | const defaults = { 322 | urls: '', 323 | savepath: '', 324 | category: '', 325 | paused: false, 326 | upLimit: -1, 327 | dlLimit: -1, 328 | rootFolder: false, 329 | skipCheck: false 330 | } 331 | const reqParams = { 332 | ...defaults, 333 | ...params 334 | } 335 | const res = await this.request('/torrents/add', { 336 | urls: reqParams.urls, 337 | savepath: reqParams.savepath, 338 | category: reqParams.category, 339 | paused: `${reqParams.paused}`, 340 | upLimit: reqParams.upLimit, 341 | dlLimit: reqParams.dlLimit, 342 | root_folder: `${reqParams.rootFolder}`, 343 | skip_checking: `${reqParams.skipCheck}` 344 | }) 345 | if (!res.data) { 346 | throw new Error('添加失败') 347 | } 348 | if (res.data.startsWith('Fail')) { 349 | throw new Error('添加失败') 350 | } 351 | return res 352 | } 353 | 354 | async addTorrentFile(params) { 355 | await this.testServer() 356 | const requestUrl = this.config.url + this.config.path + '/torrents/add' 357 | const defaults = { 358 | savepath: '', 359 | category: '', 360 | paused: false, 361 | upLimit: -1, 362 | dlLimit: -1, 363 | rootFolder: false, 364 | skipCheck: false 365 | } 366 | const reqParams = { 367 | ...defaults, 368 | ...params 369 | } 370 | if (reqParams.fileId) { 371 | delete reqParams.fileId 372 | } 373 | return new Promise((resolve, reject) => { 374 | wx.uploadFile({ 375 | url: requestUrl, 376 | filePath: params.fileId, 377 | name: 'torrents', 378 | formData: { 379 | savepath: reqParams.savepath, 380 | category: reqParams.category, 381 | paused: `${reqParams.paused}`, 382 | upLimit: reqParams.upLimit, 383 | dlLimit: reqParams.dlLimit, 384 | root_folder: `${reqParams.rootFolder}`, 385 | skip_checking: `${reqParams.skipCheck}` 386 | }, 387 | header: { 388 | Cookie: `${this.authType}=${this.auth || ''}` 389 | }, 390 | success(res) { 391 | console.log(res.data.startsWith('Fail')) 392 | if (res.data.startsWith('Fail')) { 393 | throw new Error('添加失败') 394 | } 395 | resolve({ 396 | code: 1, 397 | msg: '上传成功' 398 | }) 399 | }, 400 | fail(error) { 401 | reject(new Error(error)) 402 | }, 403 | complete() {} 404 | }) 405 | }) 406 | } 407 | 408 | async getDefaultSavePath(params) { 409 | const res = await this.request('/app/defaultSavePath', params) 410 | return res 411 | } 412 | 413 | transformClientInfo(clientInfo) { 414 | const result = { 415 | uploaded: clientInfo.up_info_data, 416 | upSpeed: clientInfo.up_info_speed, 417 | totalUploaded: clientInfo.alltime_ul, 418 | dlSpeed: clientInfo.dl_info_speed, 419 | downloaded: clientInfo.dl_info_data, 420 | totalDownloaded: clientInfo.alltime_dl, 421 | globalRatio: clientInfo.global_ratio, 422 | freeSpace: clientInfo.free_space_on_disk, 423 | tags: clientInfo.tags, 424 | category: clientInfo.category 425 | } 426 | return result 427 | } 428 | 429 | getTorrentState(status) { 430 | let state = '' 431 | let stateText = '' 432 | switch (status) { 433 | case 'downloading': 434 | case 'metaDL': 435 | case 'forcedDL': 436 | case 'stalledDL': 437 | state = 'downloading' 438 | stateText = '下载' 439 | break 440 | case 'queuedDL': 441 | case 'queuedUP': 442 | case 'allocating': 443 | state = 'queued' 444 | stateText = '等待' 445 | break 446 | case 'uploading': 447 | case 'forcedUP': 448 | case 'stalledUP': 449 | state = 'seeding' 450 | stateText = '做种' 451 | break 452 | case 'pausedUP': 453 | case 'pausedDL': 454 | state = 'paused' 455 | stateText = '暂停' 456 | break 457 | case 'checkingDL': 458 | case 'checkingUP': 459 | case 'checkingResumeData': 460 | case 'moving': 461 | state = 'checking' 462 | stateText = '校验' 463 | break 464 | case 'error': 465 | case 'missingFiles': 466 | case 'unknown': 467 | state = 'error' 468 | stateText = '错误' 469 | break 470 | default: 471 | break 472 | } 473 | return { 474 | state, 475 | stateText 476 | } 477 | } 478 | 479 | transformTorrentList(torrent) { 480 | const { stateText, state } = this.getTorrentState(torrent.state) 481 | const result = { 482 | state, 483 | stateText, 484 | addOn: torrent.added_on * 1000, 485 | completeOn: torrent.completion_on * 1000, 486 | tag: torrent.tags, 487 | category: torrent.category, 488 | completedData: torrent.completed, 489 | dlSpeed: torrent.dlspeed, 490 | downloadedData: torrent.downloaded, 491 | eta: torrent.eta, 492 | id: torrent.hash, 493 | name: torrent.name, 494 | progress: torrent.progress, 495 | ratio: torrent.ratio, 496 | savePath: torrent.save_path, 497 | size: torrent.size, 498 | totalSize: torrent.total_size, 499 | tracker: torrent.tracker, 500 | uploadedData: torrent.uploaded, 501 | upSpeed: torrent.upspeed, 502 | upLimit: torrent.up_limit >= 0 ? torrent.up_limit : -1, 503 | dlLimit: torrent.dl_limit >= 0 ? torrent.dl_limit : -1 504 | } 505 | return result 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /src/server/clients/deluge.js: -------------------------------------------------------------------------------- 1 | import wepy from 'wepy' 2 | import { cookieParse } from '../../utils' 3 | 4 | export default class Deluge { 5 | constructor(options) { 6 | this.config = { 7 | path: '/json', 8 | ...options 9 | } 10 | this.auth = '' 11 | this.requestId = 1 12 | } 13 | 14 | async request(methodName, params = [], method = 'POST', options = {}) { 15 | const requestUrl = this.config.url + this.config.path 16 | try { 17 | if (!this.auth) { 18 | const data = await this.testServer() 19 | if (data.code === -1) { 20 | throw new Error(data.msg) 21 | } 22 | } 23 | const requestOptions = { 24 | method: method, 25 | url: requestUrl, 26 | timeout: 20000, 27 | header: { 28 | Cookie: `_session_id=${this.auth || ''}`, 29 | 'content-type': 'application/json' 30 | }, 31 | data: { 32 | method: methodName, 33 | params, 34 | id: this.requestId 35 | } 36 | } 37 | const res = await wepy.request(requestOptions) 38 | if (res.data.error) { 39 | throw new Error(res.data.error.message || '请求失败') 40 | } 41 | return { 42 | code: res.statusCode || 1, 43 | msg: '请求成功', 44 | data: res.data.result 45 | } 46 | } catch (err) { 47 | return { 48 | code: err.statusCode || -1, 49 | msg: err.errMsg || err.message 50 | } 51 | } 52 | } 53 | 54 | async testServer() { 55 | const requestUrl = this.config.url + this.config.path 56 | const { password } = this.config 57 | const options = { 58 | method: 'POST', 59 | url: requestUrl, 60 | timeout: 13000, 61 | data: { 62 | method: 'auth.login', 63 | params: [password], 64 | id: this.requestId 65 | }, 66 | header: { 67 | 'content-type': 'application/json' 68 | } 69 | } 70 | try { 71 | const res = await wepy.request(options) 72 | if (!res.cookies || !res.cookies.length) { 73 | throw new Error('身份验证失败') 74 | } 75 | const cookie = cookieParse(res.cookies[0]) 76 | this.authType = cookie.key 77 | if (!cookie || cookie.key !== '_session_id') { 78 | throw new Error('登录失败') 79 | } 80 | this.auth = cookie.value 81 | console.log('auth:', this.auth) 82 | return { 83 | code: 0, 84 | msg: '授权成功', 85 | data: { 86 | auth: this.auth 87 | } 88 | } 89 | } catch (err) { 90 | throw new Error(err.errMsg || err.message) 91 | } 92 | } 93 | 94 | async getTorrentInfo(id) { 95 | const res = await this.request('web.get_torrent_status', [ 96 | id, 97 | [ 98 | 'queue', 99 | 'name', 100 | 'total_wanted', 101 | 'state', 102 | 'progress', 103 | 'num_seeds', 104 | 'total_seeds', 105 | 'label', 106 | 'num_peers', 107 | 'total_peers', 108 | 'total_uploaded', 109 | 'total_payload_upload', 110 | 'download_payload_rate', 111 | 'upload_payload_rate', 112 | 'eta', 113 | 'tracker_status', 114 | 'tracker_host', 115 | 'ratio', 116 | 'time_added', 117 | 'save_path', 118 | 'total_done', 119 | 'total_uploaded', 120 | 'max_download_speed', 121 | 'max_upload_speed', 122 | 'seeds_peers_ratio' 123 | ] 124 | ]) 125 | if (!res.data) { 126 | throw new Error('请求失败') 127 | } 128 | const result = { 129 | code: 1, 130 | msg: '请求成功', 131 | data: this.transformTorrentList(res.data) 132 | } 133 | return result 134 | } 135 | 136 | async getTorrentList(params = {}) { 137 | const { 138 | sort = 'added_on', 139 | page = 1, 140 | pageSize = 20, 141 | reverse = true, 142 | filter = 'all' 143 | } = params 144 | const filterObj = 145 | filter === 'all' ? {} : { state: this.getFilterKey(filter) } 146 | const res = await this.request('web.update_ui', [ 147 | [ 148 | 'queue', 149 | 'name', 150 | 'total_wanted', 151 | 'state', 152 | 'progress', 153 | 'num_seeds', 154 | 'total_seeds', 155 | 'label', 156 | 'num_peers', 157 | 'total_peers', 158 | 'total_uploaded', 159 | 'total_payload_upload', 160 | 'download_payload_rate', 161 | 'upload_payload_rate', 162 | 'eta', 163 | 'tracker_status', 164 | 'tracker_host', 165 | 'ratio', 166 | 'time_added', 167 | 'save_path', 168 | 'total_done', 169 | 'total_uploaded', 170 | 'max_download_speed', 171 | 'max_upload_speed', 172 | 'seeds_peers_ratio' 173 | ], 174 | filterObj 175 | ]) 176 | let result = null 177 | const allListData = this.getListArray(res.data.torrents || {}) 178 | const orderList = this.reOrderList(allListData, { 179 | sort, 180 | reverse, 181 | page, 182 | pageSize 183 | }) 184 | result = { 185 | code: 1, 186 | msg: '请求成功', 187 | data: orderList.map((torrent) => { 188 | return this.transformTorrentList(torrent) 189 | }) 190 | } 191 | return result 192 | } 193 | 194 | getListArray(list) { 195 | const torrentKeys = Object.keys(list) 196 | return torrentKeys.length 197 | ? torrentKeys.map((key) => { 198 | return { id: key, ...list[key] } 199 | }) 200 | : [] 201 | } 202 | 203 | getFilterKey(filter) { 204 | console.log(this.firstUpperCase(filter)) 205 | return this.firstUpperCase(filter) 206 | } 207 | 208 | firstUpperCase([first, ...rest]) { 209 | return first.toUpperCase() + rest.join('') 210 | } 211 | 212 | reOrderList(list, options) { 213 | const { sort, reverse, page, pageSize } = options 214 | const sortKey = this.getSortKey(sort) 215 | // 默认从小到大排序 216 | list.sort((item, other) => { 217 | if (item[sortKey] > other[sortKey]) { 218 | return reverse ? -1 : 1 219 | } 220 | if (item[sortKey] < other[sortKey]) { 221 | return reverse ? 1 : -1 222 | } 223 | }) 224 | const pageList = list.slice((page - 1) * pageSize, page * pageSize) 225 | return pageList 226 | } 227 | 228 | getSortKey(key) { 229 | const sortObject = { 230 | added_on: 'time_added', 231 | name: 'name', 232 | size: 'total_wanted', 233 | ratio: 'ratio' 234 | } 235 | return sortObject[key] 236 | } 237 | 238 | transformTorrentList(torrent) { 239 | let state = '' 240 | let stateText = '' 241 | switch (torrent.state) { 242 | case 'Downloading': 243 | state = 'downloading' 244 | stateText = '下载' 245 | break 246 | case 'Queued': 247 | state = 'queued' 248 | stateText = '等待' 249 | break 250 | case 'Seeding': 251 | state = 'seeding' 252 | stateText = '做种' 253 | break 254 | case 'Paused': 255 | state = 'paused' 256 | stateText = '暂停' 257 | break 258 | case 'Checking': 259 | state = 'checking' 260 | stateText = '校验' 261 | break 262 | case 'Active': 263 | state = 'Active' 264 | stateText = '活动' 265 | break 266 | case 'Error': 267 | state = 'error' 268 | stateText = '错误' 269 | break 270 | default: 271 | break 272 | } 273 | const result = { 274 | state, 275 | stateText, 276 | addOn: torrent.time_added * 1000, 277 | completeOn: undefined, 278 | downloadDataLeft: torrent.total_wanted - torrent.total_done, 279 | tag: torrent.label, 280 | completedData: torrent.total_done, 281 | dlSpeed: torrent.download_payload_rate, 282 | downloadedData: torrent.total_done, 283 | uploadedData: torrent.total_uploaded, 284 | upSpeed: torrent.upload_payload_rate, 285 | eta: torrent.eta, 286 | id: torrent.id, 287 | name: torrent.name, 288 | progress: torrent.progress / 100, 289 | ratio: torrent.ratio, 290 | savePath: torrent.save_path, 291 | size: torrent.total_wanted, 292 | totalSize: undefined, 293 | tracker: torrent.tracker_host, 294 | trackerStatus: torrent.tracker_status, 295 | upLimit: 296 | torrent.max_upload_speed >= 0 ? torrent.max_upload_speed * 1024 : -1, 297 | dlLimit: 298 | torrent.max_download_speed >= 0 299 | ? torrent.max_download_speed * 1024 300 | : -1 301 | } 302 | return result 303 | } 304 | 305 | async getDefaultSavePath(params) { 306 | const res = await this.request('core.get_config_values', [ 307 | ['download_location'] 308 | ]) 309 | return { 310 | code: 1, 311 | data: res.data.download_location, 312 | msg: '请求成功' 313 | } 314 | } 315 | 316 | async getClientInfo() { 317 | await this.testServer() 318 | const availableMethods = await this.listMethods() 319 | const freeSpace = this.request('web.update_ui', [[], {}]) 320 | let transInfo = new Promise((resolve) => { 321 | resolve(null) 322 | }) 323 | const sessionkeys = [ 324 | 'payload_download_rate', 325 | 'payload_upload_rate', 326 | 'total_payload_download', 327 | 'total_payload_upload', 328 | 'total_download', 329 | 'total_upload' 330 | ] 331 | if (availableMethods.data.indexOf('core.get_session_status')) { 332 | transInfo = this.request('core.get_session_status', [sessionkeys]) 333 | } 334 | 335 | const result = await Promise.all([freeSpace, transInfo]) 336 | const tagData = result[0].data.filters.label || [] 337 | const tagList = tagData.map((item) => item[0]) 338 | const returnData = Object.assign(result[0].data.stats, result[1].data, { 339 | tags: tagList 340 | }) 341 | const res = { 342 | code: 1, 343 | msg: '请求成功', 344 | data: this.transformClientInfo(returnData) 345 | } 346 | return res 347 | } 348 | 349 | transformClientInfo(clientInfo) { 350 | const result = { 351 | uploaded: clientInfo.total_payload_upload, 352 | upSpeed: clientInfo.payload_upload_rate, 353 | dlSpeed: clientInfo.payload_download_rate, 354 | totalUploaded: clientInfo.total_payload_upload, 355 | downloaded: clientInfo.total_payload_download, 356 | totalDownloaded: clientInfo.total_payload_download, 357 | freeSpace: clientInfo.free_space, 358 | tag: clientInfo.tags 359 | } 360 | const ratio = result.totalUploaded / result.totalDownloaded 361 | result.globalRatio = ratio 362 | return result 363 | } 364 | 365 | async getClientData(params) { 366 | this.auth = params.auth || '' 367 | this.requestId = params.rid || 1 368 | await this.testServer() 369 | const availableMethods = await this.listMethods() 370 | const mainData = this.request('web.update_ui', [[], {}]) 371 | let transInfo = new Promise((resolve) => { 372 | resolve(null) 373 | }) 374 | const sessionkeys = [ 375 | 'payload_download_rate', 376 | 'payload_upload_rate', 377 | 'total_payload_download', 378 | 'total_payload_upload', 379 | 'total_download', 380 | 'total_upload' 381 | ] 382 | if (availableMethods.data.indexOf('core.get_session_status')) { 383 | transInfo = this.request('core.get_session_status', [sessionkeys]) 384 | } 385 | const result = await Promise.all([mainData, transInfo]) 386 | const updateData = result[0].data 387 | console.log(result[0]) 388 | const tagData = updateData.filters.label || [] 389 | const tagList = tagData.map((item) => item[0]) 390 | const serverState = Object.assign(updateData.stats, result[1].data) 391 | const clientInfo = this.transformClientInfo(serverState) 392 | const torrentList = this.getListArray(updateData.torrents).map( 393 | (torrent) => { 394 | return this.transformTorrentList(torrent) 395 | } 396 | ) 397 | const returnData = { 398 | rid: result[0].id, 399 | tagList, 400 | serverInfo: clientInfo, 401 | removedTagList: [], 402 | torrents: torrentList, 403 | removedTorrents: [] 404 | } 405 | return { 406 | code: 1, 407 | msg: '请求成功', 408 | data: returnData 409 | } 410 | } 411 | 412 | async pauseTorrent(params) { 413 | const res = await this.request('core.pause_torrent', [[params.id]]) 414 | return res 415 | } 416 | 417 | async resumeTorrent(params) { 418 | const res = await this.request('core.resume_torrent', [[params.id]]) 419 | return res 420 | } 421 | 422 | async deleteTorrent({ id, deleteFile = true }) { 423 | const res = await this.request('core.remove_torrent', [id, deleteFile]) 424 | return res 425 | } 426 | 427 | async recheck(params) { 428 | const res = await this.request('core.force_recheck', [[params.id]]) 429 | return res 430 | } 431 | 432 | async updateTracker(params) { 433 | const res = await this.request('core.force_reannounce', [[params.id]]) 434 | return res 435 | } 436 | 437 | async setDlLimit(params) { 438 | const res = await this.request('core.set_torrent_options', [ 439 | [params.id], 440 | { max_download_speed: parseInt(params.limit) / 1.024 } 441 | ]) 442 | return res 443 | } 444 | 445 | async setUpLimit(params) { 446 | const res = await this.request('core.set_torrent_options', [ 447 | [params.id], 448 | { max_upload_speed: parseInt(params.limit) / 1.024 } 449 | ]) 450 | return res 451 | } 452 | 453 | async setLocation(params) { 454 | const res = await this.request('core.move_storage', [ 455 | [params.id], 456 | params.path 457 | ]) 458 | return res 459 | } 460 | 461 | async addTorrentsUrl(params) { 462 | const defaults = { 463 | urls: '', 464 | savepath: '', 465 | category: '', 466 | paused: false, 467 | upLimit: -1, 468 | dlLimit: -1 469 | } 470 | const reqParams = { 471 | ...defaults, 472 | ...params 473 | } 474 | const res = await this.request('core.add_torrent_url', [ 475 | reqParams.urls, 476 | { 477 | download_location: reqParams.savepath, 478 | add_paused: reqParams.paused, 479 | max_upload_speed: reqParams.upLimit, 480 | max_download_speed: reqParams.dlLimit 481 | } 482 | ]) 483 | return res 484 | } 485 | 486 | async addTorrentFile(params) { 487 | const defaults = { 488 | savepath: '', 489 | category: '', 490 | paused: false, 491 | upLimit: -1, 492 | dlLimit: -1 493 | } 494 | const reqParams = { 495 | ...defaults, 496 | ...params 497 | } 498 | const fileData = wx 499 | .getFileSystemManager() 500 | .readFileSync(params.fileId, 'base64') 501 | const res = await this.request('core.add_torrent_file', [ 502 | 'torrent', 503 | fileData, 504 | { 505 | download_location: reqParams.savepath, 506 | add_paused: reqParams.paused, 507 | max_upload_speed: reqParams.upLimit, 508 | max_download_speed: reqParams.dlLimit 509 | } 510 | ]) 511 | return res 512 | } 513 | 514 | async listMethods() { 515 | const res = await this.request('system.listMethods') 516 | return res 517 | } 518 | 519 | async getSessionState() { 520 | const res = await this.request('core.get_session_status', [ 521 | ['payload_download_rate', 'payload_upload_rate'] 522 | ]) 523 | return res 524 | } 525 | } 526 | --------------------------------------------------------------------------------