Showing preview only (308K chars total). Download the full file or copy to clipboard to get everything.
Repository: proxyee-down-org/proxyee-down
Branch: master
Commit: a8f1709f1603
Files: 90
Total size: 282.2 KB
Directory structure:
gitextract_5ck2h9al/
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── front/
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .postcssrc.js
│ ├── .prettierrc
│ ├── .vscode/
│ │ └── settings.json
│ ├── babel.config.js
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── App.vue
│ │ ├── common/
│ │ │ ├── http.js
│ │ │ └── native.js
│ │ ├── components/
│ │ │ ├── ExtensionSetting.vue
│ │ │ ├── FileChoose/
│ │ │ │ └── index.vue
│ │ │ ├── Table/
│ │ │ │ └── index.vue
│ │ │ └── Task/
│ │ │ ├── Create.vue
│ │ │ └── Resolve.vue
│ │ ├── i18n/
│ │ │ ├── en-US.js
│ │ │ ├── zh-CN.js
│ │ │ └── zh-TW.js
│ │ ├── main.js
│ │ ├── router.js
│ │ ├── store.js
│ │ └── views/
│ │ ├── About.vue
│ │ ├── Extension.vue
│ │ ├── Setting.vue
│ │ ├── Support.vue
│ │ └── Tasks.vue
│ └── vue.config.js
├── main/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── org/
│ │ └── pdown/
│ │ └── gui/
│ │ ├── DownApplication.java
│ │ ├── com/
│ │ │ ├── Browser.java
│ │ │ ├── CheckboxMenuItemGroup.java
│ │ │ └── Components.java
│ │ ├── content/
│ │ │ └── PDownConfigContent.java
│ │ ├── entity/
│ │ │ └── PDownConfigInfo.java
│ │ ├── extension/
│ │ │ ├── ContentScript.java
│ │ │ ├── ExtensionConfig.java
│ │ │ ├── ExtensionContent.java
│ │ │ ├── ExtensionInfo.java
│ │ │ ├── HookScript.java
│ │ │ ├── Meta.java
│ │ │ ├── Setting.java
│ │ │ ├── jsruntime/
│ │ │ │ ├── JavascriptEngine.java
│ │ │ │ └── polyfill/
│ │ │ │ ├── Window.java
│ │ │ │ └── property/
│ │ │ │ ├── Console.java
│ │ │ │ ├── Document.java
│ │ │ │ └── XMLHttpRequest.java
│ │ │ ├── mitm/
│ │ │ │ ├── intercept/
│ │ │ │ │ ├── AjaxIntercept.java
│ │ │ │ │ ├── CookieIntercept.java
│ │ │ │ │ ├── ScriptIntercept.java
│ │ │ │ │ └── SniffIntercept.java
│ │ │ │ ├── server/
│ │ │ │ │ └── PDownProxyServer.java
│ │ │ │ ├── ssl/
│ │ │ │ │ └── PDownCACertFactory.java
│ │ │ │ └── util/
│ │ │ │ ├── ExtensionCertUtil.java
│ │ │ │ ├── ExtensionProxyUtil.java
│ │ │ │ └── WinInet.java
│ │ │ └── util/
│ │ │ └── ExtensionUtil.java
│ │ ├── http/
│ │ │ ├── EmbedHttpServer.java
│ │ │ ├── controller/
│ │ │ │ ├── ApiController.java
│ │ │ │ ├── DefaultController.java
│ │ │ │ ├── NativeController.java
│ │ │ │ └── PacController.java
│ │ │ └── util/
│ │ │ └── HttpHandlerUtil.java
│ │ ├── rest/
│ │ │ └── HttpDownAppCallback.java
│ │ ├── update/
│ │ │ ├── CheckUpdate.java
│ │ │ └── VersionInfo.java
│ │ └── util/
│ │ ├── AppUtil.java
│ │ ├── ConfigUtil.java
│ │ ├── ExecUtil.java
│ │ └── I18nUtil.java
│ └── resources/
│ ├── application-dev.yml
│ ├── application-prd.yml
│ ├── application.yml
│ ├── banner.txt
│ ├── extension/
│ │ └── runtime.js
│ ├── i18n/
│ │ ├── messages_en-US.yml
│ │ ├── messages_zh-CN.yml
│ │ └── messages_zh-TW.yml
│ ├── logback-dev.xml
│ └── logback-prd.xml
├── pom.xml
└── runner/
├── .gitignore
├── pom.xml
└── src/
└── main/
├── deploy/
│ └── package/
│ └── macosx/
│ └── Proxyee Down.icns
└── java/
└── org/
└── pdown/
└── gui/
└── Runner.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
请根据以下模版提issue,不提供必要信息的issue将直接关闭
日志文件在proxyee-down安装目录/main/log文件夹里
-->
### 问题描述(必要)
### 版本号(必要)
### 操作系统(必要)
### 相关截图
### 相关日志
================================================
FILE: .gitignore
================================================
.DS_Store
target/
log/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
## 续作出炉!!!
新项目使用`golang`+`flutter`开发,支持所有平台的下载器,地址:https://github.com/GopeedLab/gopeed
## 暂停维护此项目
首先感谢大家支持和反馈才使得proxyee-down能一直迭代到现在的版本,但由于本人精力有限,宣布暂时停止此项目的维护,并且关闭**issue**模块。
其次因为`JAVA`不太适合做客户端开发,打包后体积太大且内存占用太高,本人计划在空余时间用`GO`来重写一遍,目标是打造一个`体积小`、`跨平台`、`内存低`、`可扩展`、`免费`的下载器。

# [Proxyee Down](https://pdown.org)
[](https://github.com/monkeyWie)
[](https://github.com/proxyee-down-org/proxyee-down/graphs/contributors)
[](https://github.com/proxyee-down-org/proxyee-down/stargazers)
[](https://github.com/proxyee-down-org/proxyee-down/fork)
[](https://github.com/proxyee-down-org/proxyee-down/blob/master/LICENSE)
> Proxyee Down 是一款开源的免费 HTTP 高速下载器,底层使用`netty`开发,支持自定义 HTTP 请求下载且支持扩展功能,可以通过安装扩展实现特殊的下载需求。
## 使用教程
[点击查看教程](https://github.com/proxyee-down-org/proxyee-down/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B)
## 交流群
1 群**11352304**、2 群**20236964**、3 群**20233754**、4 群**737991056**
## 开发
本项目后端主要使用 `java` + `spring` + `boot` + `netty`,前端使用 `vue.js` + `iview`
### 环境
  
oracle jdk 1.8+或 openjfx(openjdk默认不包含javafx包)
### 编译
```
git clone https://github.com/proxyee-down-org/proxyee-down.git
cd proxyee-down/front
#build html
npm install
npm run build
cd ../main
mvn clean package -Pprd
```
### 运行
```
java -jar proxyee-down-main.jar
```
================================================
FILE: front/.eslintrc.js
================================================
module.exports = {
root: true,
env: {
node: true
},
extends: ['plugin:vue/essential', 'eslint:recommended'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-alert': process.env.NODE_ENV === 'production' ? 'error' : 'off',
//强制使用单引号
quotes: ['error', 'single'],
//强制不使用分号结尾
semi: ['error', 'never']
},
parserOptions: {
parser: 'babel-eslint'
}
}
================================================
FILE: front/.gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj4
*.sln
*.sw*
package-lock.json
================================================
FILE: front/.postcssrc.js
================================================
module.exports = {
plugins: {
autoprefixer: {}
}
}
================================================
FILE: front/.prettierrc
================================================
{
"eslintIntegration": true,
"singleQuote": true,
"semi": false,
"printWidth": 120
}
================================================
FILE: front/.vscode/settings.json
================================================
{
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force"
}
},
"files.associations": {
"*.vue": "vue"
},
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "vue",
"autoFix": true
}
],
"eslint.autoFixOnSave": true
}
================================================
FILE: front/babel.config.js
================================================
module.exports = {
presets: ['@vue/app'],
plugins: ['jsx-v-model']
}
================================================
FILE: front/package.json
================================================
{
"name": "proxyee-down",
"version": "3.0.0",
"private": true,
"scripts": {
"start": "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.18.0",
"iview": "^2.14.3",
"numeral": "^2.0.6",
"reconnecting-websocket": "^4.1.0",
"vue": "^2.5.17",
"vue-i18n": "^8.0.0",
"vue-router": "^3.0.1",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.0.1",
"@vue/cli-plugin-eslint": "^3.0.1",
"@vue/cli-service": "^3.0.1",
"@vue/eslint-config-prettier": "^3.0.1",
"babel-plugin-jsx-v-model": "^2.0.3",
"iview-loader": "^1.2.1",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"lint-staged": "^6.0.0",
"vue-template-compiler": "^2.5.17"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"vue-cli-service lint",
"git add"
],
"*.vue": [
"vue-cli-service lint",
"git add"
]
}
}
================================================
FILE: front/public/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Proxyee Down</title>
<style>
body {
background-color: #f6f7f9 !important;
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but front doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
================================================
FILE: front/src/App.vue
================================================
<template>
<div id="app">
<i-menu mode="horizontal"
theme="dark"
:active-name="$route.path.substring(1)"
@on-select="forward">
<Badge :count="$root.badges.tasks">
<i-menu-item name="tasks">
<Icon type="ios-download-outline"></Icon>
{{ $t("nav.tasks") }}
</i-menu-item>
</Badge>
<Badge :count="$root.badges.extension">
<i-menu-item name="extension">
<Icon type="social-windows"></Icon>
{{ $t("nav.extension") }}
</i-menu-item>
</Badge>
<Badge :count="$root.badges.setting">
<i-menu-item name="setting">
<Icon type="settings"></Icon>
{{ $t("nav.setting") }}
</i-menu-item>
</Badge>
<Badge :count="$root.badges.about">
<i-menu-item name="about">
<Icon type="information-circled"></Icon>
{{ $t("nav.about") }}
</i-menu-item>
</Badge>
<Badge :count="$root.badges.support">
<i-menu-item name="support">
<Icon type="social-usd"></Icon>
{{ $t("nav.support") }}
</i-menu-item>
</Badge>
</i-menu>
<div style="padding: 1.25rem 1.25rem">
<keep-alive>
<router-view />
</keep-alive>
</div>
</div>
</template>
<script>
import { checkCert, getExtensions } from './common/native'
export default {
methods: {
forward(route) {
this.$router.push(route)
}
},
data() {
return {
badges: { tasks: 0, extension: 2, setting: 0, about: 0, support: 0 }
}
},
async created() {
// Check update
if (this.$config.needCheckUpdate) {
try {
const { data: versionInfo } = await this.$noSpinHttp.get(this.$config.adminServer + 'version/checkUpdate')
if (versionInfo && versionInfo.version > this.$config.version) {
this.$router.push({ path: '/about', query: { checkUpdate: true, versionInfo: JSON.stringify(versionInfo) } })
}
} catch (e) {
console.error(e)
}
}
// Check extension update
try {
const status = await checkCert()
if (status) {
const extensions = await getExtensions()
if (extensions.length > 0) {
const { data: serverExtensions } = await this.$noSpinHttp.post(
this.$config.adminServer + 'extension/checkExtensionUpdate',
extensions.map(e => {
return { path: e.meta.path, version: e.version }
})
)
if (serverExtensions && serverExtensions.length > 0) {
this.$root.badges.extension = serverExtensions.length
}
}
}
} catch (e) {
console.error(e)
}
}
}
</script>
<style>
i.action-icon {
cursor: pointer;
font-size: 1.25rem;
}
i.tip-icon {
position: relative;
top: 5px;
padding-left: 5px;
}
i.action-icon + i.action-icon {
padding-left: 0.625rem;
}
</style>
================================================
FILE: front/src/common/http.js
================================================
import Vue from 'vue'
import axios from 'axios'
export default {
build() {
const client = axios.create()
client.interceptors.request.use(
config => {
Vue.prototype.$Spin.show()
return config
},
error => Promise.reject(error)
)
client.interceptors.response.use(
response => {
Vue.prototype.$Spin.hide()
return response
},
error => {
Vue.prototype.$Spin.hide()
return Promise.reject(error)
}
)
return client
}
}
================================================
FILE: front/src/common/native.js
================================================
import http from './http'
import axios from 'axios'
const client = http.build()
const clientNoSpin = axios.create()
/**
* 弹出原生文件选择框
*/
export const showFileChooser = () => {
return new Promise((resolve, reject) => {
client
.get('/native/fileChooser')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 弹出原生文件夹选择框
*/
export const showDirChooser = () => {
return new Promise((resolve, reject) => {
client
.get('/native/dirChooser')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 取应用初始化配置信息
*/
export const getInitConfig = () => {
return new Promise((resolve, reject) => {
client
.get('/native/getInitConfig')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 弹出系统资源管理器并选中指定文件
* @param {string} path 文件路径
*/
export const showFile = path => {
return new Promise((resolve, reject) => {
client
.post('/native/showFile', { path: path })
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 检查证书是否安装
*/
export const checkCert = () => {
return new Promise((resolve, reject) => {
clientNoSpin
.get('/native/checkCert')
.then(response => resolve(response.data.status))
.catch(error => reject(error))
})
}
/**
* 安装证书
*/
export const installCert = () => {
return new Promise((resolve, reject) => {
client
.get('/native/installCert')
.then(response => resolve(response.data.status))
.catch(error => reject(error))
})
}
/**
* 取设置的代理模式
*/
export const getProxyMode = () => {
return new Promise((resolve, reject) => {
clientNoSpin
.get('/native/getProxyMode')
.then(response => resolve(response.data.mode))
.catch(error => reject(error))
})
}
/**
* 修改代理模式
* @param {number} mode 0.不接管系统代理 1.接管系统代理
*/
export const changeProxyMode = mode => {
return new Promise((resolve, reject) => {
client
.post('/native/changeProxyMode', { mode: mode })
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 取本地已安装的扩展列表
*/
export const getExtensions = () => {
return new Promise((resolve, reject) => {
clientNoSpin
.get('/native/getExtensions')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 安装指定扩展
* @param {object} data 扩展相关信息
*/
export const installExtension = data => {
return new Promise((resolve, reject) => {
clientNoSpin
.post('/native/installExtension', data)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 更新指定扩展
* @param {object} data 扩展相关信息
*/
export const updateExtension = data => {
return new Promise((resolve, reject) => {
clientNoSpin
.post('/native/updateExtension', data)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 安装本地扩展
* @param {string} path 扩展所在目录
*/
export const installLocalExtension = path => {
return new Promise((resolve, reject) => {
clientNoSpin
.post('/native/installLocalExtension', { path: path })
.then(response => resolve(response.data.data))
.catch(error => reject(error))
})
}
/**
* 卸载扩展
* @param {string} path 扩展所在目录
* @param {boolean} isLocal 是否本地加载的扩展
*/
export const uninstallExtension = (path, local) => {
return new Promise((resolve, reject) => {
client
.post('/native/uninstallExtension', { path, local })
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 启用或禁用扩展
* @param {object} data
*/
export const toggleExtension = data => {
return new Promise((resolve, reject) => {
client
.post('/native/toggleExtension', data)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 保存指定扩展的设置
* @param {String} path 扩展路径
* @param {object} setting 扩展设置信息
*/
export const updateExtensionSetting = (path, setting) => {
return new Promise((resolve, reject) => {
client
.post('/native/updateExtensionSetting', { path, setting })
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 打开浏览器并访问指定url
* @param {object} data
*/
export const openUrl = url => {
if (window.navigator.userAgent.indexOf('JavaFX') !== -1) {
clientNoSpin.post('/native/openUrl', { url: encodeURIComponent(url) })
} else {
window.open(url)
}
}
/**
* 在解析任务时触发
* @param {object} request
*/
export const onResolve = request => {
return new Promise((resolve, reject) => {
clientNoSpin
.post('/native/onResolve', request)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 更新软件
* @param {string} path 更新包下载地址
*/
export const doUpdate = path => {
return new Promise((resolve, reject) => {
clientNoSpin
.post('/native/doUpdate', { path: path })
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 更新软件进度获取
*/
export const getUpdateProgress = () => {
return new Promise((resolve, reject) => {
clientNoSpin
.get('/native/getUpdateProgress')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 重启软件
*/
export const doRestart = () => {
return new Promise((resolve, reject) => {
client
.get('/native/doRestart')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 取软件设置信息
*/
export const getConfig = () => {
return new Promise((resolve, reject) => {
clientNoSpin
.get('/native/getConfig')
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
/**
* 保存软件设置
* @param {object} config
*/
export const setConfig = config => {
return new Promise((resolve, reject) => {
clientNoSpin
.put('/native/setConfig', config)
.then(response => resolve(response))
.catch(error => reject(error))
})
}
/**
* 复制数据到系统剪贴板
* @param {object} data
*/
export const copy = data => {
return new Promise((resolve, reject) => {
clientNoSpin
.put('/native/copy', data)
.then(response => resolve(response))
.catch(error => reject(error))
})
}
================================================
FILE: front/src/components/ExtensionSetting.vue
================================================
<template>
<Form :label-width="70">
<FormItem v-for="(setting,index) in settings"
:key="index"
:label="setting.title"
:prop="setting.name">
<Switch v-if="setting.type==='Boolean'"
v-model="setting.value" />
<Select v-else-if="setting.options"
style="width:90%"
v-model="setting.value">
<Option v-for="(key,label) in setting.options"
:key="key"
:value="key">{{label}}</Option>
</Select>
<Input v-else
style="width:90%"
v-model="setting.value" />
<Tooltip class="item"
placement="right">
<Icon type="help-circled"
class="action-icon tip-icon" />
<div slot="content"
style="white-space: normal;width:200px;">
<p>{{ setting.description }}</p>
</div>
</Tooltip>
</FormItem>
</Form>
</template>
<script>
export default {
props: {
settings: {
type: Array
}
}
}
</script>
================================================
FILE: front/src/components/FileChoose/index.vue
================================================
<template>
<div class="file-choose">
<Input class="file-choose-input"
:value="value"
readonly
disabled />
<Button type="primary"
class="file-choose-button"
:disabled="disabled||chooserWait"
@click="showChooser">{{ $t('tip.choose') }}</Button>
</div>
</template>
<script>
import { showDirChooser, showFileChooser } from '../../common/native'
export default {
props: {
value: {
type: String
},
mode: {
type: String,
default: 'dir'
},
disabled: {
type: Boolean
}
},
data() {
return {
chooserWait: false
}
},
methods: {
showChooser() {
this.chooserWait = true
let chooserPromise =
this.mode === 'dir' ? showDirChooser() : showFileChooser()
chooserPromise
.then(result => {
if (result) {
this.$emit('input', result.path)
}
})
.finally(() => {
this.chooserWait = false
})
}
}
}
</script>
<style scoped>
.file-choose {
display: inline-block;
width: 100%;
}
.file-choose-input {
width: 85%;
padding-right: 3px;
}
.file-choose-button {
width: 15%;
}
</style>
================================================
FILE: front/src/components/Table/index.vue
================================================
<template>
<section class="prye-tb">
<div class="tb-wrapper">
<div class="bg"></div>
<div class="tb-head">
<div class="tb-tr">
<div class="ths">
<div class="th">
<Checkbox v-model="all"
@on-change="setAll"></Checkbox>
</div>
<div class="th">{{ $t("tasks.fileName") }}</div>
<div class="th">{{ $t("tasks.fileSize") }}</div>
<div class="th">{{ $t("tasks.taskProgress") }}</div>
<div class="th">{{ $t("tasks.downloadSpeed") }}</div>
<div class="th">{{ $t("tasks.status") }}</div>
<div class="th">{{ $t("tasks.operate") }}</div>
</div>
</div>
</div>
<div class="tb-body"
:style="{'max-height':maxHeight+'px'}">
<div class="tb-tr"
v-for="task in taskList"
:key="task.id">
<div class="progress"
:style="`width: ${ calcProgress(task) };`"></div>
<div class="tds">
<div class="td">
<Checkbox v-model="checkedMap[task.id]"
@on-change="toggleAll"></Checkbox>
</div>
<div class="td">{{ task.response.fileName }}</div>
<div class="td">{{ task.response.totalSize?$numeral(task.response.totalSize).format('0.00 ib'):$t('tasks.unknowLeft') }}</div>
<div class="td">{{ calcProgress(task) }}</div>
<div class="td">{{ $numeral(task.info.speed).format('0.00 ib') }}/S</div>
<div class="td">{{ calcStatus(task) }}</div>
<div class="td">
<Icon v-if="task.info.status === 1"
class="action-icon"
type="ios-pause"
:title="$t('tasks.pauseDownloads')"
@click="$emit('on-pause', task)"></Icon>
<Icon v-else-if="task.info.status !== 4"
class="action-icon"
type="ios-play"
:title="$t('tasks.continueDownloading')"
@click="$emit('on-resume', task)"></Icon>
<Icon type="ios-trash"
class="action-icon"
:title="$t('tasks.deleteTask')"
@click="$emit('on-delete', task)"></Icon>
<Icon class="action-icon"
type="ios-folder"
:title="$t('tasks.revealInFolder')"
@click="$emit('on-open', task)"></Icon>
<Poptip placement="right-end"
:title="$t('tasks.detail')"
transfer
width="400"
trigger="click">
<Icon class="action-icon"
:title="$t('tasks.detail')"
type="ios-eye-outline"></Icon>
<div class="file-detail"
slot="content">
<p>
<b>{{ $t('tasks.url') }}:</b>
<span>{{ task.request.url }}</span>
</p>
<p>
<b>{{ $t('tasks.fileName') }}:</b>
<span>{{ task.response.fileName }}</span>
</p>
<p>
<b>{{ $t('tasks.filePath') }}:</b>
<span>{{ task.config.filePath }}</span>
</p>
<p>
<b>{{ $t('tasks.fileSize') }}:</b>
<span>{{ $numeral(task.response.totalSize).format('0.00 ib') }}</span>
</p>
<p>
<b>{{ $t('tasks.connections') }}:</b>
<span>{{ task.config.connections }}</span>
</p>
<p>
<b>{{ $t('tasks.downloadSpeed') }}:</b>
<span>{{ $numeral(task.info.speed).format('0.00 ib') }}/S</span>
</p>
<p>
<b>{{ $t('tasks.status') }}:</b>
<span>{{ calcStatus(task) }}</span>
</p>
<p>
<b>{{ $t("tasks.createTime") }}:</b>
<span>{{ new Date(task.info.startTime).format('yyyy-MM-dd hh:mm:ss') }}</span>
</p>
</div>
</Poptip>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
// data
data() {
return {
all: false,
checkedMap: {}
}
},
// props
props: {
taskList: {
type: Array,
required: true
},
maxHeight: {
type: Number
}
},
watch: {
taskList() {
if (this.taskList.length === 0) {
this.checkedMap = {}
this.all = false
}
}
},
// methods
methods: {
setAll(checked) {
if (checked) {
this.taskList.forEach(task => (this.checkedMap[task.id] = true))
} else {
for (let key in this.checkedMap) {
this.checkedMap[key] = false
}
}
},
toggleAll(checked) {
if (checked) {
for (let key in this.checkedMap) {
if (this.checkedMap[key] !== true) {
return
}
}
this.all = true
} else {
this.all = false
}
},
calcProgress(task) {
let progress = task.info.downSize / task.response.totalSize
return progress ? this.$numeral(task.info.downSize / task.response.totalSize).format('0.00%') : '0%'
},
calcStatus(task) {
switch (task.info.status) {
case 0:
return this.$t('tasks.wait')
case 1:
if (task.info.speed > 0 && task.response.totalSize > 0) {
return this.$numeral((task.response.totalSize - task.info.downSize) / task.info.speed).format('00:00:00')
} else {
return this.$t('tasks.unknowLeft')
}
case 2:
return this.$t('tasks.statusPause')
case 3:
return this.$t('tasks.statusFail')
case 4:
return this.$t('tasks.statusDone')
}
},
getCheckedTasks() {
return this.taskList.filter(task => {
for (let key in this.checkedMap) {
if (this.checkedMap.hasOwnProperty(key) && this.checkedMap[key] && key === task.id) {
return true
}
}
return false
})
}
}
}
</script>
<style lang="less" scoped>
.prye-tb {
.tb-body {
overflow-y: auto;
}
.tb-wrapper {
position: relative;
width: 100%;
border: 1px solid #e9eaec;
border-bottom: 0 none;
.bg {
position: absolute;
background: white;
left: 0;
right: 0;
bottom: 0;
top: 0;
z-index: -2;
}
.tb-tr {
position: relative;
.tds,
.ths {
width: 100%;
display: flex;
.th,
.td {
padding: 12px 5px;
border-bottom: 1px solid #e9eaec;
box-sizing: border-box;
word-break: break-all;
display: flex;
align-items: center;
&:nth-child(1) {
width: 5%;
> label {
margin: 0 auto;
}
}
&:nth-child(2) {
width: 30%;
}
&:nth-child(3) {
width: 15%;
}
&:nth-child(4) {
width: 15%;
}
&:nth-child(5) {
width: 15%;
}
&:nth-child(6) {
width: 10%;
}
&:nth-child(7) {
width: 10%;
}
}
.th {
background-color: #f8f8f9;
text-align: left;
}
}
.tds {
> .td:last-child {
.action-icon {
padding: 3px 5px;
margin-right: 5px;
&:last-child {
margin-right: 0;
}
}
}
}
.progress {
position: absolute;
z-index: -1;
height: 100%;
background: rgba(87, 197, 247, 0.2);
transition: all 0.6s;
}
}
}
}
</style>
<style>
.file-detail p {
padding: 2px;
}
.file-detail b {
display: inline-block;
width: 60px;
}
</style>
================================================
FILE: front/src/components/Task/Create.vue
================================================
<template>
<Modal :title="$t('tasks.createTask')"
:value="visible"
@input="closeModal"
@on-visible-change="init"
:closable="false"
:mask-closable="false">
<Form v-if="visible"
ref="form"
:model="form"
:rules="rules"
:label-width="70">
<FormItem v-if="sameTasks.length>0"
prop="taskId"
:label="$t('tasks.sameTaskList')">
<Select v-model="form.taskId"
clearable
@on-change="sameTaskChange"
:placeholder="$t('tasks.sameTaskPlaceholder')">
<Option v-for="task in sameTasks"
:key="task.id"
:label="task.config.filePath+getFileSeparator()+task.response.fileName"
:value="task.id">
</Option>
</Select>
</FormItem>
<template v-if="!selectOldTask">
<FormItem :label="$t('tasks.fileName')"
prop="response.fileName">
<Input :disabled="disabledForm"
v-model="form.response.fileName" />
</FormItem>
<FormItem :label="$t('tasks.fileSize')">{{ form.response.totalSize?$numeral(form.response.totalSize).format('0.00 ib'):$t('tasks.unknowLeft') }}</FormItem>
<FormItem :label="$t('tasks.connections')"
prop="config.connections">
<Slider v-if="response.supportRange"
v-model="form.config.connections"
:disabled="disabledForm"
:min="2"
:max="256"
:step="2"
show-input />
<Slider v-else
disabled
v-model="form.config.connections"
:min="1"
:max="1"
show-input />
</FormItem>
<FormItem :label="$t('tasks.filePath')"
prop="config.filePath">
<FileChoose :disabled="disabledForm"
v-model="form.config.filePath" />
</FormItem>
</template>
</Form>
<div slot="footer">
<Button type="primary"
@click="onSubmit">{{ $t('tip.ok') }}</Button>
<Button @click="closeModal">{{ $t('tip.cancel') }}</Button>
</div>
<Spin size="large"
fix
v-if="load" />
</Modal>
</template>
<script>
import FileChoose from '../FileChoose'
export default {
props: {
request: {
type: Object
},
response: {
type: Object
},
config: {
type: Object
},
data: {
type: Object
}
},
data() {
return {
load: false,
selectOldTask: false,
disabledForm: false,
form: {
taskId: undefined,
request: this.request,
response: this.response,
config: this.config,
data: this.data
},
rules: {
taskId: [{ required: true, message: this.$t('tip.notNull') }],
'response.fileName': [{ required: true, message: this.$t('tip.notNull') }],
'config.filePath': [{ required: true, message: this.$t('tip.notNull') }]
},
sameTasks: []
}
},
watch: {
request() {
this.form.request = this.request
this.form.response = this.response
this.form.data = this.data
this.setDefaultConfig()
}
},
computed: {
visible() {
if (this.request && this.response) {
return true
} else {
return false
}
}
},
components: {
FileChoose
},
methods: {
closeModal() {
this.$emit('close')
},
onSubmit() {
this.$refs['form'].validate(valid => {
if (valid) {
this.load = true
if (this.form.taskId) {
//refresh download request
this.$http
.put('http://127.0.0.1:26339/tasks/' + this.form.taskId, this.form.request)
.then(() => {
this.$router.push('/')
})
.finally(() => {
this.load = false
})
} else {
//create download task
this.$http
.post('http://127.0.0.1:26339/tasks', this.form)
.then(() => {
this.$router.push('/')
})
.finally(() => {
this.load = false
})
}
}
})
},
async init(visible) {
//reset params
this.sameTasks = []
this.form.taskId = undefined
this.disabledForm = false
if (visible) {
//check same task
const { data: downTasks } = await this.$http.get('http://127.0.0.1:26339/tasks?status=1,2,3')
this.sameTasks = downTasks
? downTasks.filter(task => task.response.supportRange && task.response.totalSize === this.response.totalSize)
: []
if (this.sameTasks.length > 0) {
const _this = this
this.$Modal.confirm({
title: _this.$t('tip.tip'),
content: _this.$t('tasks.checkSameTask'),
okText: _this.$t('tip.ok'),
cancelText: _this.$t('tip.cancel'),
onOk() {
_this.selectOldTask = true
},
onCancel() {
_this.sameTasks = []
}
})
}
}
},
getFileSeparator() {
if (window.navigator.platform.indexOf('Win') != -1) {
return '\\'
} else {
return '/'
}
},
sameTaskChange(taskId) {
const oldTask = this.sameTasks.find(task => task.id == taskId)
if (oldTask) {
this.form.config = { ...oldTask.config }
this.selectOldTask = false
this.disabledForm = true
} else {
this.selectOldTask = true
}
},
setDefaultConfig() {
this.form.config = {}
this.$noSpinHttp.get('http://127.0.0.1:26339/config').then(result => {
const serverConfig = result.data
this.form.config = {
...{
filePath: serverConfig.filePath,
connections: serverConfig.connections,
timeout: serverConfig.timeout,
retryCount: serverConfig.retryCount,
autoRename: serverConfig.autoRename,
speedLimit: serverConfig.speedLimit
},
...this.config
}
})
}
},
created() {
this.setDefaultConfig()
this.init(this.visible)
}
}
</script>
================================================
FILE: front/src/components/Task/Resolve.vue
================================================
<template>
<Modal :title="$t('tasks.createTask')"
:value="value"
@input="$emit('input', arguments[0])"
:closable="false"
:mask-closable="false"
@on-visible-change="onReset">
<Form ref="form"
:rules="rules"
:model="form"
:label-width="60">
<FormItem :label="$t('tasks.method')"
prop="method">
<Select v-model="form.method"
style="width:70px;">
<Option value="GET">GET</Option>
<Option value="POST">POST</Option>
</Select>
</FormItem>
<FormItem :label="$t('tasks.url')"
prop="url">
<Input v-model="form.url" />
</FormItem>
<FormItem :label="$t('tasks.option')">
<Checkbox v-model="hasHead">{{ $t('tasks.head') }}</Checkbox>
<Checkbox v-model="hasBody">{{ $t('tasks.body') }}</Checkbox>
</FormItem>
<FormItem v-show="hasHead"
:label="$t('tasks.head')"
prop="heads">
<div v-for="(head, index) in form.heads"
:key="index"
:class="index === 0 ? null : 'head-margin' ">
<Input class="head-input"
v-model="head.key"
placeholder="key" />
<Input class="head-input"
v-model="head.value"
placeholder="value" />
<Icon v-if="index !== 0"
type="minus-circled"
@click="delHead(index)"></Icon>
<Icon v-if="index === form.heads.length - 1"
type="plus-circled"
@click="addHead"></Icon>
</div>
</FormItem>
<FormItem v-show="hasBody"
:label="$t('tasks.body')"
prop="body">
<Input type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
v-model="form.body" />
</FormItem>
</Form>
<div slot="footer">
<Button type="primary"
@click="onSubmit">{{ $t('tip.ok') }}</Button>
<Button @click="$emit('input', false)">{{ $t('tip.cancel') }}</Button>
</div>
</Modal>
</template>
<script>
import { onResolve } from '../../common/native.js'
export default {
props: {
value: {
type: Boolean
}
},
data() {
return {
hasHead: false,
hasBody: false,
form: {
method: 'GET',
url: '',
heads: [],
body: '',
dir: ''
},
rules: {
url: [
{ required: true, message: this.$t('tip.notNull') },
{ pattern: /^https?:\/\/.*$/i, message: this.$t('tip.fmtErr') }
]
}
}
},
watch: {
hasHead(val) {
if (val && this.form.heads.length === 0) {
this.addHead()
}
}
},
methods: {
addHead() {
this.form.heads.push({ key: '', value: '' })
},
delHead(index) {
this.form.heads.splice(index, 1)
},
onSubmit() {
this.$refs['form'].validate(async valid => {
if (valid) {
const requestData = {
method: this.form.method,
url: this.form.url,
heads: {},
body: ''
}
if (this.hasHead) {
for (let head of this.form.heads) {
if (head.key && head.value) {
requestData.heads[head.key] = head.value
}
}
}
if (this.hasBody) {
requestData.body = this.form.body
}
this.$Spin.show()
try {
let resolveData = await onResolve(requestData)
if (!resolveData) {
const result = await this.$http.put('http://127.0.0.1:26339/util/resolve', requestData)
resolveData = result.data
}
this.$emit('input', false)
const request = JSON.stringify(resolveData.request)
const response = JSON.stringify(resolveData.response)
const config = JSON.stringify(resolveData.config)
const data = JSON.stringify(resolveData.data)
this.$router.push({
path: '/',
query: { request: request, response: response, config: config, data: data }
})
} finally {
this.$Spin.hide()
}
}
})
},
onReset(visible) {
if (visible) {
this.$refs['form'].resetFields()
this.hasHead = false
this.hasBody = false
}
}
}
}
</script>
<style scoped lang="less">
.head-input {
width: 40%;
& + .head-input {
margin-left: 10px;
}
}
.ivu-icon {
margin-left: 10px;
font-size: 22px;
cursor: pointer;
position: relative;
top: 5px;
}
.head-margin {
margin-top: 10px;
}
</style>
================================================
FILE: front/src/i18n/en-US.js
================================================
export default {
nav: {
tasks: 'Tasks',
extension: 'Extensions',
setting: 'Settings',
about: 'About',
support: 'Support Us'
},
tip: {
tip: 'Hint',
ok: 'OK',
cancel: 'Cancel',
notNull: 'Cannot be empty',
fmtErr: 'Incorrect format',
choose: 'Choose',
save: 'Save',
refresh: 'Refresh',
copySucc: 'Copied successfully',
copyFail: 'Copy failed',
saveSucc: 'Save successfully',
saveFail: 'Save failed'
},
tasks: {
createTask: 'New Task',
continueDownloading: 'Resume',
pauseDownloads: 'Pause',
deleteTask: 'Delete Task',
deleteTaskTip: 'Delete both task and file',
revealInFolder: 'Reveal in download folder',
method: 'Method',
url: 'URL',
fileName: 'Name',
fileSize: 'Size',
connections: 'Connections',
filePath: 'Path',
status: 'Status',
operate: 'Actions',
downloadAddress: 'Address',
wait: 'Waiting',
unknowLeft: 'Unknown',
downloadSpeed: 'Speed',
createTime: 'Created',
taskProgress: 'Progress',
statusPause: 'Pause',
statusFail: 'Failed',
statusDone: 'Done',
option: 'Options',
head: 'Header',
body: 'Body',
detail: 'Details',
checkSameTask: 'The same task already exists. Refresh the task?',
sameTaskList: 'Task List',
sameTaskPlaceholder: 'Please select the task to refresh',
running: 'Downloading',
waiting: 'Waiting',
done: 'Done'
},
extension: {
conditions: 'Notes',
conditionsContent:
'When using the extension for the first time, you must install a CA certificate randomly generated by Proxyee Down. Click Install below and follow the instructions. If a Proxyee Down CA certificate has been installed, you will be prompted to delete the old CA certificate.',
install: 'Install',
globalProxy: 'Global Proxy',
proxyTip: 'View instructions',
copyPac: 'Copy PAC URL',
title: 'Title',
description: 'Description',
currVersion: 'Current Version',
newVersion: 'Latest Version',
installStatus: 'Status',
installStatusTrue: 'ON',
installStatusFalse: 'OFF',
action: 'Actions',
actionUpdate: 'Update',
actionInstall: 'Install',
uninstall: 'Uninstall',
uninstallTip: 'Do you want to uninstall this extension?',
actionDetail: 'Details',
switch: 'ON/OFF',
downloadingTip: 'Downloading...[servers(',
downloadOk: 'Downloaded successfully',
downloadErr: 'Download failed',
downloadErrTip: 'Automatically switch servers',
extCenter: 'Extension center',
installLocalExt: 'Install local extension',
installOk: 'Installed successfully',
installErr: 'Installation failed, please check the manifest.json file',
setting: 'Setting'
},
setting: {
downSetting: 'Download settings',
path: 'Path',
pathTip: 'Default download path',
connections: 'Connections',
connectionsTip: 'Default download connections',
taskLimit: 'Simultaneous download tasks',
taskSpeedLimit: 'Single task speed limit',
globalSpeedLimit: 'Global speed limit',
speedLimitTip: '0 for unlimited',
appSetting: 'System settings',
language: 'Language',
uiMode: 'UI mode',
uiModeWindows: 'Windows',
uiModeBrowser: 'Browser',
autoOpen: 'Popup at startup',
checkUpdate: 'Check for update',
checkUpdateWeek: 'Every week',
checkUpdateStartup: 'Every startup',
checkUpdateNever: 'Never',
secondProxy: {
secondProxy: 'Second proxy',
tip: 'Configure the second (pre-proxy) proxy server for the downloader',
type: 'Type',
host: 'Host',
port: 'Port',
user: 'Username',
pwd: 'password'
}
},
about: {
project: {
title: 'Project',
content:
'Proxyee-Down is an open source, free software based on the software\'s high-speed download kernel and extensions to easily and quickly download the required resources.',
githubAddress: 'Project homepage: ',
official: 'Official website: ',
community: 'Official community: ',
tutorial: 'Tutorials: ',
feedback: 'Feedback: ',
currentVersion: 'Current Version: ',
checkUpdate: 'Check update:',
noNewVersion: 'Already the latest version'
},
team: {
title: 'Team'
}
},
update: {
checkNew: 'New version available',
version: 'Version',
changeLog: 'Changelog',
update: 'Update',
done: 'Update completed',
restart: 'Restart Proxyee Down?',
error: 'Update failed, please check the network or manually download the update package'
},
alert: {
refused: 'Program exception: Connection refused',
timeout: 'Program exception: Connection timeout',
error: 'Program error',
notFound: 'Task not found',
'/tasks': {
post: {
4000: 'Params parse error',
4001: 'Request is empty',
4002: 'Request URL is empty',
4003: 'File save path is empty',
4004: 'Failed to create folder',
4005: 'No write permission',
4006: 'Not enough disk space',
4007: 'File already exists'
}
}
},
'/util/resolve': {
put: {
4000: 'Params parse error',
4001: 'Request URL is empty',
4002: 'Response status code exception',
4003: 'Request timeout'
}
},
'/config': {
put: {
4000: 'Params parse error'
}
}
}
================================================
FILE: front/src/i18n/zh-CN.js
================================================
export default {
nav: {
tasks: '任务管理',
extension: '扩展管理',
setting: '软件设置',
about: '关于项目',
support: '支持我们'
},
tip: {
tip: '提示',
ok: '确定',
cancel: '取消',
notNull: '不能为空',
fmtErr: '格式不正确',
choose: '选择',
save: '保存',
refresh: '刷新',
copySucc: '复制成功',
copyFail: '复制失败',
saveSucc: '保存成功',
saveFail: '保存失败'
},
tasks: {
createTask: '创建任务',
continueDownloading: '继续下载',
pauseDownloads: '暂停下载',
deleteTask: '删除任务',
deleteTaskTip: '是否删除任务和文件?',
revealInFolder: '打开下载目录',
method: '方法',
url: '链接',
fileName: '文件名',
fileSize: '大小',
connections: '连接数',
filePath: '路径',
status: '状态',
operate: '操作',
downloadAddress: '下载地址',
downloadSpeed: '下载速度',
createTime: '开始时间',
taskProgress: '任务进度',
wait: '待下载',
unknowLeft: '未知',
statusPause: '暂停',
statusFail: '失败',
statusDone: '完成',
option: '附加',
head: '请求头',
body: '请求体',
detail: '下载详情',
checkSameTask: '检测到可能相同的下载任务,是否选择任务进行刷新?',
sameTaskList: '任务列表',
sameTaskPlaceholder: '请选择要刷新的任务',
running: '进行中',
waiting: '等待中',
done: '已完成'
},
extension: {
conditions: '使用须知',
conditionsContent:
'首次使用扩展模块时,必须安装由Proxyee Down随机生成的一个CA证书,点击下面的安装按钮并按系统的引导进行确认安装。(注意:程序会在安装前检测操作系统中是否有安装过证书,当检测到有安装的情况会提示删除对应的旧CA证书)',
install: '安装',
globalProxy: '全局代理',
proxyTip: '点击查看说明',
copyPac: '复制PAC链接',
title: '名称',
description: '描述',
currVersion: '当前版本',
newVersion: '最新版本',
installStatus: '状态',
installStatusTrue: '已安装',
installStatusFalse: '未安装',
action: '操作',
actionUpdate: '更新',
actionInstall: '安装',
uninstall: '卸载',
uninstallTip: '确定卸载此扩展吗?',
actionDetail: '详情',
switch: '开关',
downloadingTip: '下载中...[服务器(',
downloadOk: '下载成功',
downloadErr: '下载失败',
downloadErrTip: '自动切换服务器',
extCenter: '扩展中心',
installLocalExt: '加载本地扩展',
installOk: '加载成功',
installErr: '加载失败,请检查manifest.json文件',
setting: '设置'
},
setting: {
downSetting: '下载设置',
path: '路径',
pathTip: '默认下载路径',
connections: '连接数',
connectionsTip: '默认连接数',
taskLimit: '同时下载任务数',
taskSpeedLimit: '单任务限速',
globalSpeedLimit: '全局限速',
speedLimitTip: '0为不限速',
appSetting: '系统设置',
language: '语言',
uiMode: 'UI模式',
uiModeWindows: '窗口',
uiModeBrowser: '浏览器',
autoOpen: '启动弹窗',
checkUpdate: '检查更新',
checkUpdateWeek: '每周',
checkUpdateStartup: '每次启动',
checkUpdateNever: '从不',
secondProxy: {
secondProxy: '二级代理',
tip: '配置下载器的二级(前置)代理服务器',
type: '类型',
host: '服务器',
port: '端口',
user: '用户名',
pwd: '密码'
}
},
about: {
project: {
title: '项目',
content: 'Proxyee Down是一款开源的免费软件,基于本软件的高速下载内核和扩展,可以方便并快速的下载所需资源。',
githubAddress: '项目主页:',
official: '官方网站:',
community: '官方社区:',
tutorial: '使用教程:',
feedback: '问题反馈:',
currentVersion: '当前版本:',
checkUpdate: '检查更新:',
noNewVersion: '已经是最新版本'
},
team: {
title: '团队'
}
},
update: {
checkNew: '检测到新版本',
version: '版本号',
changeLog: '更新内容',
update: '更新',
done: '更新完毕',
restart: '是否重新启动?',
error: '更新失败,请检查网络或手动下载更新包'
},
alert: {
refused: '程序异常,拒绝访问',
timeout: '程序异常,连接超时',
error: '程序出错',
notFound: '任务不存在',
'/tasks': {
post: {
4000: '参数解析错误',
4001: '请求对象不能为空',
4002: '请求地址不能为空',
4003: '文件保存路径不能为空',
4004: '创建文件夹失败',
4005: '无写入权限',
4006: '磁盘空间不足',
4007: '文件已存在'
}
},
'/util/resolve': {
put: {
4000: '参数解析错误',
4001: '请求地址不能为空',
4002: '响应状态码异常',
4003: '请求超时'
}
},
'/config': {
put: {
4000: '参数解析错误'
}
}
}
}
================================================
FILE: front/src/i18n/zh-TW.js
================================================
export default {
nav: {
tasks: '任務管理',
extension: '擴充管理',
setting: '軟體設定',
about: '關於專案',
support: '支持我們'
},
tip: {
tip: '提示',
ok: '確定',
cancel: '取消',
notNull: '不能為空',
fmtErr: '格式不正確',
choose: '選擇',
save: '儲存',
refresh: '刷新',
copySucc: '複製成功',
copyFail: '複製失敗',
saveSucc: '保存成功',
saveFail: '保存失敗'
},
tasks: {
createTask: '建立任務',
continueDownloading: '繼續下載',
pauseDownloads: '暫停下載',
deleteTask: '刪除任務',
deleteTaskTip: '是否刪除任務和檔案?',
revealInFolder: '打開下載目錄',
method: '方法',
url: '連結',
fileName: '名稱',
fileSize: '大小',
connections: '連線數',
filePath: '路徑',
status: '狀態',
operate: '操作',
downloadAddress: '下載位址',
downloadSpeed: '下載速度',
createTime: '開始時間',
taskProgress: '任務進度',
wait: '待下載',
unknowLeft: '不詳',
statusPause: '暫停',
statusFail: '失敗',
statusDone: '完成',
option: '附加',
head: '要求標頭',
body: '要求主體',
detail: '下載細節',
checkSameTask: '偵測到可能相同的下載任務,是否選擇任務進行更新?',
sameTaskList: '任務清單',
sameTaskPlaceholder: '請選擇要更新的任務',
running: '進行中',
waiting: '等待中',
done: '已完成'
},
extension: {
conditions: '使用須知',
conditionsContent:
'首次使用擴充模組時,必須安裝由 Proxyee Down 隨機產生的一個 CA 憑證,點選下方的安裝按鈕並依系統的引導進行確認安裝。(注意:程式會在安裝前偵測作業系統中是否有安裝過憑證,當偵測到有安裝的情況會提示刪除對應的舊 CA 憑證)',
install: '安裝',
globalProxy: '全域代理',
proxyTip: '點選檢視說明',
copyPac: '複製 PAC 連結',
title: '名稱',
description: '描述',
currVersion: '目前版本',
newVersion: '最新版本',
installStatus: '狀態',
installStatusTrue: '已安装',
installStatusFalse: '未安装',
action: '操作',
actionUpdate: '更新',
actionInstall: '安裝',
actionDetail: '細節',
uninstall: '卸載',
uninstallTip: '確定卸載此擴展嗎?',
switch: '開關',
downloadingTip: '下載中...[伺服器(',
downloadOk: '下載成功',
downloadErr: '下載失敗',
downloadErrTip: '自動切換伺服器',
extCenter: '擴充中心',
installLocalExt: '加載本地擴充',
installOk: '加載成功',
installErr: '加載失敗,請檢查manifest.json文件',
setting: '預設'
},
setting: {
downSetting: '下載設定',
path: '路徑',
pathTip: '預設下載路徑',
connections: '連線數',
connectionsTip: '預設連線數',
taskLimit: '同時下載任務數',
taskSpeedLimit: '單任務限速',
globalSpeedLimit: '全域限速',
speedLimitTip: '0為不限速',
appSetting: '系統設定',
language: '語言',
uiMode: 'UI 模式',
uiModeWindows: '視窗',
uiModeBrowser: '瀏覽器',
autoOpen: '啟動彈窗',
checkUpdate: '檢查更新',
checkUpdateWeek: '每週',
checkUpdateStartup: '每次啟動',
checkUpdateNever: '從不',
secondProxy: {
secondProxy: '二級代理',
tip: '配置下載器的二級(前置)代理服務器',
type: '類型',
host: '服務器',
port: '端口',
user: '用戶名',
pwd: '密碼'
}
},
about: {
project: {
title: '項目',
content: 'Proxyee Down 是一款開源的免費軟體,基於本軟體的高速下載核心和擴充套件,可以方便並快速的下載所需資源。',
githubAddress: '項目首頁:',
official: '官方網站:',
community: '官方社區:',
tutorial: '使用教學:',
feedback: '問題回報:',
currentVersion: '目前版本:',
checkUpdate: '檢查更新:',
noNewVersion: '已經是最新版本'
},
team: {
title: '團隊'
}
},
update: {
checkNew: '偵測到新版本',
version: '版本號',
changeLog: '更新內容',
update: '更新',
done: '更新完畢',
restart: '是否重新啟動?',
error: '更新失敗,請檢查網絡或手動下載更新包'
},
alert: {
refused: '程式異常,拒絕存取',
timeout: '程式異常,連線逾時',
error: '程式出錯',
notFound: '任務不存在',
'/tasks': {
post: {
4000: '參數解析錯誤',
4001: '要求對象不能為空',
4002: '要求位址不能為空',
4003: '檔案儲存路徑不能為空',
4004: '建立資料夾失敗',
4005: '無寫入權限',
4006: '磁碟空間不足',
4007: '檔案已存在'
}
},
'/util/resolve': {
put: {
4000: '參數解析錯誤',
4001: '要求位址不能為空',
4002: '回應狀態碼異常',
4003: '請求超時'
}
},
'/config': {
put: {
4000: '參數解析錯誤'
}
}
}
}
================================================
FILE: front/src/main.js
================================================
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import iView from 'iview'
import axios from 'axios'
import VueI18n from 'vue-i18n'
import numeral from 'numeral'
import 'iview/dist/styles/iview.css'
import en_US from 'iview/dist/locale/en-US'
import zh_CN from 'iview/dist/locale/zh-CN'
import zh_TW from 'iview/dist/locale/zh-TW'
import http from './common/http'
import { getInitConfig } from './common/native'
Vue.use(VueI18n)
Vue.use(iView)
Vue.config.productionTip = false
// Setting i18n
const i18n = new VueI18n({
locale: 'zh-CN',
messages: {
'en-US': Object.assign(require('./i18n/en-US').default, en_US),
'zh-CN': Object.assign(require('./i18n/zh-CN').default, zh_CN),
'zh-TW': Object.assign(require('./i18n/zh-TW').default, zh_TW)
}
})
Vue.prototype.$noSpinHttp = axios.create()
Vue.prototype.$http = http.build()
Vue.prototype.$http.interceptors.response.use(
response => {
return response
},
error => {
if (!error.response) {
Vue.prototype.$Message.error(i18n.t('alert.refused'))
} else if (error.response.status == 400) {
let i18nKey =
'alert["' +
new URL(error.config.url).pathname +
'"]' +
'.' +
error.config.method +
'.' +
error.response.data.code
Vue.prototype.$Message.error(i18n.t(i18nKey))
} else if (error.response.status == 404) {
Vue.prototype.$Message.error(i18n.t('alert.notFound'))
} else if (error.response.status == 504) {
Vue.prototype.$Message.error(i18n.t('alert.timeout'))
} else {
Vue.prototype.$Message.error(i18n.t('alert.error'))
}
return Promise.reject(error)
}
)
//去除字节大小格式化后的i字符
const format = numeral.prototype.constructor.fn.format
numeral.prototype.constructor.fn.format = function(fmt) {
let result = format.call(this, fmt)
if (/^.*ib$/.test(fmt)) {
result = result.replace('i', '')
}
return result
}
Vue.prototype.$numeral = numeral
Date.prototype.format = function(fmt) {
var o = {
'M+': this.getMonth() + 1, // Month
'd+': this.getDate(), // Day
'h+': this.getHours(), // Hour
'm+': this.getMinutes(), // Minute
's+': this.getSeconds(), // Second
'q+': Math.floor((this.getMonth() + 3) / 3), // Quarter
S: this.getMilliseconds() // Millisecond
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
}
}
return fmt
}
Promise.prototype.finally = function(callback) {
let P = this.constructor
return this.then(
value => P.resolve(callback()).then(() => value),
reason =>
P.resolve(callback()).then(() => {
throw reason
})
)
}
// Change the page according to the routing changes title
router.beforeEach((to, from, next) => {
if (to.meta.title) {
document.title = `Proxyee Down-${to.meta.title}`
}
next()
})
// Get client configuration information
getInitConfig()
.then(result => {
Vue.prototype.$config = result
// Set default language
i18n.locale = result.locale
})
.catch(() => {
Vue.prototype.$config = {}
})
.finally(() => {
new Vue({
router,
store,
i18n,
data() {
return {
badges: { tasks: 0, extension: 0, setting: 0, about: 0, support: 0 }
}
},
render: h => h(App)
}).$mount('#app')
})
================================================
FILE: front/src/router.js
================================================
import Vue from 'vue'
import Router from 'vue-router'
import Tasks from './views/Tasks.vue'
import Extension from './views/Extension.vue'
import Setting from './views/Setting.vue'
import About from './views/About.vue'
import Support from './views/Support.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/tasks'
},
{
path: '/tasks',
name: 'tasks',
component: Tasks
},
{
path: '/extension',
name: 'extension',
component: Extension
},
{
path: '/setting',
name: 'setting',
component: Setting
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/support',
name: 'Support',
component: Support
}
]
})
================================================
FILE: front/src/store.js
================================================
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {}
})
================================================
FILE: front/src/views/About.vue
================================================
<template>
<div class="v-about">
<Card>
<p slot="title">{{ $t("about.project.title") }}</p>
<ul class="project-ul">
<li>
<p>{{ $t("about.project.content") }}</p>
</li>
<li>
<b>{{ $t("about.project.githubAddress") }}</b>
<a href="javascript:void(0)"
@click="openUrl('https://github.com/proxyee-down-org/proxyee-down')">
GitHub@proxyee-down
</a>
</li>
<li>
<b>{{ $t("about.project.official") }}</b>
<a href="javascript:void(0)"
@click="openUrl('https://pdown.org')">
pdown.org
</a>
</li>
<li>
<b>{{ $t("about.project.community") }}</b>
<a href="javascript:void(0)"
@click="openUrl('https://community.pdown.org')">
community.pdown.org
</a>
</li>
<li>
<b>{{ $t("about.project.tutorial") }}</b>
<a href="javascript:void(0)"
@click="openUrl('https://github.com/proxyee-down-org/proxyee-down/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B')">
GitHub@proxyee-down/wiki
</a>
</li>
<li>
<b>{{ $t("about.project.feedback") }}</b>
<a href="javascript:void(0)"
@click="openUrl('https://github.com/proxyee-down-org/proxyee-down/issues')">
GitHub@proxyee-down/issues
</a>
</li>
<li>
<b>{{ $t("about.project.currentVersion") }}</b>
<span> {{ $config.version }}</span>
</li>
<li>
<b>{{ $t("about.project.checkUpdate") }}</b>
<Icon type="ios-refresh-empty"
@click="checkUpdate()"></Icon>
</li>
</ul>
</Card>
<br>
<Card class="team">
<p slot="title">{{ $t("about.team.title") }}</p>
<Card class="item">
<div>
<img src="team_header/monkeyWie.png"
@click="openUrl('https://github.com/monkeyWie')">
<b>monkeyWie</b>
</div>
</Card>
<Card class="item">
<div>
<img src="team_header/Black-Hole.png"
@click="openUrl('https://github.com/BlackHole1')">
<b>Black-Hole</b>
</div>
</Card>
<Card class="item">
<div>
<img src="team_header/NISAL.png"
@click="openUrl('https://github.com/hiNISAL')">
<b>NISAL</b>
</div>
</Card>
<br>
</Card>
<Modal v-model="hasUpdate"
:title="$t('update.checkNew')">
<b>{{ $t('update.version') }}:</b>
<span>{{ versionInfo.version }}</span>
<br>
<br>
<b>{{ $t('update.changeLog') }}:</b>
<div style="padding-top:10px;"
v-html="versionInfo.description"></div>
<span slot="footer">
<Button @click="hasUpdate = false">{{ $t('tip.cancel') }}</Button>
<Button type="primary"
@click="doUpdate()">{{ $t('update.update') }}</Button>
</span>
</Modal>
<Modal v-model="restatModel"
:title="$t('update.done')">
<h3>{{ $t('update.restart') }}</h3>
<span slot="footer">
<Button @click="restatModel = false">{{ $t('tip.cancel') }}</Button>
<Button type="primary"
@click="doRestart()">{{ $t('tip.ok') }}</Button>
</span>
</Modal>
<Spin v-if="showUpdateProgress"
size="large"
class="update-progress"
fix>
<Circle :percent="updateInfo.progress"
:size="150">
<h1>{{ updateInfo.progress.toFixed(2) }}%</h1>
<p>{{ $numeral(updateInfo.speed).format('0.00 ib') }}/S</p>
</Circle>
</Spin>
</div>
</template>
<script>
import { openUrl, doUpdate, getUpdateProgress, doRestart } from '../common/native.js'
export default {
name: 'about',
data() {
return {
hasUpdate: false,
showUpdateProgress: false,
versionInfo: {},
restatModel: false,
updateInfo: {
progress: 0,
speed: 0
}
}
},
watch: {
$route() {
this.onRouteChange()
}
},
methods: {
openUrl(url) {
openUrl(url)
},
onRouteChange() {
if (this.$route.query.checkUpdate) {
this.checkUpdate(this.$route.query.versionInfo)
}
},
checkUpdate(versionInfoQuery) {
if (versionInfoQuery) {
const versionInfo = JSON.parse(versionInfoQuery)
if (versionInfo.version > this.$config.version) {
this.hasUpdate = true
this.versionInfo = versionInfo
}
} else {
this.$http.get(this.$config.adminServer + 'version/checkUpdate').then(result => {
const versionInfo = result.data
if (versionInfo && versionInfo.version > this.$config.version) {
this.hasUpdate = true
this.versionInfo = versionInfo
} else {
this.$Message.warning(this.$t('about.project.noNewVersion'))
}
})
}
},
doUpdate() {
this.showUpdateProgress = true
this.hasUpdate = false
//开始下载更新包
doUpdate(this.versionInfo.path)
.then(() => {
//获取更新进度
const updateProgressInterval = setInterval(() => {
getUpdateProgress().then(result => {
this.updateInfo.progress = (result.downSize / result.totalSize) * 100
this.updateInfo.speed = result.speed
if (result.status == 3) {
//下载失败
this.$Message.error({
content: this.$t('update.error'),
duration: 0
})
} else if (result.status == 4) {
//下载完成
this.restatModel = true
}
if (result.status == 3 || result.status == 4) {
this.showUpdateProgress = false
clearInterval(updateProgressInterval)
}
})
}, 1000)
})
.catch(() => {
this.showUpdateProgress = false
this.$Message.error({
content: this.$t('update.error'),
duration: 0
})
})
},
doRestart() {
doRestart().then((this.restatModel = false))
}
},
created() {
this.onRouteChange()
}
}
</script>
<style lang="less" scoped>
.v-about {
.team {
.item {
display: inline-table;
margin-right: 8px;
width: 10rem;
height: 10rem;
div {
text-align: center;
img {
width: 6.25rem;
border-radius: 50px;
cursor: pointer;
}
b,
span {
display: block;
}
b {
margin-bottom: 5px;
}
}
}
}
.project-ul {
list-style: none;
li:not(:first-child) {
padding-top: 5px;
}
.ivu-icon {
cursor: pointer;
font-size: 1.5em;
position: relative;
top: 4px;
left: 4px;
}
}
}
.update-progress {
z-index: 1001;
h1 {
color: #3f414d;
font-size: 28px;
font-weight: normal;
}
p {
color: #657180;
font-size: 14px;
padding-top: 10px;
}
}
</style>
================================================
FILE: front/src/views/Extension.vue
================================================
<template>
<div v-if="!certStatus">
<Card shadow>
<p slot="title">{{ $t('extension.conditions') }}</p>
<p>{{ $t('extension.conditionsContent') }}</p>
</Card>
<Button type="primary"
class="install-button"
@click="installCert">{{ $t('extension.install') }}</Button>
</div>
<div v-else>
<div class="proxy-switch-div">
<b>{{ $t('extension.globalProxy') }}</b>
<Switch v-model="proxySwitch"
@on-change="changeProxyMode">
</Switch>
<Tooltip :content="$t('extension.proxyTip')"
theme="light">
<Icon type="help-circled"
@click="openUrl('https://github.com/proxyee-down-org/proxyee-down/wiki/%E5%AE%89%E8%A3%85%E6%89%A9%E5%B1%95')"
class="action-icon tip-icon" />
</Tooltip>
<Tooltip class="icon-button"
:content="$t('tip.refresh')"
theme="light">
<Button type="info"
shape="circle"
icon="loop"
@click="loadExtensions">
</Button>
</Tooltip>
<Tooltip class="icon-button"
:content="$t('extension.copyPac')"
theme="light">
<Button type="info"
shape="circle"
icon="ios-copy"
@click="copyPac">
</Button>
</Tooltip>
<Tooltip class="icon-button"
:content="$t('extension.installLocalExt')"
theme="light">
<Button type="info"
shape="circle"
icon="android-folder-open"
@click="installLocalExt">
</Button>
</Tooltip>
</div>
<Tabs type="card"
:animated="false"
v-model="activeTab">
<TabPane :label="$t('extension.extCenter')+'('+onlinePage.totalCount+')'"
name="online"
icon="android-playstore">
<Table :columns="onlineColumns"
:data="onlinePage.data"
:loading="onlineLoading"></Table>
<div style="margin: 10px;overflow: hidden">
<div style="float: right;">
<Page :total="onlinePage.totalCount"
:current="onlinePage.pageNum"
:page-size="onlinePage.pageSize"
@on-change="searchExtensions(arguments[0])"></Page>
</div>
</div>
</TabPane>
<TabPane :label="$t('extension.installStatusTrue')+'('+localAllList.length+')'"
name="local"
icon="social-buffer">
<Table :columns="localColumns"
:data="localAllList"></Table>
<Modal v-model="settingModal"
:title="$t('extension.setting')">
<ExtensionSetting :settings="settings" />
<span slot="footer">
<Button @click="settingModal = false">{{ $t('tip.cancel') }}</Button>
<Button type="primary"
@click="saveSetting()">{{ $t('tip.ok') }}</Button>
</span>
</Modal>
</TabPane>
</Tabs>
<Spin fix
v-if="spinShow">
<Icon type="load-c"
class="spin-icon-load"></Icon>
<div>{{ spinTip }}</div>
</Spin>
</div>
</template>
<script>
import { Icon, Tag } from 'iview'
import ExtensionSetting from '../components/ExtensionSetting.vue'
import {
checkCert,
installCert,
getProxyMode,
changeProxyMode,
getExtensions,
installExtension,
updateExtension,
installLocalExtension,
uninstallExtension,
toggleExtension,
openUrl,
copy,
showDirChooser,
updateExtensionSetting
} from '../common/native.js'
export default {
name: 'extension',
components: {
ExtensionSetting
},
data() {
return {
certStatus: false,
proxySwitch: false,
activeTab: 'online',
onlineLoading: false,
onlinePage: {
pageNum: 1,
pageSize: 10,
totalPage: 0,
totalCount: 0,
data: []
},
localAllList: [],
spinShow: false,
spinTip: '',
onlineColumns: this.buildCommonColumns(),
localColumns: this.buildCommonColumns(true),
settingModal: false,
settingExt: null,
settings: []
}
},
methods: {
installCert() {
installCert().then(status => {
this.certStatus = status
if (status) {
// Install Success
changeProxyMode(1).then(() => (this.proxySwitch = true))
this.loadExtensions()
}
})
},
changeProxyMode(val) {
changeProxyMode(val ? 1 : 0)
},
buildCommonColumns(local) {
const _this = this
return [
{
title: this.$t('extension.title'),
key: 'title'
},
{
title: this.$t('extension.description'),
key: 'description'
},
{
title: this.$t('extension.currVersion'),
key: 'currVersion',
width: 100
},
...(local
? []
: [
{
title: this.$t('extension.newVersion'),
key: 'version',
width: 100
}
]),
{
title: this.$t('extension.installStatus'),
key: 'meta.disabled',
align: 'center',
width: 100,
render(h, params) {
return (
<div>
{params.row.installed ? (
<Tag color="green">{_this.$t('extension.installStatusTrue')}</Tag>
) : (
<Tag>{_this.$t('extension.installStatusFalse')}</Tag>
)}
</div>
)
}
},
{
title: this.$t('extension.action'),
key: 'action',
align: 'center',
width: 150,
render(h, params) {
return [
...(params.row.installed
? [
...(params.row.currVersion < params.row.version
? [
<Icon
type="ios-cloud-upload-outline"
class="action-icon"
title={_this.$t('extension.actionUpdate')}
nativeOnClick={() => _this.downExtension(true, params.row, 0)}
/>
]
: []),
<Icon
type="trash-a"
class="action-icon"
title={_this.$t('extension.uninstall')}
nativeOnClick={() => _this.uninstallExtension(params.row)}
/>,
...(params.row.settings && params.row.settings.length
? [
<Icon
type="android-settings"
class="action-icon"
title={_this.$t('extension.setting')}
nativeOnClick={() => {
_this.settingModal = true
_this.settingExt = params.row
_this.settings = params.row.settings
const settingValues = params.row.meta.settings
if (settingValues) {
_this.settings.forEach(s => (s.value = settingValues[s.name] || s.value))
}
}}
/>
]
: [])
]
: [
<Icon
type="ios-cloud-download-outline"
class="action-icon"
title={_this.$t('extension.actionInstall')}
nativeOnClick={() => _this.downExtension(false, params.row, 0)}
/>
]),
<Icon
type="ios-home"
class="action-icon"
title={_this.$t('extension.actionDetail')}
nativeOnClick={() => {
_this.openHomepage(params.row)
}}
/>
]
}
},
{
title: this.$t('extension.switch'),
key: 'switch',
align: 'center',
width: 100,
render(h, params) {
return (
<Switch
disabled={!params.row.installed}
v-model={params.row.meta.enabled}
onOn-change={enabled => _this.changeEnabled(enabled, params.row)}
/>
)
}
}
]
},
changeEnabled(enabled, row) {
toggleExtension({ path: row.meta.path, enabled: enabled, local: row.meta.local }).then(() => {
const localExt = this.localAllList.find(localExt => localExt.meta.path == row.meta.path)
localExt.meta.enabled = enabled
this.refreshExtensions()
})
},
downExtension(isUpdate, row, index) {
let _this = this
let extFileServers = this.$config.extFileServers
if (index < extFileServers.length) {
this.spinShow = true
let extFileServer = extFileServers[index]
let url = new URL(extFileServer)
this.spinTip =
this.$t('extension.downloadingTip') + (index + 1) + '/' + extFileServers.length + '):' + url.host + ']'
const params = {
server: extFileServer,
path: row.meta.path,
files: row.files
}
let downPromise = isUpdate ? updateExtension(params) : installExtension(params)
downPromise
.then(() => {
const localExt = this.localAllList.find(localExt => localExt.meta.path == row.meta.path)
if (localExt) {
localExt.version = row.version
localExt.currVersion = row.version
localExt.title = row.title
localExt.description = row.description
} else {
row.installed = true
row.currVersion = row.version
row.meta = { path: row.meta.path, enabled: true }
this.localAllList.push(row)
}
this.getLocalExtensions(() => {
this.refreshExtensions()
})
this.spinShow = false
this.$Message.success({
content: this.$t('extension.downloadOk'),
closable: true
})
const afterBadges = this.$root.badges.extension - 1
this.$root.badges.extension = afterBadges < 0 ? 0 : afterBadges
// Update info to server
this.$noSpinHttp.get(
this.$config.adminServer +
'extension/down?ext_id=' +
row.id +
'&version=' +
row.version +
'&pd_version=' +
this.$config.version
)
})
.catch(error => {
if (index + 1 < extFileServers.length) {
this.$Notice.error({
title: this.$t('extension.downloadErr'),
desc: this.$t('extension.downloadErrTip'),
onClose() {
_this.downExtension(isUpdate, row, index + 1)
}
})
} else {
this.$Notice.error({
title: this.$t('extension.downloadErr'),
desc: error.response.data.error,
duration: 0,
closable: true
})
this.spinShow = false
}
})
}
},
installLocalExt() {
showDirChooser().then(result => {
if (!result) {
return
}
installLocalExtension(result.path)
.then(localExt => {
localExt.installed = true
localExt.currVersion = localExt.version
this.localAllList.push(localExt)
this.refreshExtensions()
this.$Message.success(this.$t('extension.installOk'))
this.activeTab = 'local'
})
.catch(error => {
if (error.response.status == 400) {
this.$Message.error(this.$t('extension.installErr'))
} else {
this.$Message.error(this.$t('alert.error'))
}
})
})
},
uninstallExtension(row) {
const _this = this
_this.$Modal.confirm({
title: _this.$t('extension.uninstall'),
content: _this.$t('extension.uninstallTip'),
okText: _this.$t('tip.ok'),
cancelText: _this.$t('tip.cancel'),
onOk() {
uninstallExtension(row.meta.path, row.meta.local)
.then(() => {
const index = _this.localAllList.findIndex(localExt => localExt.meta.path == row.meta.path)
if (index != -1) {
_this.localAllList.splice(index, 1)
_this.refreshExtensions()
}
})
.catch(() => _this.$Message.error(_this.$t('alert.error')))
}
})
},
getLocalExtensions(callback) {
getExtensions().then(localAllList => {
this.localAllList = localAllList
this.localAllList.forEach(localExt => {
localExt.installed = true
localExt.currVersion = localExt.version
})
if (callback) {
callback()
}
})
},
loadExtensions() {
// Loading proxy mode
getProxyMode().then(mode => (this.proxySwitch = mode === 1))
// Get local installed extension
this.getLocalExtensions(() => {
this.searchExtensions()
})
},
searchExtensions(pageSize) {
pageSize = pageSize ? pageSize : 1
this.onlineLoading = true
this.$noSpinHttp
.get(`${this.$config.adminServer}extension/search?pageSize=${pageSize}&version=${this.$config.version}`)
.then(result => {
this.onlinePage = result.data
this.refreshExtensions()
})
.finally(() => (this.onlineLoading = false))
},
refreshExtensions() {
this.onlinePage.data.forEach(onlineExt => {
const localExt = this.localAllList.find(localExt => localExt.meta.path == onlineExt.path)
if (localExt) {
this.$set(onlineExt, 'installed', true)
this.$set(onlineExt, 'currVersion', localExt.version)
this.$set(onlineExt, 'meta', localExt.meta)
this.$set(onlineExt, 'settings', localExt.settings)
} else {
onlineExt.installed = false
onlineExt.meta = { path: onlineExt.path, enabled: false }
}
})
},
openHomepage(row) {
const url =
row.homepage ||
'https://github.com/proxyee-down-org/proxyee-down-extension/blob/master' + row.meta.path + '/README.md'
openUrl(url)
},
openUrl(url) {
openUrl(url)
},
copyPac() {
const { protocol, host } = window.location
copy({
type: 'text',
data: `${protocol}//${host}/pac/pdown.pac?t=` + new Date().getTime()
})
.then(() => this.$Message.success(this.$t('tip.copySucc')))
.catch(() => this.$Message.error(this.$t('tip.copyFail')))
},
saveSetting() {
const setting = {}
this.settingExt.settings.forEach(s => {
setting[s.name] = s.value
})
updateExtensionSetting(this.settingExt.meta.path, setting)
.then(() => this.$Message.success(this.$t('tip.saveSucc')))
.catch(() => this.$Message.error(this.$t('tip.saveFail')))
}
},
created() {
// Check whether the certificate has been installed
checkCert().then(status => {
this.certStatus = status
// Already installed
if (status) {
this.loadExtensions()
}
})
}
}
</script>
<style scoped>
.install-button {
margin-top: 1.25rem;
}
.proxy-switch-div {
margin-bottom: 1.25rem;
}
.proxy-switch-div b {
padding-right: 10px;
}
.proxy-switch-div .icon-button {
margin-left: 25px;
}
.spin-icon-load {
animation: ani-demo-spin 1s linear infinite;
}
</style>
================================================
FILE: front/src/views/Setting.vue
================================================
<template>
<Form ref="form"
:label-width="100"
:model="form"
:rules="rules"
class="setting-form">
<Collapse :value="['down','app']">
<Panel name="down">
{{ $t('setting.downSetting') }}
<div slot="content">
<FormItem :label="$t('setting.path')"
prop="downConfig.filePath">
<FileChoose v-model="form.downConfig.filePath"
style="width: 30rem" />
<Tooltip class="item"
placement="right">
<Icon type="help-circled"
class="action-icon tip-icon" />
<div slot="content">
<p>{{ $t('setting.pathTip') }}</p>
</div>
</Tooltip>
</FormItem>
<FormItem :label="$t('setting.connections')"
prop="downConfig.connections">
<Slider v-model="form.downConfig.connections"
:min="2"
:max="256"
:step="2"
show-input
style="width: 30rem" />
<Tooltip class="item"
style="position:absolute;left:30rem;top:-15px;"
placement="right">
<Icon type="help-circled"
class="action-icon tip-icon" />
<div slot="content">
<p>{{ $t('setting.connectionsTip') }}</p>
</div>
</Tooltip>
</FormItem>
<FormItem :label="$t('setting.taskLimit')"
prop="downConfig.taskLimit">
<InputNumber v-model="form.downConfig.taskLimit"
:min="1"
:max="10"></InputNumber>
</FormItem>
<FormItem :label="$t('setting.taskSpeedLimit')"
prop="downConfig.speedLimit">
<Input v-model="form.downConfig.speedLimit" />
<span style="padding-left:5px">KB/S({{ $t('setting.speedLimitTip') }})</span>
</FormItem>
<FormItem :label="$t('setting.globalSpeedLimit')"
prop="downConfig.totalSpeedLimit">
<Input v-model="form.downConfig.totalSpeedLimit" />
<span style="padding-left:5px">KB/S({{ $t('setting.speedLimitTip') }})</span>
</FormItem>
</div>
</Panel>
<Panel name="app">
{{ $t('setting.appSetting') }}
<div slot="content">
<FormItem :label="$t('setting.language')"
prop="appConfig.locale">
<Select v-model="form.appConfig.locale">
<Option value="zh-CN">中文(简体)</Option>
<Option value="zh-TW">中文(繁體)</Option>
<Option value="en-US">English(USA)</Option>
</Select>
</FormItem>
<FormItem :label="$t('setting.uiMode')"
prop="appConfig.uiMode">
<Select v-model="form.appConfig.uiMode">
<Option v-for="option in setting.uiModes"
:key="option.value"
:value="option.value">{{ option.text }}</Option>
</Select>
</FormItem>
<FormItem :label="$t('setting.checkUpdate')"
prop="appConfig.updateCheckRate">
<Select v-model="form.appConfig.updateCheckRate">
<Option v-for="option in setting.updateChecks"
:key="option.value"
:value="option.value">{{ option.text }}</Option>
</Select>
</FormItem>
<FormItem :label="$t('setting.autoOpen')"
prop="appConfig.autoOpen">
<Switch v-model="form.appConfig.autoOpen"></Switch>
</FormItem>
<FormItem :label="$t('setting.secondProxy.secondProxy')">
<Switch v-model="secondProxyEnable"
@on-change="switchSecondProxy"></Switch>
<Tooltip class="item"
placement="right">
<Icon type="help-circled"
class="action-icon tip-icon" />
<div slot="content">
<p>{{$t('setting.secondProxy.tip')}}</p>
</div>
</Tooltip>
</FormItem>
<div v-if="secondProxyEnable">
<FormItem :label="$t('setting.secondProxy.type')"
prop="appConfig.proxyConfig.proxyType">
<Select v-model="form.appConfig.proxyConfig.proxyType"
style="width:6rem;">
<Option value="HTTP">HTTP</Option>
<Option value="SOCKS4">SOCKS4</Option>
<Option value="SOCKS5">SOCKS5</Option>
</Select>
</FormItem>
<FormItem :label="$t('setting.secondProxy.host')"
prop="appConfig.proxyConfig.host">
<Input v-model="form.appConfig.proxyConfig.host"
class="string-input" />
</FormItem>
<FormItem :label="$t('setting.secondProxy.port')"
prop="appConfig.proxyConfig.port">
<InputNumber v-model="form.appConfig.proxyConfig.port"
:min="1"
:max="65535" />
</FormItem>
<FormItem :label="$t('setting.secondProxy.user')">
<Input v-model="form.appConfig.proxyConfig.user"
class="string-input" />
</FormItem>
<FormItem :label="$t('setting.secondProxy.pwd')">
<Input type="password"
v-model="form.appConfig.proxyConfig.pwd"
class="string-input" />
</FormItem>
</div>
</div>
</Panel>
</Collapse>
</Form>
</template>
<script>
import FileChoose from '../components/FileChoose'
import { getConfig, setConfig } from '../common/native.js'
let debounceTimer
export default {
name: 'setting',
components: {
FileChoose
},
data() {
return {
form: {
downConfig: {},
appConfig: {}
},
secondProxyEnable: false,
rules: {
'downConfig.speedLimit': [
{ required: true, message: this.$t('tip.notNull') },
{ pattern: /^\d+$/, message: this.$t('tip.fmtErr') }
],
'downConfig.totalSpeedLimit': [
{ required: true, message: this.$t('tip.notNull') },
{ pattern: /^\d+$/, message: this.$t('tip.fmtErr') }
]
}
}
},
computed: {
setting() {
return {
uiModes: [
{ value: 0, text: this.$t('setting.uiModeBrowser') },
{ value: 1, text: this.$t('setting.uiModeWindows') }
],
updateChecks: [
{ value: 0, text: this.$t('setting.checkUpdateNever') },
{ value: 1, text: this.$t('setting.checkUpdateWeek') },
{ value: 2, text: this.$t('setting.checkUpdateStartup') }
]
}
}
},
watch: {
form: {
handler(nowVal, oldVal) {
//不是首次加载触发
if (Object.keys(oldVal.downConfig).length !== 0) {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
debounceTimer = null
this.setConfig()
}, 300)
}
},
deep: true
}
},
methods: {
switchSecondProxy(val) {
if (val) {
this.rules['appConfig.proxyConfig.proxyType'] = [{ required: true, message: this.$t('tip.notNull') }]
this.rules['appConfig.proxyConfig.host'] = [{ required: true, message: this.$t('tip.notNull') }]
this.rules['appConfig.proxyConfig.port'] = [{ required: true, message: this.$t('tip.notNull') }]
} else if (this.form.appConfig.proxyConfig) {
this.rules['appConfig.proxyConfig.proxyType'] = null
this.rules['appConfig.proxyConfig.host'] = null
this.rules['appConfig.proxyConfig.port'] = null
this.setConfig()
}
},
async getConfig() {
let downConfig = await this.$noSpinHttp.get('http://127.0.0.1:26339/config')
let appConfig = await getConfig()
this.form = {
downConfig: { ...downConfig.data },
appConfig: { ...appConfig }
}
this.secondProxyEnable = !!this.form.appConfig.proxyConfig
//设置默认值
if (!this.form.appConfig.proxyConfig) {
this.$set(this.form.appConfig, 'proxyConfig', {
proxyType: 'HTTP',
host: null,
port: null,
user: null,
pwd: null
})
}
if (this.form.downConfig.speedLimit > 0) {
this.form.downConfig.speedLimit /= 1024
}
if (this.form.downConfig.totalSpeedLimit > 0) {
this.form.downConfig.totalSpeedLimit /= 1024
}
},
async setConfig() {
this.$refs['form'].validate(async valid => {
if (valid) {
let downConfig = { ...{}, ...this.form.downConfig }
let appConfig = { ...{}, ...this.form.appConfig }
if (downConfig.speedLimit > 0) {
downConfig.speedLimit *= 1024
}
if (downConfig.totalSpeedLimit > 0) {
downConfig.totalSpeedLimit *= 1024
}
if (!this.secondProxyEnable) {
this.$delete(appConfig, 'proxyConfig')
this.$delete(downConfig, 'proxyConfig')
} else {
downConfig.proxyConfig = { ...this.form.appConfig.proxyConfig }
}
await this.$noSpinHttp.put('http://127.0.0.1:26339/config', downConfig)
await setConfig(appConfig)
this.$Message.success(this.$t('tip.saveSucc'))
if (this.$i18n.locale != this.form.appConfig.locale) {
this.$i18n.locale = this.form.appConfig.locale
//强制渲染一遍,避免切换语言后下拉框不渲染的问题
/* const uiMode = this.form.appConfig.uiMode
const updateCheckRate = this.form.appConfig.updateCheckRate
this.form.appConfig.uiMode = null
this.form.appConfig.updateCheckRate = null
setTimeout(() => {
this.form.appConfig.uiMode = uiMode
this.form.appConfig.updateCheckRate = updateCheckRate
}, 0) */
}
}
})
}
},
created() {
this.getConfig()
}
}
</script>
<style scoped>
.setting-form .ivu-input-wrapper {
width: 5rem;
}
.setting-form .ivu-select {
width: 10rem;
}
.setting-form .string-input {
width: 10rem;
}
</style>
================================================
FILE: front/src/views/Support.vue
================================================
<template>
<div>
<Card>
<p slot="title">如果觉得本软件不错的话,可以通过下面的二维码打赏作者,让作者有动力持续更新版本和修复BUG。</p>
<div class="card-container qr-container">
<img class="qr-img" src="pay/alipay.png" />
<b>支付宝</b>
</div>
<div class="card-container qr-container">
<img class="qr-img" src="pay/weipay.png" />
<b>微信</b>
</div>
</Card>
<Card v-if="softList.length>0"
style="margin-top:20px;">
<p slot="title">另外作者在这里推荐些正版软件,有购买意向的话可以点进去看一看,你们每一次点击也是对作者的支持与鼓励。</p>
<Card v-for="(soft,index) in softList"
:key="index"
class="card-container recommend-container">
<a href="javascript:;"
@click="openUrl(soft.url)">
<img class="ad-img" :src="soft.preview" />
<b>{{soft.title}}</b></a>
</Card>
</Card>
</div>
</template>
<script>
import { openUrl } from '../common/native.js'
export default {
data() {
return {
softList: []
}
},
methods: {
openUrl(url) {
openUrl(url)
}
},
created() {
this.$noSpinHttp.get(this.$config.adminServer + 'recommend/soft').then(result => {
this.softList = result.data
})
}
}
</script>
<style scoped>
.card-container {
width: 220px;
text-align: center;
margin: 5px;
}
.qr-container {
display: inline-block;
}
.recommend-container {
display: inline-flex;
height: 300px;
}
.card-container b {
display: inline-block;
padding-top: 5px;
}
.card-container img {
width: 200px;
height: 200px;
}
</style>
================================================
FILE: front/src/views/Tasks.vue
================================================
<template>
<div class="tasks">
<div class="tasks-entry">
<Button type="info"
icon="plus"
class="tasks-button"
@click="resolveVisible=true">{{ $t("tasks.createTask") }}</Button>
<Button type="warning"
icon="ios-pause"
class="tasks-button"
@click="onPauseBatch">{{ $t("tasks.pauseDownloads") }}</Button>
<Button type="warning"
icon="ios-play"
class="tasks-button"
@click="onResumeBatch">{{ $t("tasks.continueDownloading") }}</Button>
<Button type="error"
icon="ios-trash"
class="tasks-button"
@click="onDeleteBatch">{{ $t("tasks.deleteTask") }}</Button>
</div>
<Tabs type="card"
:animated="false"
v-model="activeTab"
style="overflow:visible;">
<TabPane :label="$t('tasks.running')+'('+runList.length+')'"
name="run"
icon="play">
<Table :taskList="runList"
ref="runTable"
:maxHeight="taskListMaxHeight"
@on-delete="onDelete"
@on-pause="onPause"
@on-resume="onResume"
@on-open="onOpen" />
</TabPane>
<TabPane :label="$t('tasks.waiting')+'('+waitList.length+')'"
name="wait"
icon="pause">
<Table :taskList="waitList"
ref="waitTable"
:maxHeight="taskListMaxHeight"
@on-delete="onDelete"
@on-pause="onPause"
@on-resume="onResume"
@on-open="onOpen" />
</TabPane>
<TabPane :label="$t('tasks.done')+'('+doneList.length+')'"
name="done"
icon="checkmark">
<Table :taskList="doneList"
ref="doneTable"
:maxHeight="taskListMaxHeight"
@on-delete="onDelete"
@on-pause="onPause"
@on-resume="onResume"
@on-open="onOpen" />
</TabPane>
</Tabs>
<Modal v-model="deleteModal"
:title="$t('tasks.deleteTask')">
<Checkbox v-model="delFile"></Checkbox>
<span @click="delFile=!delFile">{{ $t('tasks.deleteTaskTip') }}</span>
<div slot="footer">
<Button type="primary"
@click="doDelete(delTaskIds)">{{ $t('tip.ok') }}</Button>
<Button @click="deleteModal=false">{{ $t('tip.cancel') }}</Button>
</div>
</Modal>
<Resolve v-model="resolveVisible" />
<Create :request="createForm.request"
:response="createForm.response"
:config="createForm.config"
:data="createForm.data"
@close="$router.push('/');" />
</div>
</template>
<script>
import Table from '../components/Table'
import Resolve from '../components/Task/Resolve'
import Create from '../components/Task/Create'
import { showFile } from '../common/native'
import ReconnectingWebSocket from 'reconnecting-websocket'
let ws
export default {
name: 'tasks',
components: {
Table,
Resolve,
Create
},
mounted() {
ws = new ReconnectingWebSocket('ws://' + window.location.hostname + ':26339/ws')
ws.onmessage = evt => {
const msg = eval('(' + evt.data + ')')
const data = msg.data
const updateTask = (taskIds, fromListArray, toList, handle) => {
if (taskIds && taskIds.length) {
taskIds.forEach(taskId => {
fromListArray.forEach(fromList => {
const index = fromList.findIndex(task => task.id == taskId)
if (index >= 0) {
const task = fromList[index]
let moveFlag = false
if (handle) {
moveFlag = handle(task)
}
//Move to the other task list
if (moveFlag && fromList != toList) {
fromList.splice(index, 1)
toList.splice(0, 0, task)
}
this.refreshMaxHeight()
}
})
})
}
}
switch (msg.type) {
case 'CREATE':
if (data.info.status == 1) {
this.runList.push(data)
this.activeTab = 'run'
} else {
this.waitList.push(data)
this.activeTab = 'wait'
}
this.refreshMaxHeight()
break
case 'PROGRESS':
updateTask([data.id], [this.runList], this.doneList, task => {
//Update the task progress info
task.info = data.info
return data.info.status == 4
})
break
case 'PAUSE':
updateTask(data, [this.runList, this.waitList], this.waitList, task => {
//Update the task status to pause
task.info.status = 2
return true
})
this.activeTab = 'wait'
break
case 'ERROR':
updateTask([data.id], [this.runList], this.waitList, task => {
//Update the task status to error
task.info.status = 3
return true
})
this.activeTab = 'wait'
break
case 'RESUME':
updateTask(data.pauseIds, [this.runList, this.waitList], this.waitList, task => {
//Update the task status to pause
task.info.status = 2
return true
})
updateTask(data.waitIds, [this.runList, this.waitList], this.waitList, task => {
//Update the task status to wait
task.info.status = 0
return true
})
updateTask(data.resumeIds, [this.waitList], this.runList, task => {
//Update the task status to downloading
task.info.status = 1
return true
})
this.activeTab = 'run'
break
case 'DELETE': {
let list = this[this.activeTab + 'List']
if (list && data) {
data.forEach(taskId => {
const index = list.findIndex(task => task.id == taskId)
if (index != -1) {
list.splice(index, 1)
}
})
}
break
}
}
}
this.retryLoadTaskList()
},
destroyed() {
ws.close()
},
data() {
return {
activeTab: 'run',
taskListMaxHeight: undefined,
runList: [],
waitList: [],
doneList: [],
deleteModal: false,
delTaskIds: [],
delFile: false,
resolveVisible: false,
createForm: {
request: null,
response: null,
config: null,
data: null
}
}
},
watch: {
$route() {
this.onRouteChange(this.$route.query)
}
},
methods: {
showResolve() {
this.resolveVisible = true
},
onRouteChange(query) {
const flag = !!(query.request && query.response)
this.createForm = {
request: flag ? JSON.parse(query.request) : null,
response: flag ? JSON.parse(query.response) : null,
config: flag && query.config ? JSON.parse(query.config) : null,
data: flag && query.data ? JSON.parse(query.data) : null
}
},
onPause(task) {
this.doPause([task.id])
},
onResume(task) {
this.doResume([task.id])
},
onDelete(task) {
this.delTaskIds = [task.id]
this.delFile = false
this.deleteModal = true
},
onOpen(task) {
showFile(`${task.config.filePath}/${task.response.fileName}`)
},
onPauseBatch() {
const ids = this.getCheckedIds()
if (ids.length) {
this.doPause(ids)
}
},
onResumeBatch() {
const ids = this.getCheckedIds()
if (ids.length) {
this.doResume(ids)
}
},
onDeleteBatch() {
const ids = this.getCheckedIds()
if (ids.length) {
this.delTaskIds = ids
this.delFile = false
this.deleteModal = true
}
},
doPause(ids) {
this.$http.put('http://127.0.0.1:26339/tasks/pause', ids)
},
doResume(ids) {
this.$http.put('http://127.0.0.1:26339/tasks/resume', ids)
},
doDelete(ids) {
this.$http
.post(`http://127.0.0.1:26339/tasks/delete?delFile=${this.delFile}`, ids)
.finally(() => (this.deleteModal = false))
},
getCheckedIds() {
return this.$refs[this.activeTab + 'Table'].getCheckedTasks().map(task => task.id)
},
getIndexByTaskId(taskId) {
return this.taskList.findIndex(t => t.id === taskId)
},
getTop(e) {
let offset = e.offsetTop
if (e.offsetParent) {
offset += this.getTop(e.offsetParent)
}
return offset
},
retryLoadTaskList() {
this.$noSpinHttp
.get('http://127.0.0.1:26339/tasks')
.then(result => {
result.data.forEach(task => {
if (task.info.status == 1) {
//Downloading tasks
this.runList.push(task)
} else if (task.info.status == 4) {
//Completed task
this.doneList.push(task)
} else {
this.waitList.push(task)
}
})
this.refreshMaxHeight()
})
.catch(error => {
if (!error.response) {
setTimeout(this.retryLoadTaskList, 3000)
}
})
},
refreshMaxHeight() {
const taskListTop = this.getTop(this.$refs[this.activeTab + 'Table'].$el.querySelector('div.tb-body'))
const windowHeight = document.documentElement.clientHeight || document.body.clientHeight
this.taskListMaxHeight = windowHeight - taskListTop - 25
}
},
created() {
window.onresize = () => {
this.refreshMaxHeight()
}
this.onRouteChange(this.$route.query)
}
}
</script>
<style lang="less" scoped>
.tasks-entry {
margin-bottom: 20px;
.tasks-button {
margin-right: 10px;
&:last-of-type {
margin-right: 0;
}
}
}
.ivu-table {
td {
background-color: inherit;
}
}
</style>
<style lang="less">
.tasks {
.ivu-table {
.taskList {
// background-color: yellow;
}
td {
background-color: inherit;
}
}
}
</style>
================================================
FILE: front/vue.config.js
================================================
module.exports = {
productionSourceMap: true, // Production environment does not generate source-map
css: {
sourceMap: false // CSS does not generate source-map
},
outputDir: '../main/src/main/resources/http',
devServer: {
proxy: {
'/native': {
target: 'http://127.0.0.1:7478',
changeOrigin: true
},
'/pac': {
target: 'http://127.0.0.1:7478',
changeOrigin: true
},
'/ws': {
target: 'http://127.0.0.1:7478',
ws: true,
}
}
},
chainWebpack: config => {
config.module
.rule('vue')
.test(/\.vue$/)
.use('iview-loader')
.loader('iview-loader')
.options({
prefix: true
})
}
}
================================================
FILE: main/.gitignore
================================================
target/
!.mvn/wrapper/maven-wrapper.jar
src/main/resources/http
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
================================================
FILE: main/pom.xml
================================================
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.pdown.gui</groupId>
<artifactId>proxyee-down</artifactId>
<version>3.0</version>
</parent>
<groupId>org.pdown.gui</groupId>
<artifactId>main</artifactId>
<packaging>jar</packaging>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<profiles>
<profile>
<id>dev</id>
<properties>
<environment>dev</environment>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prd</id>
<properties>
<environment>prd</environment>
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.pdown</groupId>
<artifactId>rest</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.monkeywie</groupId>
<artifactId>proxyee</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<excludes>
<exclude>application.yml</exclude>
<exclude>application-dev.yml</exclude>
<exclude>application-prd.yml</exclude>
<exclude>logback-dev.xml</exclude>
<exclude>logback-prd.xml</exclude>
</excludes>
</resource>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>logback-${environment}.xml</include>
<include>application-${environment}.yml</include>
<include>application.yml</include>
</includes>
</resource>
</resources>
<finalName>proxyee-down-main</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>bin</nonFilteredFileExtension>
<nonFilteredFileExtension>css</nonFilteredFileExtension>
<nonFilteredFileExtension>js</nonFilteredFileExtension>
<nonFilteredFileExtension>eot</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>ico</nonFilteredFileExtension>
<nonFilteredFileExtension>html</nonFilteredFileExtension>
<nonFilteredFileExtension>svg</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.2.RELEASE</version>
<configuration>
<mainClass>org.pdown.gui.DownApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: main/src/main/java/org/pdown/gui/DownApplication.java
================================================
package org.pdown.gui;
import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.swing.JOptionPane;
import org.pdown.core.util.OsUtil;
import org.pdown.gui.com.Browser;
import org.pdown.gui.com.Components;
import org.pdown.gui.content.PDownConfigContent;
import org.pdown.gui.extension.ExtensionContent;
import org.pdown.gui.extension.mitm.util.ExtensionProxyUtil;
import org.pdown.gui.http.EmbedHttpServer;
import org.pdown.gui.http.controller.ApiController;
import org.pdown.gui.http.controller.NativeController;
import org.pdown.gui.http.controller.PacController;
import org.pdown.gui.rest.HttpDownAppCallback;
import org.pdown.gui.util.AppUtil;
import org.pdown.gui.util.ConfigUtil;
import org.pdown.gui.util.ExecUtil;
import org.pdown.gui.util.I18nUtil;
import org.pdown.rest.DownRestServer;
import org.pdown.rest.content.ConfigContent;
import org.pdown.rest.content.RestWebServerFactoryCustomizer;
import org.pdown.rest.controller.HttpDownRestCallback;
import org.pdown.rest.entity.ServerConfigInfo;
import org.pdown.rest.util.PathUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.util.StringUtils;
public class DownApplication extends Application {
private static final Logger LOGGER = LoggerFactory.getLogger(DownApplication.class);
private static final String OS = OsUtil.isWindows() ? "windows"
: (OsUtil.isMac() ? "mac" : "linux");
private static final String ICON_PATH = OS + (OsUtil.isWindowsXP() ? "/logo_xp.png" : "/logo.png");
public static DownApplication INSTANCE;
private Stage stage;
private Browser browser;
private TrayIcon trayIcon;
private CountDownLatch countDownLatch;
//前端页面http服务器端口
public int FRONT_PORT;
//native api服务器端口
public int API_PORT;
//代理服务器端口
public int PROXY_PORT;
@Override
public void start(Stage primaryStage) throws Exception {
INSTANCE = this;
stage = primaryStage;
Platform.setImplicitExit(false);
//load config
initConfig();
//load pdown-rest
initRest();
initMacMITMTool();
initEmbedHttpServer();
initExtension();
initTray();
//xp不支持webview
if (!OsUtil.isWindowsXP()) {
initWindow();
initBrowser();
}
loadUri(null, true, true);
}
private void initConfig() throws IOException {
PDownConfigContent.getInstance().load();
//取前端http server端口
FRONT_PORT = ConfigUtil.getInt("front.port");
//取native api http server端口
API_PORT = ConfigUtil.getInt("api.port");
if ("prd".equals(ConfigUtil.getString("spring.profiles.active"))) {
try {
//端口被时占用随机分配一个端口
API_PORT = OsUtil.getFreePort(API_PORT);
if (FRONT_PORT == -1) {
FRONT_PORT = API_PORT;
}
} catch (IOException e) {
LOGGER.error("initConfig error", e);
alertAndExit(I18nUtil.getMessage("gui.alert.startError", e.getMessage()));
}
}
}
private void initRest() {
//init rest server config
HttpDownRestCallback.setCallback(new HttpDownAppCallback());
RestWebServerFactoryCustomizer.init(null);
ServerConfigInfo serverConfigInfo = ConfigContent.getInstance().get();
serverConfigInfo.setPort(REST_PORT);
if (StringUtils.isEmpty(serverConfigInfo.getFilePath())) {
serverConfigInfo.setFilePath(System.getProperty("user.home") + File.separator + "Downloads");
}
new SpringApplicationBuilder(DownRestServer.class).headless(false).build().run();
}
//读取扩展信息和启动代理服务器
private void initExtension() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//退出时把系统代理还原
if (PDownConfigContent.getInstance().get().getProxyMode() == 1) {
try {
ExtensionProxyUtil.disabledProxy();
} catch (IOException e) {
}
}
}));
new Thread(() -> {
//检查是否安装了证书
try {
if (AppUtil.checkIsInstalledCert()) {
AppUtil.startProxyServer();
}
} catch (Exception e) {
LOGGER.error("Init extension error", e);
}
}).start();
//根据扩展生成pac文件并切换系统代理
try {
ExtensionContent.load();
AppUtil.refreshPAC();
} catch (IOException e) {
LOGGER.error("Extension content load error", e);
}
}
public static int macToolPort;
//加载mac tool
private void initMacMITMTool() {
if (OsUtil.isMac()) {
new Thread(() -> {
String toolUri = "mac/mitm-tool.bin";
Path toolPath = Paths.get(PathUtil.ROOT_PATH + File.separator + toolUri);
try {
if (!toolPath.toFile().exists()) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL url = classLoader.getResource(toolUri);
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
if (!toolPath.getParent().toFile().exists()) {
Files.createDirectories(toolPath.getParent());
}
Files.copy(classLoader.getResourceAsStream(toolUri), toolPath);
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxrw-rw-");
Files.setPosixFilePermissions(toolPath, perms);
}
}
//取一个空闲端口来运行mac tool
macToolPort = OsUtil.getFreePort();
//程序退出监听
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
ExecUtil.httpGet("http://127.0.0.1:" + macToolPort + "/quit");
} catch (IOException e) {
}
}));
ExecUtil.execBlockWithAdmin("'" + toolPath.toFile().getPath() + "' " + macToolPort);
} catch (Exception e) {
LOGGER.error("initMacMITMTool error", e);
alertAndExit("Init mitm-tool error:" + e.getMessage());
}
System.exit(0);
}).start();
}
}
private void initEmbedHttpServer() {
countDownLatch = new CountDownLatch(1);
new Thread(() -> {
EmbedHttpServer embedHttpServer = new EmbedHttpServer(API_PORT);
embedHttpServer.addController(new NativeController());
embedHttpServer.addController(new ApiController());
embedHttpServer.addController(new PacController());
embedHttpServer.start(future -> countDownLatch.countDown());
}).start();
}
//加载托盘
private void initTray() throws AWTException {
if (SystemTray.isSupported()) {
// 获得系统托盘对象
SystemTray systemTray = SystemTray.getSystemTray();
// 获取图片所在的URL
URL url = Thread.currentThread().getContextClassLoader().getResource(ICON_PATH);
// 为系统托盘加托盘图标
Image trayImage = Toolkit.getDefaultToolkit().getImage(url);
Dimension trayIconSize = systemTray.getTrayIconSize();
trayImage = trayImage.getScaledInstance(trayIconSize.width, trayIconSize.height, Image.SCALE_SMOOTH);
trayIcon = new TrayIcon(trayImage, "Proxyee Down");
systemTray.add(trayIcon);
loadPopupMenu();
//双击事件监听
trayIcon.addActionListener(event -> Platform.runLater(() -> loadUri(null, true)));
}
}
public void loadPopupMenu() {
//添加右键菜单
PopupMenu popupMenu = new PopupMenu();
MenuItem showItem = new MenuItem(I18nUtil.getMessage("gui.tray.show"));
showItem.addActionListener(event -> Platform.runLater(() -> loadUri("", true)));
MenuItem setItem = new MenuItem(I18nUtil.getMessage("gui.tray.set"));
setItem.addActionListener(event -> loadUri("/#/setting", true));
MenuItem aboutItem = new MenuItem(I18nUtil.getMessage("gui.tray.about"));
aboutItem.addActionListener(event -> loadUri("/#/about", true));
MenuItem supportItem = new MenuItem(I18nUtil.getMessage("gui.tray.support"));
supportItem.addActionListener(event -> loadUri("/#/support", true));
MenuItem closeItem = new MenuItem(I18nUtil.getMessage("gui.tray.exit"));
closeItem.addActionListener(event -> {
Platform.exit();
System.exit(0);
});
popupMenu.add(showItem);
popupMenu.addSeparator();
popupMenu.add(setItem);
popupMenu.add(aboutItem);
popupMenu.add(supportItem);
popupMenu.addSeparator();
popupMenu.add(closeItem);
trayIcon.setPopupMenu(popupMenu);
}
public void refreshBrowserMenu() {
browser.refreshText();
}
//加载webView
private void initBrowser() throws AWTException {
browser = new Browser();
stage.setScene(new Scene(browser));
try {
countDownLatch.await();
} catch (InterruptedException e) {
}
}
//加载gui窗口
private void initWindow() {
stage.setTitle("Proxyee Down");
Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
int width = 1024;
int height = 576;
stage.setX((bounds.getWidth() - width) / 2);
stage.setY((bounds.getHeight() - height) / 2);
stage.setMinWidth(width);
stage.setMinHeight(height);
stage.getIcons().add(new javafx.scene.image.Image(Thread.currentThread().getContextClassLoader().getResourceAsStream(ICON_PATH)));
stage.setResizable(true);
//关闭窗口监听
stage.setOnCloseRequest(event -> {
event.consume();
stage.hide();
});
}
/**
* 显示gui窗口
*
* @param isTray 是否从托盘按钮打开的(windows下如果非托盘按钮调用窗口可能不会置顶)
*/
public void show(boolean isTray) {
//是否需要调用窗口置顶
boolean isFront = false;
if (stage.isShowing()) {
if (stage.isIconified()) {
stage.setIconified(false);
} else {
isFront = true;
stage.toFront();
}
} else {
isFront = true;
stage.show();
stage.toFront();
}
//避免有时候窗口不弹出
if (isFront && !isTray && OsUtil.isWindows()) {
stage.setIconified(true);
stage.setIconified(false);
}
}
public void loadUri(String uri, boolean isTray, boolean isStartup) {
String url = "http://127.0.0.1:" + FRONT_PORT + (uri == null ? "" : uri);
boolean autoOpen = PDownConfigContent.getInstance().get().isAutoOpen();
if (OsUtil.isWindowsXP() || PDownConfigContent.getInstance().get().getUiMode() == 0) {
if (!isStartup || autoOpen) {
try {
Desktop.getDesktop().browse(URI.create(url));
} catch (IOException e) {
LOGGER.error("Open browse error", e);
}
}
} else {
Platform.runLater(() -> {
if (uri != null || !browser.isLoad()) {
browser.load(url);
}
if (!isStartup || autoOpen) {
show(isTray);
}
});
}
}
public void loadUri(String uri, boolean isTray) {
loadUri(uri, isTray, false);
}
//提示并退出程序
private void alertAndExit(String msg) {
Platform.runLater(() -> {
Components.alert(msg);
System.exit(0);
});
}
static {
//设置日志存放路径
System.setProperty("ROOT_PATH", PathUtil.ROOT_PATH);
//webView允许跨域访问
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
//处理MAC dock图标
if (OsUtil.isMac()) {
try {
Class<?> appClass = Class.forName("com.apple.eawt.Application");
Method getApplication = appClass.getMethod("getApplication");
Object application = getApplication.invoke(appClass);
Method setDockIconImage = appClass.getMethod("setDockIconImage", Image.class);
URL url = Thread.currentThread().getContextClassLoader().getResource("mac/dock_logo.png");
Image image = Toolkit.getDefaultToolkit().getImage(url);
setDockIconImage.invoke(application, image);
} catch (Exception e) {
LOGGER.error("handle mac dock icon error", e);
}
}
}
private static final int REST_PORT = 26339;
private static void doCheck() {
if (OsUtil.isBusyPort(REST_PORT)) {
JOptionPane.showMessageDialog(
null,
I18nUtil.getMessage("gui.alert.startError", I18nUtil.getMessage("gui.alert.restPortBusy")),
I18nUtil.getMessage("gui.warning"),
JOptionPane.WARNING_MESSAGE);
System.exit(0);
}
}
//-Dio.netty.leakDetection.level=PARANOID
//https://stackoverflow.com/questions/39192528/how-can-you-send-information-to-the-windows-task-bar-from-java-o-javafx
public static void main(String[] args) {
//get free port
doCheck();
launch(args);
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/com/Browser.java
================================================
package org.pdown.gui.com;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.pdown.gui.util.I18nUtil;
public class Browser extends Region {
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
private javafx.scene.control.MenuItem copy;
private javafx.scene.control.MenuItem paste;
public Browser() {
getChildren().add(webView);
webView.setContextMenuEnabled(false);
//自定义webview右键菜单
final Clipboard clipboard = Clipboard.getSystemClipboard();
ContextMenu contextMenu = new ContextMenu();
copy = new javafx.scene.control.MenuItem();
copy.setOnAction(e -> {
ClipboardContent content = new ClipboardContent();
Object selection = webView.getEngine().executeScript("window.getSelection().toString()");
if (selection != null) {
content.putString(selection.toString());
clipboard.setContent(content);
}
});
paste = new javafx.scene.control.MenuItem();
paste.setOnAction(e -> {
Object content = clipboard.getContent(DataFormat.PLAIN_TEXT);
if (content != null) {
webView.getEngine().executeScript("if(document.activeElement.selectionStart>=0){"
+ "var value = document.activeElement.value;"
+ "if(!value){"
+ " document.activeElement.value='" + content + "';"
+ "}else{"
+ " document.activeElement.value=value.substring(0,document.activeElement.selectionStart)+'" + content + "'+value.substring(document.activeElement.selectionEnd);"
+ "}"
+ "var event = document.createEvent('Event');"
+ "event.initEvent('input', true, true);"
+ "document.activeElement.dispatchEvent(event);"
+ "}");
}
});
refreshText();
contextMenu.getItems().addAll(copy, paste);
webView.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
contextMenu.show(webView, e.getScreenX(), e.getScreenY());
} else {
contextMenu.hide();
}
});
}
@Override
protected void layoutChildren() {
double w = getWidth();
double h = getHeight();
layoutInArea(webView, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
}
public void load(String url) {
webEngine.load(url);
}
public boolean isLoad() {
return webEngine.getLocation() != null;
}
public void refreshText() {
copy.setText(I18nUtil.getMessage("gui.menu.copy"));
paste.setText(I18nUtil.getMessage("gui.menu.paste"));
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/com/CheckboxMenuItemGroup.java
================================================
package org.pdown.gui.com;
import java.awt.CheckboxMenuItem;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashSet;
import java.util.Set;
public class CheckboxMenuItemGroup implements ItemListener {
private Set<CheckboxMenuItem> items = new HashSet<>();
private ItemListener itemListener;
public void add(CheckboxMenuItem cbmi) {
cbmi.addItemListener(this);
cbmi.setState(false);
items.add(cbmi);
}
public void addActionListener(ItemListener itemListener) {
this.itemListener = itemListener;
}
@Override
public void itemStateChanged(ItemEvent e) {
CheckboxMenuItem checkedItem = ((CheckboxMenuItem) e.getSource());
if (e.getStateChange() == ItemEvent.SELECTED) {
String selectedItemName = checkedItem.getName();
for (CheckboxMenuItem item : items) {
if (!item.getName().equals(selectedItemName)) {
item.setState(false);
}
}
if (itemListener != null) {
itemListener.itemStateChanged(e);
}
} else {
checkedItem.setState(true);
}
}
public void selectItem(CheckboxMenuItem itemToSelect) {
for (CheckboxMenuItem item : items) {
item.setState(item == itemToSelect);
}
}
public CheckboxMenuItem getSelectedItem() {
for (CheckboxMenuItem item : items) {
if (item.getState()) {
return item;
}
}
return null;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/com/Components.java
================================================
package org.pdown.gui.com;
import java.io.File;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.ButtonType;
import javafx.scene.control.DialogPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Components {
/**
* 弹出提示窗,窗口置顶
*/
public static void alert(String msg) {
Alert alert = new Alert(AlertType.INFORMATION);
// alert.setTitle("提示");
alert.setHeaderText(null);
alert.setContentText(msg);
DialogPane root = alert.getDialogPane();
Stage dialogStage = new Stage();
for (ButtonType buttonType : root.getButtonTypes()) {
ButtonBase button = (ButtonBase) root.lookupButton(buttonType);
button.setOnAction(evt -> dialogStage.close());
}
root.getScene().setRoot(new Group());
root.setPadding(new Insets(10, 0, 10, 0));
Scene scene = new Scene(root);
dialogStage.setScene(scene);
dialogStage.initModality(Modality.APPLICATION_MODAL);
dialogStage.setAlwaysOnTop(true);
dialogStage.setResizable(false);
dialogStage.showAndWait();
}
/**
* 弹出文件选择框
*/
public static File fileChooser() {
Stage stage = buildBackgroundTopStage();
FileChooser chooser = new FileChooser();
chooser.setTitle("选择文件");
File file = chooser.showOpenDialog(stage);
stage.close();
return file;
}
/**
* 弹出文件夹选择框
*/
public static File dirChooser() {
Stage stage = buildBackgroundTopStage();
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("选择文件夹");
File file = chooser.showDialog(stage);
stage.close();
return file;
}
private static Stage buildBackgroundTopStage() {
Stage stage = new Stage();
stage.setAlwaysOnTop(true);
stage.setWidth(1);
stage.setHeight(1);
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
return stage;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/content/PDownConfigContent.java
================================================
package org.pdown.gui.content;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.pdown.gui.entity.PDownConfigInfo;
import org.pdown.rest.base.content.PersistenceContent;
import org.pdown.rest.util.PathUtil;
public class PDownConfigContent extends PersistenceContent<PDownConfigInfo, PDownConfigContent> {
private static final PDownConfigContent INSTANCE = new PDownConfigContent();
public static PDownConfigContent getInstance() {
return INSTANCE;
}
@Override
protected TypeReference type() {
return new TypeReference<PDownConfigInfo>() {
};
}
@Override
protected String savePath() {
return PathUtil.ROOT_PATH + File.separator + "pdown.cfg";
}
@Override
protected PDownConfigInfo defaultValue() {
PDownConfigInfo pDownConfigInfo = new PDownConfigInfo();
//取系统默认语言
Locale defaultLocale = Locale.getDefault();
pDownConfigInfo.setLocale(defaultLocale.getLanguage() + "-" + defaultLocale.getCountry());
//插件文件服务器
List<String> extFileServers = new ArrayList<>();
extFileServers.add("https://github.com/proxyee-down-org/proxyee-down-extension/raw/master");
extFileServers.add("http://static.pdown.org/extensions");
pDownConfigInfo.setExtFileServers(extFileServers);
return pDownConfigInfo;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/entity/PDownConfigInfo.java
================================================
package org.pdown.gui.entity;
import java.io.Serializable;
import java.util.List;
import org.pdown.core.proxy.ProxyConfig;
public class PDownConfigInfo implements Serializable {
private static final long serialVersionUID = 250452934883002540L;
//客户端语言
private String locale;
//UI模式 0.浏览器模式 1.GUI模式
private int uiMode = 1;
//代理模式 0.不接管系统代理 1.由pdown接管系统代理
private int proxyMode;
//插件文件服务器(用于下载插件相关文件)
private List<String> extFileServers;
//检测更新频率 0.从不 1.一周检查一次 2.每次打开检查
private int updateCheckRate = 2;
//启动时是否自动打开窗口
private boolean autoOpen = true;
//最后一次检查更新时间
private long lastUpdateCheck;
//前置代理
private ProxyConfig proxyConfig;
public String getLocale() {
return locale;
}
public PDownConfigInfo setLocale(String locale) {
this.locale = locale;
return this;
}
public int getUiMode() {
return uiMode;
}
public PDownConfigInfo setUiMode(int uiMode) {
this.uiMode = uiMode;
return this;
}
public int getProxyMode() {
return proxyMode;
}
public PDownConfigInfo setProxyMode(int proxyMode) {
this.proxyMode = proxyMode;
return this;
}
public List<String> getExtFileServers() {
return extFileServers;
}
public PDownConfigInfo setExtFileServers(List<String> extFileServers) {
this.extFileServers = extFileServers;
return this;
}
public int getUpdateCheckRate() {
return updateCheckRate;
}
public PDownConfigInfo setUpdateCheckRate(int updateCheckRate) {
this.updateCheckRate = updateCheckRate;
return this;
}
public long getLastUpdateCheck() {
return lastUpdateCheck;
}
public PDownConfigInfo setLastUpdateCheck(long lastUpdateCheck) {
this.lastUpdateCheck = lastUpdateCheck;
return this;
}
public ProxyConfig getProxyConfig() {
return proxyConfig;
}
public PDownConfigInfo setProxyConfig(ProxyConfig proxyConfig) {
this.proxyConfig = proxyConfig;
return this;
}
public boolean isAutoOpen() {
return autoOpen;
}
public PDownConfigInfo setAutoOpen(boolean autoOpen) {
this.autoOpen = autoOpen;
return this;
}
public static com.github.monkeywie.proxyee.proxy.ProxyConfig convert(ProxyConfig proxyConfig) {
if (proxyConfig == null) {
return null;
}
return new com.github.monkeywie.proxyee.proxy.ProxyConfig(
com.github.monkeywie.proxyee.proxy.ProxyType.valueOf(proxyConfig.getProxyType().name()),
proxyConfig.getHost(),
proxyConfig.getPort(),
proxyConfig.getUser(),
proxyConfig.getPwd());
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/ContentScript.java
================================================
package org.pdown.gui.extension;
import java.util.Arrays;
public class ContentScript {
private String[] matches;
private String[] scripts;
public String[] getMatches() {
return matches;
}
public ContentScript setMatches(String[] matches) {
this.matches = matches;
return this;
}
public String[] getScripts() {
return scripts;
}
public ContentScript setScripts(String[] scripts) {
this.scripts = scripts;
return this;
}
public boolean isMatch(String url) {
if (matches != null
&& Arrays.stream(matches).anyMatch(m -> url.matches(m))) {
return true;
}
return false;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionConfig.java
================================================
package org.pdown.gui.extension;
import java.util.List;
public class ExtensionConfig {
//本地加载的扩展
private List<String> localExtensions;
public List<String> getLocalExtensions() {
return localExtensions;
}
public void setLocalExtensions(List<String> localExtensions) {
this.localExtensions = localExtensions;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionContent.java
================================================
package org.pdown.gui.extension;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.pdown.core.util.FileUtil;
import org.pdown.rest.util.ContentUtil;
import org.pdown.rest.util.PathUtil;
public class ExtensionContent {
public static final String EXT_DIR = PathUtil.ROOT_PATH + File.separator + "extensions";
public static final String EXT_DIR_CONFIG = EXT_DIR + File.separator + "ext.cfg";
private static final String EXT_MANIFEST = "manifest.json";
private static List<ExtensionInfo> EXTENSION_INFO_LIST;
//代理服务器域名通配符列表
private static Set<String> PROXY_WILDCARDS;
//需要嗅探下载的url正则表达式列表
private static Set<String> SNIFF_REGEXS;
//配置
private static ExtensionConfig CONFIG;
public static void load() throws IOException {
File file = new File(EXT_DIR);
if (EXTENSION_INFO_LIST == null) {
EXTENSION_INFO_LIST = new ArrayList<>();
} else {
EXTENSION_INFO_LIST.clear();
}
if (file.exists() && file.isDirectory()) {
//加载所有已安装的扩展
for (File extendDir : file.listFiles()) {
if (extendDir.isDirectory()) {
//读取manifest.json
ExtensionInfo extensionInfo = parseExtensionDir(extendDir);
if (extensionInfo != null) {
EXTENSION_INFO_LIST.add(extensionInfo);
}
}
}
}
//加载本地安装的扩展
try {
CONFIG = ContentUtil.get(EXT_DIR_CONFIG, ExtensionConfig.class);
} catch (Exception e) {
}
if (CONFIG == null) {
CONFIG = new ExtensionConfig();
} else if (CONFIG.getLocalExtensions() != null) {
for (String localExtendDir : CONFIG.getLocalExtensions()) {
File extendDir = new File(localExtendDir);
if (extendDir.isDirectory()) {
//读取manifest.json
ExtensionInfo extensionInfo = parseExtensionDir(extendDir, true);
if (extensionInfo != null) {
EXTENSION_INFO_LIST.add(extensionInfo);
}
}
}
}
refresh();
}
public static ExtensionConfig getConfig() {
return CONFIG;
}
public synchronized static void saveConfig() throws IOException {
ContentUtil.save(CONFIG, EXT_DIR_CONFIG);
}
public synchronized static ExtensionInfo refresh(String path, boolean isLocal) throws IOException {
ExtensionInfo loadExt = parseExtensionDir(new File((isLocal ? "" : EXT_DIR) + path), isLocal);
if (loadExt != null && EXTENSION_INFO_LIST != null && path != null) {
boolean match = false;
for (int i = 0; i < EXTENSION_INFO_LIST.size(); i++) {
ExtensionInfo extensionInfo = EXTENSION_INFO_LIST.get(i);
if (loadExt.getMeta().getPath().equals(extensionInfo.getMeta().getPath())
&& loadExt.getMeta().isLocal() == extensionInfo.getMeta().isLocal()) {
match = true;
EXTENSION_INFO_LIST.set(i, loadExt);
break;
}
}
if (!match) {
EXTENSION_INFO_LIST.add(loadExt);
if (isLocal) {
//保存文件
if (CONFIG.getLocalExtensions() == null) {
CONFIG.setLocalExtensions(new ArrayList<>());
}
CONFIG.getLocalExtensions().add(path);
ExtensionContent.saveConfig();
}
}
refresh();
}
return loadExt;
}
public synchronized static ExtensionInfo refresh(String path) throws IOException {
return refresh(path, false);
}
public synchronized static void remove(String path, boolean isLocal) throws IOException {
if (EXTENSION_INFO_LIST != null && path != null) {
for (int i = 0; i < EXTENSION_INFO_LIST.size(); i++) {
ExtensionInfo extensionInfo = EXTENSION_INFO_LIST.get(i);
if (path.equals(extensionInfo.getMeta().getPath())
&& extensionInfo.getMeta().isLocal() == isLocal) {
EXTENSION_INFO_LIST.remove(i);
if (!extensionInfo.getMeta().isLocal()) {
FileUtil.deleteIfExists(extensionInfo.getMeta().getFullPath());
} else {
//保存文件
if (CONFIG.getLocalExtensions() != null) {
CONFIG.getLocalExtensions().remove(extensionInfo.getMeta().getFullPath());
ExtensionContent.saveConfig();
}
}
break;
}
}
refresh();
}
}
public synchronized static void refresh() {
if (PROXY_WILDCARDS == null) {
PROXY_WILDCARDS = new HashSet<>();
} else {
PROXY_WILDCARDS.clear();
}
if (SNIFF_REGEXS == null) {
SNIFF_REGEXS = new HashSet<>();
} else {
SNIFF_REGEXS.clear();
}
if (EXTENSION_INFO_LIST != null) {
for (ExtensionInfo extensionInfo : EXTENSION_INFO_LIST) {
if (extensionInfo.getMeta().isEnabled()) {
//读取需要代理的域名匹配符
if (extensionInfo.getProxyWildcards() != null) {
for (String wildcard : extensionInfo.getProxyWildcards()) {
PROXY_WILDCARDS.add(wildcard.trim());
}
}
//读取需要嗅探下载的url正则表达式
if (extensionInfo.getSniffRegexs() != null) {
for (String regex : extensionInfo.getSniffRegexs()) {
SNIFF_REGEXS.add(regex.trim());
}
}
}
}
}
}
private static ExtensionInfo parseExtensionDir(File extendDir, boolean isLocal) {
ExtensionInfo extensionInfo = null;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
extensionInfo = objectMapper.readValue(new FileInputStream(extendDir + File.separator + EXT_MANIFEST), ExtensionInfo.class);
} catch (IOException e) {
}
if (extensionInfo != null) {
Meta meta = Meta.load(extendDir.getPath());
meta.setLocal(isLocal);
//如果没有设置则生成默认设置信息
if (extensionInfo.getSettings() != null
&& extensionInfo.getSettings().size() > 0) {
if (meta.getSettings() == null) {
meta.setSettings(new HashMap<>());
}
if (meta.getSettings().size() == 0) {
for (Setting setting : extensionInfo.getSettings()) {
meta.getSettings().put(setting.getName(), setting.getValue());
}
}
}
extensionInfo.setMeta(meta);
}
return extensionInfo;
}
private static ExtensionInfo parseExtensionDir(File extendDir) {
return parseExtensionDir(extendDir, false);
}
public static List<ExtensionInfo> get() {
return EXTENSION_INFO_LIST;
}
public static Set<String> getProxyWildCards() {
return PROXY_WILDCARDS;
}
public static Set<String> getSniffRegexs() {
return SNIFF_REGEXS;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionInfo.java
================================================
package org.pdown.gui.extension;
import java.util.List;
public class ExtensionInfo {
private String title; //扩展名称
private double version; //扩展版本号
private String homepage; //扩展主页
private String description; //扩展描述
private List<String> proxyWildcards; //扩展生效配置的域名通配符列表
private List<String> sniffRegexs; //扩展嗅探下载的url正则表达式列表
private List<ContentScript> contentScripts;
private HookScript hookScript; //下载状态变更时触发的钩子函数脚本
private List<Setting> settings; //扩展设置选项
private Meta meta;
public String getTitle() {
return title;
}
public ExtensionInfo setTitle(String title) {
this.title = title;
return this;
}
public double getVersion() {
return version;
}
public ExtensionInfo setVersion(double version) {
this.version = version;
return this;
}
public String getHomepage() {
return homepage;
}
public ExtensionInfo setHomepage(String homepage) {
this.homepage = homepage;
return this;
}
public String getDescription() {
return description;
}
public ExtensionInfo setDescription(String description) {
this.description = description;
return this;
}
public List<String> getProxyWildcards() {
return proxyWildcards;
}
public ExtensionInfo setProxyWildcards(List<String> proxyWildcards) {
this.proxyWildcards = proxyWildcards;
return this;
}
public List<String> getSniffRegexs() {
return sniffRegexs;
}
public ExtensionInfo setSniffRegexs(List<String> sniffRegexs) {
this.sniffRegexs = sniffRegexs;
return this;
}
public List<ContentScript> getContentScripts() {
return contentScripts;
}
public ExtensionInfo setContentScripts(List<ContentScript> contentScripts) {
this.contentScripts = contentScripts;
return this;
}
public HookScript getHookScript() {
return hookScript;
}
public ExtensionInfo setHookScript(HookScript hookScript) {
this.hookScript = hookScript;
return this;
}
public List<Setting> getSettings() {
return settings;
}
public ExtensionInfo setSettings(List<Setting> settings) {
this.settings = settings;
return this;
}
public Meta getMeta() {
return meta;
}
public ExtensionInfo setMeta(Meta meta) {
this.meta = meta;
return this;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/HookScript.java
================================================
package org.pdown.gui.extension;
import java.util.Arrays;
public class HookScript {
public static final String EVENT_RESOLVE = "resolve";
public static final String EVENT_START = "start";
public static final String EVENT_RESUME = "resume";
public static final String EVENT_PAUSE = "pause";
public static final String EVENT_ERROR = "error";
public static final String EVENT_DONE = "done";
public static final String EVENT_DELETE = "delete";
private Event[] events;
private String script;
public Event[] getEvents() {
return events;
}
public HookScript setEvents(Event[] events) {
this.events = events;
return this;
}
public String getScript() {
return script;
}
public HookScript setScript(String script) {
this.script = script;
return this;
}
/**
* 判断扩展是否有注册钩子函数
*/
public Event hasEvent(String event, String url) {
String matchUrl = url != null ? url.replaceAll("^(?i)(https?://)", "") : "";
if (events != null) {
return Arrays.stream(events)
.filter(e -> event.equalsIgnoreCase(e.getOn()) && (e.getMatches() == null || (Arrays.stream(e.getMatches()).anyMatch(m -> matchUrl.matches(m)))))
.findFirst()
.orElse(null);
}
return null;
}
public static class Event {
private String on;
private String[] matches;
private String method;
public String getOn() {
return on;
}
public Event setOn(String on) {
this.on = on;
return this;
}
public String[] getMatches() {
return matches;
}
public Event setMatches(String[] matches) {
this.matches = matches;
return this;
}
public String getMethod() {
return method;
}
public Event setMethod(String method) {
this.method = method;
return this;
}
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/Meta.java
================================================
package org.pdown.gui.extension;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.pdown.rest.util.ContentUtil;
public class Meta {
public transient static final String CONFIG_FILE = ".ext_data/.config.dat";
private transient String path;
private transient String fullPath;
private boolean enabled = true;
private boolean local = true;
private Map<String, Object> settings;
private Map<String, Object> data;
public String getPath() {
return path;
}
public Meta setPath(String path) {
this.path = path;
return this;
}
public String getFullPath() {
return fullPath;
}
public Meta setFullPath(String fullPath) {
this.fullPath = fullPath;
return this;
}
public boolean isEnabled() {
return enabled;
}
public Meta setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
public Map<String, Object> getSettings() {
return settings;
}
public Meta setSettings(Map<String, Object> settings) {
this.settings = settings;
return this;
}
public Map<String, Object> getData() {
return data;
}
public Meta setData(Map<String, Object> data) {
this.data = data;
return this;
}
public boolean isLocal() {
return local;
}
public void setLocal(boolean local) {
this.local = local;
}
public void save() {
try {
ContentUtil.save(this, getFullPath() + File.separator + CONFIG_FILE, true);
} catch (IOException e) {
}
}
public static Meta load(String path) {
Meta meta = null;
try {
meta = ContentUtil.get(path + File.separator + CONFIG_FILE, Meta.class);
} catch (IOException e) {
}
if (meta == null) {
meta = new Meta();
}
meta.setPath("/" + new File(path).getName());
meta.setFullPath(path);
return meta;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/Setting.java
================================================
package org.pdown.gui.extension;
import java.util.Map;
public class Setting {
private String name;
private String title;
private String type;
private Object value;
private String description;
private boolean isMultiple;
private Map<String, Object> options;
public String getName() {
return name;
}
public Setting setName(String name) {
this.name = name;
return this;
}
public String getTitle() {
return title;
}
public Setting setTitle(String title) {
this.title = title;
return this;
}
public String getType() {
return type;
}
public Setting setType(String type) {
this.type = type;
return this;
}
public Object getValue() {
return value;
}
public Setting setValue(Object value) {
this.value = value;
return this;
}
public String getDescription() {
return description;
}
public Setting setDescription(String description) {
this.description = description;
return this;
}
public boolean isMultiple() {
return isMultiple;
}
public Setting setMultiple(boolean multiple) {
isMultiple = multiple;
return this;
}
public Map<String, Object> getOptions() {
return options;
}
public Setting setOptions(Map<String, Object> options) {
this.options = options;
return this;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/JavascriptEngine.java
================================================
package org.pdown.gui.extension.jsruntime;
import com.fasterxml.jackson.core.JsonProcessingException;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import org.pdown.gui.extension.jsruntime.polyfill.Window;
import org.pdown.rest.form.HttpRequestForm;
public class JavascriptEngine {
public static ScriptEngine buildEngine() throws ScriptException, NoSuchMethodException {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine(new SafeClassFilter());
Window window = new Window();
Object global = engine.eval("this");
Object jsObject = engine.eval("Object");
Invocable invocable = (Invocable) engine;
invocable.invokeMethod(jsObject, "bindProperties", global, window);
engine.eval("var window = this");
return engine;
}
/**
* 禁止任何显式调用java代码
*/
private static class SafeClassFilter implements ClassFilter {
@Override
public boolean exposeToScripts(String s) {
return false;
}
}
public static void main(String[] args) throws ScriptException, NoSuchMethodException, JsonProcessingException, InterruptedException {
ScriptEngine engine = buildEngine();
Invocable invocable = (Invocable) engine;
engine.eval("load('E:/study/extensions/bilibili-helper/dist/hook.js')");
HttpRequestForm requestForm = new HttpRequestForm();
requestForm.setUrl("https://www.bilibili.com/video/av34765642");
Object result = invocable.invokeFunction("error");
ScriptContext ctx = new SimpleScriptContext();
ctx.setAttribute("result", result, ScriptContext.ENGINE_SCOPE);
System.out.println(engine.eval("!!result&&typeof result=='object'&&typeof result.then=='function'", ctx));
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/Window.java
================================================
package org.pdown.gui.extension.jsruntime.polyfill;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import jdk.internal.dynalink.beans.StaticClass;
import org.pdown.gui.extension.jsruntime.polyfill.property.Console;
import org.pdown.gui.extension.jsruntime.polyfill.property.Document;
public class Window {
private static final String TIMEOUT_THREAD_NAME = "babel4j-timeout-";
private static final String INTERVAL_THREAD_NAME = "babel4j-interval-";
private static final AtomicLong THREAD_ID = new AtomicLong(0);
public Console console = new Console();
public Document document = new Document();
public StaticClass XMLHttpRequest = StaticClass.forClass(org.pdown.gui.extension.jsruntime.polyfill.property.XMLHttpRequest.class);
public long setTimeout(Function function, long timeout) {
Long id = THREAD_ID.addAndGet(1);
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(timeout);
function.apply(null);
} catch (InterruptedException e) {
}
}, TIMEOUT_THREAD_NAME + id).start();
return id;
}
public void clearTimeout(Long id) {
Thread temp = Thread.getAllStackTraces().keySet().stream()
.filter(thread -> (TIMEOUT_THREAD_NAME + id).equals(thread.getName()))
.findFirst()
.orElse(null);
if (temp != null) {
temp.interrupt();
}
}
public long setInterval(Function function, long timeout) {
Long id = THREAD_ID.addAndGet(1);
new Thread(() -> {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(timeout);
function.apply(null);
}
} catch (InterruptedException e) {
}
}, INTERVAL_THREAD_NAME + id).start();
return id;
}
public void clearInterval(Long id) {
Thread temp = Thread.getAllStackTraces().keySet().stream()
.filter(thread -> (INTERVAL_THREAD_NAME + id).equals(thread.getName()))
.findFirst()
.orElse(null);
if (temp != null) {
temp.interrupt();
}
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Console.java
================================================
package org.pdown.gui.extension.jsruntime.polyfill.property;
public class Console {
public void log(Object object) {
System.out.println(object);
}
public void debug(Object object) {
System.out.println(object);
}
public void error(Object object) {
if (object instanceof Throwable) {
Throwable throwable = (Throwable) object;
throwable.printStackTrace();
} else {
System.out.println(object);
}
}
public void error(Object msg, Object throwable) {
if (throwable instanceof Throwable) {
((Throwable) throwable).printStackTrace();
} else {
System.out.println(msg);
}
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Document.java
================================================
package org.pdown.gui.extension.jsruntime.polyfill.property;
public class Document {
private String cookie;
public String getCookie() {
return cookie;
}
public void setCookie(String cookie) {
this.cookie = cookie;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/XMLHttpRequest.java
================================================
package org.pdown.gui.extension.jsruntime.polyfill.property;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import org.springframework.util.StringUtils;
public class XMLHttpRequest {
private String method;
private String url;
public Function onreadystatechange;
public int readyState = 0;
public int status = 0;
public String responseText;
private Map<String, String> customRequestHeads = new LinkedHashMap<>();
private Map<String, String> responseHeads = new LinkedHashMap<>();
public void setRequestHeader(String header, String value) {
customRequestHeads.put(header, value);
}
public String getResponseHeader(String header) {
return responseHeads.get(header.toLowerCase());
}
public void open(String method, String url) {
this.method = method;
this.url = url;
}
public void open(String method, String url, boolean async) {
this.open(method, url);
}
private static String DEFAULT_UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36";
public void send(String data) throws IOException {
URL u = new URL(url);
HttpURLConnection connection = (HttpURLConnection) u.openConnection();
readystatechange(1);
connection.setRequestMethod(method.toUpperCase());
connection.setRequestProperty("User-Agent", DEFAULT_UA);
customRequestHeads.entrySet().stream().forEach(entry -> connection.setRequestProperty(entry.getKey(), entry.getValue()));
connection.setDoOutput(true);
if (data != null && data.trim().length() > 0) {
try (
OutputStream outputStream = connection.getOutputStream()
) {
outputStream.write(data.getBytes(Charset.forName("UTF-8")));
}
}
int code = connection.getResponseCode();
connection.getHeaderFields().entrySet().forEach(entry -> {
if (entry.getKey() != null) {
responseHeads.put(entry.getKey().toLowerCase(), entry.getValue().stream().collect(Collectors.joining("; ")));
}
}
);
readystatechange(2, code);
String charset = "UTF-8";
String contentType = connection.getContentType();
if (!StringUtils.isEmpty(contentType)) {
Pattern pattern = Pattern.compile("charset=(.*)$", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(contentType);
if (matcher.find()) {
charset = matcher.group(1);
}
}
InputStream inputStream = code != 200 ? connection.getErrorStream() : connection.getInputStream();
if (responseHeads.entrySet().stream().anyMatch(entry -> "Content-Encoding".equalsIgnoreCase(entry.getKey()) && entry.getValue().matches("^.*(?i)(gzip).*$"))) {
inputStream = new GZIPInputStream(inputStream);
}
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))
) {
readystatechange(3, code);
responseText = reader.lines().collect(Collectors.joining("\n"));
readystatechange(4, code);
}
}
public static void main(String[] args) throws IOException {
URL u = new URL("http://www.baidu.com");
Proxy proxy = new Proxy(Type.SOCKS, new InetSocketAddress("127.0.0.1", 1088));
HttpURLConnection connection = (HttpURLConnection) u.openConnection(proxy);
System.out.println(connection.getResponseCode());
}
public void send() throws IOException {
send(null);
}
private void readystatechange(int readyState, int status) {
this.readyState = readyState;
this.status = status;
if (onreadystatechange != null) {
onreadystatechange.apply(null);
}
}
private void readystatechange(int readyState) {
readystatechange(readyState, status);
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/AjaxIntercept.java
================================================
package org.pdown.gui.extension.mitm.intercept;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Map;
/**
* 通过代理服务器代理ajax请求,避免浏览器CORS问题
*/
public class AjaxIntercept extends HttpProxyIntercept {
private static final String PROXY_SEND_KEY = "X-Proxy-Send";
private boolean proxyFlag;
@Override
public void beforeRequest(Channel clientChannel, HttpRequest httpRequest, HttpProxyInterceptPipeline pipeline) throws Exception {
proxyFlag = httpRequest.headers().contains(PROXY_SEND_KEY);
super.beforeRequest(clientChannel, httpRequest, pipeline);
}
@Override
public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) throws Exception {
if (proxyFlag) {
httpResponse.setStatus(HttpResponseStatus.OK);
proxyChannel.close();
ObjectMapper objectMapper = new ObjectMapper();
LastHttpContent content = new DefaultLastHttpContent();
String proxyRequestRaw = URLDecoder.decode(pipeline.getHttpRequest().headers().get(PROXY_SEND_KEY), "utf-8");
try {
ProxyRequest proxyRequest = objectMapper.readValue(proxyRequestRaw, ProxyRequest.class);
ProxyResponse proxyResponse = doRequest(proxyRequest);
httpResponse.setStatus(HttpResponseStatus.valueOf(proxyResponse.getStatus()));
content.content().writeBytes(proxyResponse.getData());
} catch (IOException e) {
e.printStackTrace();
httpResponse.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
}
httpResponse.headers().remove(HttpHeaderNames.CONTENT_ENCODING);
httpResponse.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.content().readableBytes());
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, AsciiString.cached("application/json; charset=utf-8"));
super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
clientChannel.writeAndFlush(content);
} else {
super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
}
}
@Override
public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpContent httpContent, HttpProxyInterceptPipeline pipeline) throws Exception {
if (!proxyFlag) {
super.afterResponse(clientChannel, proxyChannel, httpContent, pipeline);
}
}
private ProxyResponse doRequest(ProxyRequest proxyRequest) throws IOException {
URL url = new URL(proxyRequest.getUrl());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(proxyRequest.getMethod().toUpperCase());
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
if (proxyRequest.getHeads() != null) {
for (Map.Entry<String, String> entry : proxyRequest.getHeads().entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
connection.setDoInput(true);
if (!StringUtils.isEmpty(proxyRequest.getRawData())) {
connection.setDoOutput(true);
try (
OutputStream output = connection.getOutputStream()
) {
output.write(proxyRequest.getRawData().getBytes());
output.flush();
}
} else if (proxyRequest.getData() != null) {
connection.setDoOutput(true);
try (
OutputStream output = connection.getOutputStream()
) {
ObjectMapper objectMapper = new ObjectMapper();
output.write(objectMapper.writeValueAsBytes(proxyRequest.getData()));
output.flush();
}
}
ProxyResponse proxyResponse = new ProxyResponse();
proxyResponse.setStatus(connection.getResponseCode());
try (
ByteArrayOutputStream output = new ByteArrayOutputStream();
InputStream input = connection.getResponseCode() == 200 ? connection.getInputStream() : connection.getErrorStream()
) {
byte[] bts = new byte[8192];
int len;
while ((len = input.read(bts)) != -1) {
output.write(bts, 0, len);
}
proxyResponse.setData(output.toByteArray());
return proxyResponse;
}
}
static class ProxyRequest {
private String method;
private String url;
private Map<String, String> heads;
private Map<String, Object> data;
private String rawData;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Map<String, String> getHeads() {
return heads;
}
public void setHeads(Map<String, String> heads) {
this.heads = heads;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
public String getRawData() {
return rawData;
}
public void setRawData(String rawData) {
this.rawData = rawData;
}
}
static class ProxyResponse {
private int status;
private byte[] data;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/CookieIntercept.java
================================================
package org.pdown.gui.extension.mitm.intercept;
import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.AsciiString;
import io.netty.util.internal.StringUtil;
import java.net.URL;
/**
* 嗅探目标网站cookie,支持HTTP only
*/
public class CookieIntercept extends HttpProxyIntercept {
@Override
public void beforeRequest(Channel clientChannel, HttpRequest httpRequest, HttpProxyInterceptPipeline pipeline) throws Exception {
String acceptValue = httpRequest.headers().get(HttpHeaderNames.ACCEPT);
if (acceptValue != null && acceptValue.contains("application/x-sniff-cookie")) {
HttpResponse httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, new DefaultHttpHeaders());
httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
AsciiString customHeadKey = AsciiString.cached("X-Sniff-Cookie");
String cookie = pipeline.getHttpRequest().headers().get(HttpHeaderNames.COOKIE);
httpResponse.headers().set(customHeadKey, cookie == null ? "" : cookie);
httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, customHeadKey);
String origin = httpRequest.headers().get(HttpHeaderNames.ORIGIN);
if (StringUtil.isNullOrEmpty(origin)) {
String referer = httpRequest.headers().get(HttpHeaderNames.REFERER);
URL url = new URL(referer);
origin = url.getHost();
}
httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
httpResponse.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, true);
clientChannel.writeAndFlush(httpResponse);
clientChannel.writeAndFlush(new DefaultLastHttpContent());
clientChannel.close();
} else {
super.beforeRequest(clientChannel, httpRequest, pipeline);
}
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/ScriptIntercept.java
================================================
package org.pdown.gui.extension.mitm.intercept;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
import com.github.monkeywie.proxyee.intercept.common.FullResponseIntercept;
import com.github.monkeywie.proxyee.util.ByteUtil;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.List;
import org.pdown.core.util.HttpDownUtil;
import org.pdown.gui.extension.ContentScript;
import org.pdown.gui.extension.ExtensionContent;
import org.pdown.gui.extension.ExtensionInfo;
import org.pdown.gui.extension.util.ExtensionUtil;
public class ScriptIntercept extends FullResponseIntercept {
@Override
public boolean match(HttpRequest httpRequest, HttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) {
return isHtml(httpRequest, httpResponse);
}
private static final String INSERT_TOKEN = "</head>";
private static final String INIT_TEMPLATE = ";(function (pdown) {\n"
+ " ${content}\n"
+ "})(${runtime})";
private String readInsertTemplate(ExtensionInfo extensionInfo) {
String js = ExtensionUtil.readRuntimeTemplate(extensionInfo);
js = INIT_TEMPLATE.replace("${runtime}", js);
js = "<script type=\"text/javascript\">\n" + js + "\n</script>";
return js;
}
@Override
public void handelResponse(HttpRequest httpRequest, FullHttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) {
List<ExtensionInfo> extensionInfoList = ExtensionContent.get();
if (isEmpty(extensionInfoList)) {
return;
}
for (ExtensionInfo extensionInfo : extensionInfoList) {
if (isEmpty(extensionInfo.getContentScripts())) {
continue;
}
for (ContentScript contentScript : extensionInfo.getContentScripts()) {
//扩展注入正则表达式与当前访问的url匹配则注入脚本
String url = HttpDownUtil.getUrl(httpRequest);
if (contentScript.isMatch(url)) {
String apiTemplate = readInsertTemplate(extensionInfo);
StringBuilder scriptsBuilder = new StringBuilder();
for (String script : contentScript.getScripts()) {
File scriptFile = new File(extensionInfo.getMeta().getFullPath() + File.separator + script);
if (scriptFile.exists() && scriptFile.isFile()) {
try {
scriptsBuilder.append(new String(Files.readAllBytes(scriptFile.toPath()), "UTF-8"));
} catch (IOException e) {
}
}
}
apiTemplate = apiTemplate.replace("${content}", scriptsBuilder.toString());
int index = ByteUtil.findText(httpResponse.content(), INSERT_TOKEN);
ByteUtil.insertText(httpResponse.content(), index == -1 ? 0 : index - INSERT_TOKEN.length(), apiTemplate, Charset.forName("UTF-8"));
}
}
}
}
private boolean isEmpty(List list) {
return list == null || list.size() == 0;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/SniffIntercept.java
================================================
package org.pdown.gui.extension.mitm.intercept;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
import com.github.monkeywie.proxyee.util.HttpUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.ReferenceCountUtil;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Set;
import org.pdown.core.entity.HttpRequestInfo;
import org.pdown.core.entity.HttpResponseInfo;
import org.pdown.core.util.HttpDownUtil;
import org.pdown.core.util.ProtoUtil.RequestProto;
import org.pdown.gui.DownApplication;
import org.pdown.gui.extension.ExtensionContent;
import org.pdown.rest.form.HttpRequestForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SniffIntercept extends HttpProxyIntercept {
private final static Logger LOGGER = LoggerFactory.getLogger(SniffIntercept.class);
private boolean matchFlag = false;
private ByteBuf content;
private boolean downFlag = false;
@Override
public void beforeRequest(Channel clientChannel, HttpRequest httpRequest,
HttpProxyInterceptPipeline pipeline) throws Exception {
Set<String> sniffRegexs = ExtensionContent.getSniffRegexs();
if (sniffRegexs == null) {
matchFlag = false;
} else {
matchFlag = sniffRegexs.stream().anyMatch(regex -> HttpUtil.checkUrl(httpRequest, regex));
}
if (!matchFlag) {
super.beforeRequest(clientChannel, httpRequest, pipeline);
return;
}
String contentLength = httpRequest.headers().get(HttpHeaderNames.CONTENT_LENGTH);
//缓存request content
if (contentLength != null) {
content = PooledByteBufAllocator.DEFAULT.buffer();
}
pipeline.beforeRequest(clientChannel, HttpRequestInfo.adapter(httpRequest));
}
@Override
public void beforeRequest(Channel clientChannel, HttpContent httpContent,
HttpProxyInterceptPipeline pipeline) throws Exception {
if (!matchFlag) {
super.beforeRequest(clientChannel, httpContent, pipeline);
return;
}
if (content != null) {
ByteBuf temp = httpContent.content().slice();
content.writeBytes(temp);
if (httpContent instanceof LastHttpContent) {
try {
byte[] contentBts = new byte[content.readableBytes()];
content.readBytes(contentBts);
((HttpRequestInfo) pipeline.getHttpRequest()).setContent(contentBts);
} finally {
ReferenceCountUtil.release(content);
}
}
}
pipeline.beforeRequest(clientChannel, httpContent);
}
@Override
public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse,
HttpProxyInterceptPipeline pipeline) throws Exception {
if (!matchFlag) {
super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
return;
}
if ((httpResponse.status().code() + "").indexOf("20") == 0) { //响应码为20x
HttpHeaders httpResHeaders = httpResponse.headers();
String accept = pipeline.getHttpRequest().headers().get(HttpHeaderNames.ACCEPT);
String contentType = httpResHeaders.get(HttpHeaderNames.CONTENT_TYPE);
//有两种情况进行下载 1.url后缀为.xxx 2.带有CONTENT_DISPOSITION:ATTACHMENT响应头
String disposition = httpResHeaders.get(HttpHeaderNames.CONTENT_DISPOSITION);
if (accept != null
&& accept.matches("^.*text/html.*$")
&& ((disposition != null
&& disposition.contains(HttpHeaderValues.ATTACHMENT)
&& disposition.contains(HttpHeaderValues.FILENAME))
|| isDownContentType(contentType))) {
downFlag = true;
}
HttpRequestInfo httpRequestInfo = (HttpRequestInfo) pipeline.getHttpRequest();
if (downFlag) { //如果是下载
LOGGER.debug("=====================下载===========================\n" +
pipeline.getHttpRequest().toString() + "\n" +
"------------------------------------------------" +
httpResponse.toString() + "\n" +
"================================================");
proxyChannel.close();//关闭嗅探下载连接
httpRequestInfo.setRequestProto(new RequestProto(pipeline.getRequestProto().getHost(), pipeline.getRequestProto().getPort(), pipeline.getRequestProto().getSsl()));
HttpRequestForm requestForm = HttpRequestForm.parse(httpRequestInfo);
HttpResponseInfo responseInfo = HttpDownUtil.getHttpResponseInfo(httpRequestInfo, null, null, (NioEventLoopGroup) clientChannel.eventLoop().parent());
httpResponse.headers().clear();
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
String js = "<script type=\"text/javascript\">window.history.go(-1)</script>";
HttpContent httpContent = new DefaultLastHttpContent();
httpContent.content().writeBytes(js.getBytes());
httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, httpContent.content().readableBytes());
clientChannel.writeAndFlush(httpResponse);
clientChannel.writeAndFlush(httpContent);
clientChannel.close();
ObjectMapper objectMapper = new ObjectMapper();
String requestParam = URLEncoder.encode(objectMapper.writeValueAsString(requestForm), "utf-8");
String responseParam = URLEncoder.encode(objectMapper.writeValueAsString(responseInfo), "utf-8");
String uri = "/#/tasks?request=" + requestParam + "&response=" + responseParam;
DownApplication.INSTANCE.loadUri(uri, false);
return;
} else {
if (httpRequestInfo.content() != null) {
httpRequestInfo.setContent(null);
}
}
}
super.afterResponse(clientChannel, proxyChannel, httpResponse, pipeline);
}
@Override
public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpContent httpContent,
HttpProxyInterceptPipeline pipeline) throws Exception {
if (!matchFlag) {
super.afterResponse(clientChannel, proxyChannel, httpContent, pipeline);
return;
}
if (downFlag) {
httpContent.release();
} else {
pipeline.afterResponse(clientChannel, proxyChannel, httpContent);
}
}
//https://chromium.googlesource.com/chromium/src/+/master/net/base/mime_util.cc
private static final String[] CONTENT_TYPES = {
"application/javascript",
"application/x-javascript",
"application/wasm",
"application/x-chrome-extension",
"application/xhtml+xml",
"application/font-woff",
"application/json",
"application/x-shockwave-flash",
"audio/mpeg",
"audio/flac",
"audio/mp3",
"audio/ogg",
"audio/wav",
"audio/webm",
"audio/x-m4a",
"image/gif",
"image/jpeg",
"image/png",
"image/apng",
"image/webp",
"image/x-icon",
"image/bmp",
"image/jpeg",
"image/svg+xml",
"image/tiff",
"image/vnd.microsoft.icon",
"image/x-png",
"image/x-xbitmap",
"video/webm",
"video/ogg",
"video/mp4",
"video/mpeg",
"text/css",
"text/html",
"text/xml",
"text/calendar",
"text/html",
"text/plain",
"text/x-sh",
"text/xml",
"multipart/related",
"message/rfc822",
};
private boolean isDownContentType(String contentType) {
if (contentType != null) {
String contentTypeFinal = contentType.split(";")[0].trim().toLowerCase();
return Arrays.stream(CONTENT_TYPES).noneMatch(type -> contentTypeFinal.equals(type));
}
return true;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/server/PDownProxyServer.java
================================================
package org.pdown.gui.extension.mitm.server;
import com.github.monkeywie.proxyee.exception.HttpProxyExceptionHandle;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptInitializer;
import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
import com.github.monkeywie.proxyee.server.HttpProxyServer;
import com.github.monkeywie.proxyee.server.HttpProxyServerConfig;
import io.netty.channel.Channel;
import org.pdown.gui.content.PDownConfigContent;
import org.pdown.gui.entity.PDownConfigInfo;
import org.pdown.gui.extension.mitm.intercept.AjaxIntercept;
import org.pdown.gui.extension.mitm.intercept.CookieIntercept;
import org.pdown.gui.extension.mitm.intercept.ScriptIntercept;
import org.pdown.gui.extension.mitm.intercept.SniffIntercept;
import org.pdown.gui.extension.mitm.ssl.PDownCACertFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PDownProxyServer {
private static final Logger LOGGER = LoggerFactory.getLogger(PDownProxyServer.class);
private static volatile HttpProxyServer httpProxyServer;
public static volatile boolean isStart = false;
public static void start(int port) {
HttpProxyServerConfig config = new HttpProxyServerConfig();
//处理ssl
config.setHandleSsl(true);
//线程池数量都设置为1
config.setBossGroupThreads(1);
config.setWorkerGroupThreads(1);
config.setProxyGroupThreads(1);
httpProxyServer = new HttpProxyServer()
.serverConfig(config)
.proxyConfig(PDownConfigInfo.convert(PDownConfigContent.getInstance().get().getProxyConfig()))
.caCertFactory(new PDownCACertFactory())
.proxyInterceptInitializer(new HttpProxyInterceptInitializer() {
@Override
public void init(HttpProxyInterceptPipeline pipeline) {
pipeline.addLast(new CookieIntercept());
pipeline.addLast(new AjaxIntercept());
pipeline.addLast(new ScriptIntercept());
pipeline.addLast(new SniffIntercept());
}
})
.httpProxyExceptionHandle(new HttpProxyExceptionHandle() {
@Override
public void beforeCatch(Channel clientChannel, Throwable cause) throws Exception {
LOGGER.warn("beforeCatch", cause);
}
@Override
public void afterCatch(Channel clientChannel, Channel proxyChannel, Throwable cause) throws Exception {
LOGGER.warn("afterCatch", cause);
}
});
isStart = true;
httpProxyServer.start(port);
}
public static void close() {
if (httpProxyServer != null) {
httpProxyServer.close();
}
isStart = false;
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/ssl/PDownCACertFactory.java
================================================
package org.pdown.gui.extension.mitm.ssl;
import com.github.monkeywie.proxyee.crt.CertUtil;
import com.github.monkeywie.proxyee.server.HttpProxyCACertFactory;
import java.io.File;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import org.pdown.rest.util.PathUtil;
public class PDownCACertFactory implements HttpProxyCACertFactory {
private static final String SSL_PATH = PathUtil.ROOT_PATH + File.separator + "ssl" + File.separator;
@Override
public X509Certificate getCACert() throws Exception {
return CertUtil.loadCert(SSL_PATH + "ca.crt");
}
@Override
public PrivateKey getCAPriKey() throws Exception {
return CertUtil.loadPriKey(SSL_PATH + ".ca_pri.der");
}
}
================================================
FILE: main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionCertUtil.java
================================================
package org.pdown.gui.extension.mitm.util;
import com.github.monkeywie.proxyee.crt.CertUtil;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.pdown.core.util.FileUtil;
import org.pdown.core.util.OsUtil;
import org.pdown.gui.DownApplication;
import org.pdown.gui.util.ExecUtil;
import sun.security.x509.X500Name;
/**
* 用于处理系统的证书安装、查询和卸载
*/
public class ExtensionCertUtil {
/**
* 在指定目录生成一个ca证书和私钥
*/
public static void buildCert(String path, String subjectName) throws Exception {
//生成ca证书和私钥
KeyPair keyPair = CertUtil.genKeyPair();
File priKeyFile = FileUtil.createFile(path + File.separator + ".ca_pri.der", true);
File caCertFile = FileUtil.createFile(path + File.separator + "ca.crt", false);
Files.write(Paths.get(priKeyFile.toURI()), keyPair.getPrivate().getEncoded());
Files.write(Paths.get(caCertFile.toURI()),
CertUtil.genCACert(
"C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=" + subjectName,
new Date(),
new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(3650)),
keyPair)
.getEncoded());
}
/**
* 安装证书
*/
public static void installCert(File file) throws IOException {
String path = file.getPath();
if (OsUtil.isWindows()) {
ExecUtil.execBlock("certutil",
"-addstore",
"-user",
"root",
path);
} else if (OsUtil.isMac()) {
ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/cert/install?path=" + URLEncoder.encode(path, "utf-8"));
}
}
/**
* 通过证书subjectName和sha1,判断系统是否已安装该证书
*/
public static boolean isInstalledCert(File file) throws Exception {
if (!file.exists()) {
return false;
}
if (OsUtil.isUnix()) {
return true;
}
X509Certificate cert = CertUtil.loadCert(file.toURI());
String subjectName = ((X500Name) cert.getSubjectDN()).getCommonName();
String sha1 = getCertSHA1(cert);
return findCertList(subjectName).toUpperCase().replaceAll("\\s", "").indexOf(":" + sha1.toUpperCase()) != -1;
}
/**
* 通过证书subjectName,判断系统是否已安装此subjectName的证书
*/
public static boolean existsCert(String subjectName) throws IOException {
if (OsUtil.isWindows() && findCertList(subjectName).toUpperCase().indexOf("=====") != -1) {
return true;
} else if (OsUtil.isMac() && findCertList(subjectName).toUpperCase().indexOf("BEGIN CERTIFICATE") != -1) {
return true;
}
return false;
}
/**
* 通过证书name,卸载证书
*/
public static void uninstallCert(String subjectName) throws IOException {
if (OsUtil.isWindows()) {
Pattern pattern = Pattern.compile("(?i)\\(sha1\\):\\s(.*)\r?\n");
String certList = findCertList(subjectName);
Matcher matcher = pattern.matcher(certList);
while (matcher.find()) {
String hash = matcher.group(1).replaceAll("\\s", "");
ExecUtil.execBlock("certutil",
"-delstore",
"-user",
"root",
hash);
}
} else if (OsUtil.isMac()) {
String certList = findCertList(subjectName);
Pattern pattern = Pattern.compile("(?i)SHA-1 hash:\\s(.*)\r?\n");
Matcher matcher = pattern.matcher(certList);
while (matcher.find()) {
String hash = matcher.group(1);
ExecUtil.httpGet("http://127.0.0.1:" + DownApplication.macToolPort + "/cert/uninstall"
+ "?hash=" + hash);
}
}
}
//查询证书列表
private static String findCertList(String subjectName) throws IOException {
if (OsUtil.isWindows()) {
return ExecUtil.exec("certutil ",
"-store",
"-user",
gitextract_5ck2h9al/
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── front/
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .postcssrc.js
│ ├── .prettierrc
│ ├── .vscode/
│ │ └── settings.json
│ ├── babel.config.js
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── App.vue
│ │ ├── common/
│ │ │ ├── http.js
│ │ │ └── native.js
│ │ ├── components/
│ │ │ ├── ExtensionSetting.vue
│ │ │ ├── FileChoose/
│ │ │ │ └── index.vue
│ │ │ ├── Table/
│ │ │ │ └── index.vue
│ │ │ └── Task/
│ │ │ ├── Create.vue
│ │ │ └── Resolve.vue
│ │ ├── i18n/
│ │ │ ├── en-US.js
│ │ │ ├── zh-CN.js
│ │ │ └── zh-TW.js
│ │ ├── main.js
│ │ ├── router.js
│ │ ├── store.js
│ │ └── views/
│ │ ├── About.vue
│ │ ├── Extension.vue
│ │ ├── Setting.vue
│ │ ├── Support.vue
│ │ └── Tasks.vue
│ └── vue.config.js
├── main/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── org/
│ │ └── pdown/
│ │ └── gui/
│ │ ├── DownApplication.java
│ │ ├── com/
│ │ │ ├── Browser.java
│ │ │ ├── CheckboxMenuItemGroup.java
│ │ │ └── Components.java
│ │ ├── content/
│ │ │ └── PDownConfigContent.java
│ │ ├── entity/
│ │ │ └── PDownConfigInfo.java
│ │ ├── extension/
│ │ │ ├── ContentScript.java
│ │ │ ├── ExtensionConfig.java
│ │ │ ├── ExtensionContent.java
│ │ │ ├── ExtensionInfo.java
│ │ │ ├── HookScript.java
│ │ │ ├── Meta.java
│ │ │ ├── Setting.java
│ │ │ ├── jsruntime/
│ │ │ │ ├── JavascriptEngine.java
│ │ │ │ └── polyfill/
│ │ │ │ ├── Window.java
│ │ │ │ └── property/
│ │ │ │ ├── Console.java
│ │ │ │ ├── Document.java
│ │ │ │ └── XMLHttpRequest.java
│ │ │ ├── mitm/
│ │ │ │ ├── intercept/
│ │ │ │ │ ├── AjaxIntercept.java
│ │ │ │ │ ├── CookieIntercept.java
│ │ │ │ │ ├── ScriptIntercept.java
│ │ │ │ │ └── SniffIntercept.java
│ │ │ │ ├── server/
│ │ │ │ │ └── PDownProxyServer.java
│ │ │ │ ├── ssl/
│ │ │ │ │ └── PDownCACertFactory.java
│ │ │ │ └── util/
│ │ │ │ ├── ExtensionCertUtil.java
│ │ │ │ ├── ExtensionProxyUtil.java
│ │ │ │ └── WinInet.java
│ │ │ └── util/
│ │ │ └── ExtensionUtil.java
│ │ ├── http/
│ │ │ ├── EmbedHttpServer.java
│ │ │ ├── controller/
│ │ │ │ ├── ApiController.java
│ │ │ │ ├── DefaultController.java
│ │ │ │ ├── NativeController.java
│ │ │ │ └── PacController.java
│ │ │ └── util/
│ │ │ └── HttpHandlerUtil.java
│ │ ├── rest/
│ │ │ └── HttpDownAppCallback.java
│ │ ├── update/
│ │ │ ├── CheckUpdate.java
│ │ │ └── VersionInfo.java
│ │ └── util/
│ │ ├── AppUtil.java
│ │ ├── ConfigUtil.java
│ │ ├── ExecUtil.java
│ │ └── I18nUtil.java
│ └── resources/
│ ├── application-dev.yml
│ ├── application-prd.yml
│ ├── application.yml
│ ├── banner.txt
│ ├── extension/
│ │ └── runtime.js
│ ├── i18n/
│ │ ├── messages_en-US.yml
│ │ ├── messages_zh-CN.yml
│ │ └── messages_zh-TW.yml
│ ├── logback-dev.xml
│ └── logback-prd.xml
├── pom.xml
└── runner/
├── .gitignore
├── pom.xml
└── src/
└── main/
├── deploy/
│ └── package/
│ └── macosx/
│ └── Proxyee Down.icns
└── java/
└── org/
└── pdown/
└── gui/
└── Runner.java
SYMBOL INDEX (363 symbols across 44 files)
FILE: front/src/common/http.js
method build (line 5) | build() {
FILE: front/src/main.js
method data (line 128) | data() {
FILE: main/src/main/java/org/pdown/gui/DownApplication.java
class DownApplication (line 59) | public class DownApplication extends Application {
method start (line 81) | @Override
method initConfig (line 103) | private void initConfig() throws IOException {
method initRest (line 123) | private void initRest() {
method initExtension (line 136) | private void initExtension() {
method initMacMITMTool (line 168) | private void initMacMITMTool() {
method initEmbedHttpServer (line 206) | private void initEmbedHttpServer() {
method initTray (line 218) | private void initTray() throws AWTException {
method loadPopupMenu (line 236) | public void loadPopupMenu() {
method refreshBrowserMenu (line 262) | public void refreshBrowserMenu() {
method initBrowser (line 267) | private void initBrowser() throws AWTException {
method initWindow (line 277) | private void initWindow() {
method show (line 300) | public void show(boolean isTray) {
method loadUri (line 322) | public void loadUri(String uri, boolean isTray, boolean isStartup) {
method loadUri (line 346) | public void loadUri(String uri, boolean isTray) {
method alertAndExit (line 351) | private void alertAndExit(String msg) {
method doCheck (line 382) | private static void doCheck() {
method main (line 395) | public static void main(String[] args) {
FILE: main/src/main/java/org/pdown/gui/com/Browser.java
class Browser (line 15) | public class Browser extends Region {
method Browser (line 22) | public Browser() {
method layoutChildren (line 65) | @Override
method load (line 72) | public void load(String url) {
method isLoad (line 76) | public boolean isLoad() {
method refreshText (line 80) | public void refreshText() {
FILE: main/src/main/java/org/pdown/gui/com/CheckboxMenuItemGroup.java
class CheckboxMenuItemGroup (line 9) | public class CheckboxMenuItemGroup implements ItemListener {
method add (line 14) | public void add(CheckboxMenuItem cbmi) {
method addActionListener (line 20) | public void addActionListener(ItemListener itemListener) {
method itemStateChanged (line 24) | @Override
method selectItem (line 42) | public void selectItem(CheckboxMenuItem itemToSelect) {
method getSelectedItem (line 48) | public CheckboxMenuItem getSelectedItem() {
FILE: main/src/main/java/org/pdown/gui/com/Components.java
class Components (line 18) | public class Components {
method alert (line 23) | public static void alert(String msg) {
method fileChooser (line 51) | public static File fileChooser() {
method dirChooser (line 63) | public static File dirChooser() {
method buildBackgroundTopStage (line 72) | private static Stage buildBackgroundTopStage() {
FILE: main/src/main/java/org/pdown/gui/content/PDownConfigContent.java
class PDownConfigContent (line 14) | public class PDownConfigContent extends PersistenceContent<PDownConfigIn...
method getInstance (line 18) | public static PDownConfigContent getInstance() {
method type (line 22) | @Override
method savePath (line 28) | @Override
method defaultValue (line 33) | @Override
FILE: main/src/main/java/org/pdown/gui/entity/PDownConfigInfo.java
class PDownConfigInfo (line 7) | public class PDownConfigInfo implements Serializable {
method getLocale (line 27) | public String getLocale() {
method setLocale (line 31) | public PDownConfigInfo setLocale(String locale) {
method getUiMode (line 36) | public int getUiMode() {
method setUiMode (line 40) | public PDownConfigInfo setUiMode(int uiMode) {
method getProxyMode (line 45) | public int getProxyMode() {
method setProxyMode (line 49) | public PDownConfigInfo setProxyMode(int proxyMode) {
method getExtFileServers (line 54) | public List<String> getExtFileServers() {
method setExtFileServers (line 58) | public PDownConfigInfo setExtFileServers(List<String> extFileServers) {
method getUpdateCheckRate (line 63) | public int getUpdateCheckRate() {
method setUpdateCheckRate (line 67) | public PDownConfigInfo setUpdateCheckRate(int updateCheckRate) {
method getLastUpdateCheck (line 72) | public long getLastUpdateCheck() {
method setLastUpdateCheck (line 76) | public PDownConfigInfo setLastUpdateCheck(long lastUpdateCheck) {
method getProxyConfig (line 81) | public ProxyConfig getProxyConfig() {
method setProxyConfig (line 85) | public PDownConfigInfo setProxyConfig(ProxyConfig proxyConfig) {
method isAutoOpen (line 90) | public boolean isAutoOpen() {
method setAutoOpen (line 94) | public PDownConfigInfo setAutoOpen(boolean autoOpen) {
method convert (line 99) | public static com.github.monkeywie.proxyee.proxy.ProxyConfig convert(P...
FILE: main/src/main/java/org/pdown/gui/extension/ContentScript.java
class ContentScript (line 5) | public class ContentScript {
method getMatches (line 10) | public String[] getMatches() {
method setMatches (line 14) | public ContentScript setMatches(String[] matches) {
method getScripts (line 19) | public String[] getScripts() {
method setScripts (line 23) | public ContentScript setScripts(String[] scripts) {
method isMatch (line 28) | public boolean isMatch(String url) {
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionConfig.java
class ExtensionConfig (line 5) | public class ExtensionConfig {
method getLocalExtensions (line 10) | public List<String> getLocalExtensions() {
method setLocalExtensions (line 14) | public void setLocalExtensions(List<String> localExtensions) {
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionContent.java
class ExtensionContent (line 17) | public class ExtensionContent {
method load (line 31) | public static void load() throws IOException {
method getConfig (line 72) | public static ExtensionConfig getConfig() {
method saveConfig (line 76) | public synchronized static void saveConfig() throws IOException {
method refresh (line 80) | public synchronized static ExtensionInfo refresh(String path, boolean ...
method refresh (line 109) | public synchronized static ExtensionInfo refresh(String path) throws I...
method remove (line 113) | public synchronized static void remove(String path, boolean isLocal) t...
method refresh (line 136) | public synchronized static void refresh() {
method parseExtensionDir (line 167) | private static ExtensionInfo parseExtensionDir(File extendDir, boolean...
method parseExtensionDir (line 195) | private static ExtensionInfo parseExtensionDir(File extendDir) {
method get (line 199) | public static List<ExtensionInfo> get() {
method getProxyWildCards (line 203) | public static Set<String> getProxyWildCards() {
method getSniffRegexs (line 207) | public static Set<String> getSniffRegexs() {
FILE: main/src/main/java/org/pdown/gui/extension/ExtensionInfo.java
class ExtensionInfo (line 5) | public class ExtensionInfo {
method getTitle (line 18) | public String getTitle() {
method setTitle (line 22) | public ExtensionInfo setTitle(String title) {
method getVersion (line 27) | public double getVersion() {
method setVersion (line 31) | public ExtensionInfo setVersion(double version) {
method getHomepage (line 36) | public String getHomepage() {
method setHomepage (line 40) | public ExtensionInfo setHomepage(String homepage) {
method getDescription (line 45) | public String getDescription() {
method setDescription (line 49) | public ExtensionInfo setDescription(String description) {
method getProxyWildcards (line 54) | public List<String> getProxyWildcards() {
method setProxyWildcards (line 58) | public ExtensionInfo setProxyWildcards(List<String> proxyWildcards) {
method getSniffRegexs (line 63) | public List<String> getSniffRegexs() {
method setSniffRegexs (line 67) | public ExtensionInfo setSniffRegexs(List<String> sniffRegexs) {
method getContentScripts (line 72) | public List<ContentScript> getContentScripts() {
method setContentScripts (line 76) | public ExtensionInfo setContentScripts(List<ContentScript> contentScri...
method getHookScript (line 81) | public HookScript getHookScript() {
method setHookScript (line 85) | public ExtensionInfo setHookScript(HookScript hookScript) {
method getSettings (line 90) | public List<Setting> getSettings() {
method setSettings (line 94) | public ExtensionInfo setSettings(List<Setting> settings) {
method getMeta (line 99) | public Meta getMeta() {
method setMeta (line 103) | public ExtensionInfo setMeta(Meta meta) {
FILE: main/src/main/java/org/pdown/gui/extension/HookScript.java
class HookScript (line 5) | public class HookScript {
method getEvents (line 18) | public Event[] getEvents() {
method setEvents (line 22) | public HookScript setEvents(Event[] events) {
method getScript (line 27) | public String getScript() {
method setScript (line 31) | public HookScript setScript(String script) {
method hasEvent (line 39) | public Event hasEvent(String event, String url) {
class Event (line 50) | public static class Event {
method getOn (line 56) | public String getOn() {
method setOn (line 60) | public Event setOn(String on) {
method getMatches (line 65) | public String[] getMatches() {
method setMatches (line 69) | public Event setMatches(String[] matches) {
method getMethod (line 74) | public String getMethod() {
method setMethod (line 78) | public Event setMethod(String method) {
FILE: main/src/main/java/org/pdown/gui/extension/Meta.java
class Meta (line 8) | public class Meta {
method getPath (line 19) | public String getPath() {
method setPath (line 23) | public Meta setPath(String path) {
method getFullPath (line 28) | public String getFullPath() {
method setFullPath (line 32) | public Meta setFullPath(String fullPath) {
method isEnabled (line 37) | public boolean isEnabled() {
method setEnabled (line 41) | public Meta setEnabled(boolean enabled) {
method getSettings (line 46) | public Map<String, Object> getSettings() {
method setSettings (line 50) | public Meta setSettings(Map<String, Object> settings) {
method getData (line 55) | public Map<String, Object> getData() {
method setData (line 59) | public Meta setData(Map<String, Object> data) {
method isLocal (line 64) | public boolean isLocal() {
method setLocal (line 68) | public void setLocal(boolean local) {
method save (line 72) | public void save() {
method load (line 79) | public static Meta load(String path) {
FILE: main/src/main/java/org/pdown/gui/extension/Setting.java
class Setting (line 5) | public class Setting {
method getName (line 15) | public String getName() {
method setName (line 19) | public Setting setName(String name) {
method getTitle (line 24) | public String getTitle() {
method setTitle (line 28) | public Setting setTitle(String title) {
method getType (line 33) | public String getType() {
method setType (line 37) | public Setting setType(String type) {
method getValue (line 42) | public Object getValue() {
method setValue (line 46) | public Setting setValue(Object value) {
method getDescription (line 51) | public String getDescription() {
method setDescription (line 55) | public Setting setDescription(String description) {
method isMultiple (line 60) | public boolean isMultiple() {
method setMultiple (line 64) | public Setting setMultiple(boolean multiple) {
method getOptions (line 69) | public Map<String, Object> getOptions() {
method setOptions (line 73) | public Setting setOptions(Map<String, Object> options) {
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/JavascriptEngine.java
class JavascriptEngine (line 15) | public class JavascriptEngine {
method buildEngine (line 17) | public static ScriptEngine buildEngine() throws ScriptException, NoSuc...
class SafeClassFilter (line 32) | private static class SafeClassFilter implements ClassFilter {
method exposeToScripts (line 34) | @Override
method main (line 40) | public static void main(String[] args) throws ScriptException, NoSuchM...
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/Window.java
class Window (line 10) | public class Window {
method setTimeout (line 20) | public long setTimeout(Function function, long timeout) {
method clearTimeout (line 32) | public void clearTimeout(Long id) {
method setInterval (line 42) | public long setInterval(Function function, long timeout) {
method clearInterval (line 56) | public void clearInterval(Long id) {
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Console.java
class Console (line 3) | public class Console {
method log (line 5) | public void log(Object object) {
method debug (line 9) | public void debug(Object object) {
method error (line 13) | public void error(Object object) {
method error (line 22) | public void error(Object msg, Object throwable) {
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Document.java
class Document (line 3) | public class Document {
method getCookie (line 7) | public String getCookie() {
method setCookie (line 11) | public void setCookie(String cookie) {
FILE: main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/XMLHttpRequest.java
class XMLHttpRequest (line 23) | public class XMLHttpRequest {
method setRequestHeader (line 36) | public void setRequestHeader(String header, String value) {
method getResponseHeader (line 40) | public String getResponseHeader(String header) {
method open (line 44) | public void open(String method, String url) {
method open (line 49) | public void open(String method, String url, boolean async) {
method send (line 55) | public void send(String data) throws IOException {
method main (line 100) | public static void main(String[] args) throws IOException {
method send (line 107) | public void send() throws IOException {
method readystatechange (line 111) | private void readystatechange(int readyState, int status) {
method readystatechange (line 119) | private void readystatechange(int readyState) {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/AjaxIntercept.java
class AjaxIntercept (line 23) | public class AjaxIntercept extends HttpProxyIntercept {
method beforeRequest (line 29) | @Override
method afterResponse (line 35) | @Override
method afterResponse (line 63) | @Override
method doRequest (line 70) | private ProxyResponse doRequest(ProxyRequest proxyRequest) throws IOEx...
class ProxyRequest (line 115) | static class ProxyRequest {
method getMethod (line 124) | public String getMethod() {
method setMethod (line 128) | public void setMethod(String method) {
method getUrl (line 132) | public String getUrl() {
method setUrl (line 136) | public void setUrl(String url) {
method getHeads (line 140) | public Map<String, String> getHeads() {
method setHeads (line 144) | public void setHeads(Map<String, String> heads) {
method getData (line 148) | public Map<String, Object> getData() {
method setData (line 152) | public void setData(Map<String, Object> data) {
method getRawData (line 156) | public String getRawData() {
method setRawData (line 160) | public void setRawData(String rawData) {
class ProxyResponse (line 165) | static class ProxyResponse {
method getStatus (line 170) | public int getStatus() {
method setStatus (line 174) | public void setStatus(int status) {
method getData (line 178) | public byte[] getData() {
method setData (line 182) | public void setData(byte[] data) {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/CookieIntercept.java
class CookieIntercept (line 21) | public class CookieIntercept extends HttpProxyIntercept {
method beforeRequest (line 23) | @Override
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/ScriptIntercept.java
class ScriptIntercept (line 20) | public class ScriptIntercept extends FullResponseIntercept {
method match (line 22) | @Override
method readInsertTemplate (line 32) | private String readInsertTemplate(ExtensionInfo extensionInfo) {
method handelResponse (line 39) | @Override
method isEmpty (line 72) | private boolean isEmpty(List list) {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/intercept/SniffIntercept.java
class SniffIntercept (line 33) | public class SniffIntercept extends HttpProxyIntercept {
method beforeRequest (line 42) | @Override
method beforeRequest (line 63) | @Override
method afterResponse (line 86) | @Override
method afterResponse (line 144) | @Override
method isDownContentType (line 204) | private boolean isDownContentType(String contentType) {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/server/PDownProxyServer.java
class PDownProxyServer (line 19) | public class PDownProxyServer {
method start (line 26) | public static void start(int port) {
method close (line 62) | public static void close() {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/ssl/PDownCACertFactory.java
class PDownCACertFactory (line 10) | public class PDownCACertFactory implements HttpProxyCACertFactory {
method getCACert (line 14) | @Override
method getCAPriKey (line 19) | @Override
FILE: main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionCertUtil.java
class ExtensionCertUtil (line 25) | public class ExtensionCertUtil {
method buildCert (line 31) | public static void buildCert(String path, String subjectName) throws E...
method installCert (line 49) | public static void installCert(File file) throws IOException {
method isInstalledCert (line 65) | public static boolean isInstalledCert(File file) throws Exception {
method existsCert (line 81) | public static boolean existsCert(String subjectName) throws IOException {
method uninstallCert (line 93) | public static void uninstallCert(String subjectName) throws IOException {
method findCertList (line 119) | private static String findCertList(String subjectName) throws IOExcept...
method getCertSHA1 (line 139) | private static String getCertSHA1(X509Certificate certificate) throws ...
method btsToHex (line 146) | private static String btsToHex(byte[] bts) {
method main (line 154) | public static void main(String[] args) throws Exception {
FILE: main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionProxyUtil.java
class ExtensionProxyUtil (line 28) | public class ExtensionProxyUtil {
method enabledPACProxy (line 33) | public static void enabledPACProxy(String url) throws IOException {
method enabledHTTPProxy (line 61) | public static void enabledHTTPProxy(String host, int port) throws IOEx...
method disabledProxy (line 91) | public static String disabledProxy() throws IOException {
method getRemoteInterface (line 115) | private static String getRemoteInterface() throws IOException {
method getInterfacesInfo (line 148) | public static Map<String, List<String>> getInterfacesInfo() throws Soc...
method main (line 168) | public static void main(String[] args) throws Exception {
method buildOptionList (line 172) | private static INTERNET_PER_CONN_OPTION_LIST buildOptionList(String co...
method refreshOptions (line 192) | private static boolean refreshOptions(INTERNET_PER_CONN_OPTION_LIST li...
FILE: main/src/main/java/org/pdown/gui/extension/mitm/util/WinInet.java
type WinInet (line 12) | public interface WinInet extends StdCallLibrary {
method InternetSetOption (line 133) | boolean InternetSetOption(Pointer hInternet, int dwOption, INTERNET_PE...
method InternetSetOption (line 135) | boolean InternetSetOption(Pointer hInternet, int dwOption, Pointer lpB...
class FILETIME (line 141) | class FILETIME extends Structure {
class ByReference (line 142) | public static class ByReference extends FILETIME implements Structur...
method ByReference (line 143) | public ByReference() {
method ByReference (line 146) | public ByReference(Pointer memory) {
method FILETIME (line 151) | public FILETIME() {
method FILETIME (line 154) | public FILETIME(Pointer memory) {
method getFieldOrder (line 162) | @SuppressWarnings("rawtypes")
class INTERNET_PER_CONN_OPTION (line 177) | class INTERNET_PER_CONN_OPTION extends Structure {
class ByReference (line 178) | public static class ByReference extends INTERNET_PER_CONN_OPTION imp...
method ByReference (line 179) | public ByReference() {
method ByReference (line 182) | public ByReference(Pointer memory) {
method INTERNET_PER_CONN_OPTION (line 187) | public INTERNET_PER_CONN_OPTION() {
method INTERNET_PER_CONN_OPTION (line 190) | public INTERNET_PER_CONN_OPTION(Pointer memory) {
class VALUE_UNION (line 195) | public static class VALUE_UNION extends Union {
method getFieldOrder (line 204) | @SuppressWarnings("rawtypes")
class INTERNET_PER_CONN_OPTION_LIST (line 218) | class INTERNET_PER_CONN_OPTION_LIST extends Structure {
class ByReference (line 219) | public static class ByReference extends INTERNET_PER_CONN_OPTION_LIS...
method ByReference (line 220) | public ByReference() {
method ByReference (line 223) | public ByReference(Pointer memory) {
method INTERNET_PER_CONN_OPTION_LIST (line 228) | public INTERNET_PER_CONN_OPTION_LIST() {
method INTERNET_PER_CONN_OPTION_LIST (line 231) | public INTERNET_PER_CONN_OPTION_LIST(Pointer memory) {
method getFieldOrder (line 242) | @SuppressWarnings("rawtypes")
FILE: main/src/main/java/org/pdown/gui/extension/util/ExtensionUtil.java
class ExtensionUtil (line 45) | public class ExtensionUtil {
method install (line 52) | public static void install(String server, String path, String files) t...
method update (line 59) | public static void update(String server, String path, String files) th...
method download (line 99) | private static void download(String server, String path, String writeP...
method copy (line 109) | public static void copy(File sourceLocation, File targetLocation) thro...
method copyDirectory (line 117) | private static void copyDirectory(File source, File target) throws IOE...
method copyFile (line 126) | private static void copyFile(File source, File target) throws IOExcept...
method readRuntimeTemplate (line 142) | public static String readRuntimeTemplate(ExtensionInfo extensionInfo) {
method buildExtensionRuntimeEngine (line 169) | public static ScriptEngine buildExtensionRuntimeEngine(ExtensionInfo e...
method invoke (line 183) | public static Object invoke(ExtensionInfo extensionInfo, Event event, ...
method main (line 223) | public static void main(String[] args) {
FILE: main/src/main/java/org/pdown/gui/http/EmbedHttpServer.java
class EmbedHttpServer (line 32) | public class EmbedHttpServer {
method EmbedHttpServer (line 40) | public EmbedHttpServer(int port) {
method invoke (line 47) | public FullHttpResponse invoke(String uri, Channel channel, FullHttpRe...
method fixUri (line 70) | private String fixUri(String uri) {
method start (line 81) | public void start() {
method start (line 85) | public void start(GenericFutureListener startedListener) {
method addController (line 141) | public EmbedHttpServer addController(Object obj) {
method main (line 146) | public static void main(String[] args) {
FILE: main/src/main/java/org/pdown/gui/http/controller/ApiController.java
class ApiController (line 16) | @RequestMapping("api")
method createTask (line 19) | @RequestMapping("createTask")
method getQueryParams (line 31) | private Map<String, String> getQueryParams(FullHttpRequest request) th...
FILE: main/src/main/java/org/pdown/gui/http/controller/DefaultController.java
class DefaultController (line 16) | @RequestMapping("/")
method handle (line 19) | public FullHttpResponse handle(Channel channel, FullHttpRequest reques...
method buildHead (line 49) | private void buildHead(FullHttpResponse httpResponse, String mime) {
FILE: main/src/main/java/org/pdown/gui/http/controller/NativeController.java
class NativeController (line 51) | @RequestMapping("native")
method dirChooser (line 56) | @RequestMapping("dirChooser")
method handle (line 73) | @RequestMapping("fileChooser")
method getInitConfig (line 94) | @RequestMapping("getInitConfig")
method getConfig (line 122) | @RequestMapping("getConfig")
method setConfig (line 127) | @RequestMapping("setConfig")
method showFile (line 154) | @RequestMapping("showFile")
method openUrl (line 171) | @RequestMapping("openUrl")
method doUpdate (line 181) | @RequestMapping("doUpdate")
method getUpdateProgress (line 213) | @RequestMapping("getUpdateProgress")
method doRestart (line 227) | @RequestMapping("doRestart")
method getExtensions (line 236) | @RequestMapping("getExtensions")
method installExtension (line 246) | @RequestMapping("installExtension")
method updateExtension (line 254) | @RequestMapping("updateExtension")
method installLocalExtension (line 262) | @RequestMapping("installLocalExtension")
method uninstallExtension (line 281) | @RequestMapping("uninstallExtension")
method extensionCommon (line 294) | private FullHttpResponse extensionCommon(FullHttpRequest request, bool...
method toggleExtension (line 314) | @RequestMapping("toggleExtension")
method getProxyMode (line 332) | @RequestMapping("getProxyMode")
method changeProxyMode (line 339) | @RequestMapping("changeProxyMode")
method checkCert (line 354) | @RequestMapping("checkCert")
method installCert (line 361) | @RequestMapping("installCert")
method copy (line 401) | @RequestMapping("copy")
method updateExtensionSetting (line 413) | @RequestMapping("updateExtensionSetting")
method onResolve (line 427) | @RequestMapping("onResolve")
method getJSONParams (line 458) | private Map<String, Object> getJSONParams(FullHttpRequest request) thr...
method getJSONParams (line 463) | private <T> T getJSONParams(FullHttpRequest request, Class<T> clazz) t...
FILE: main/src/main/java/org/pdown/gui/http/controller/PacController.java
class PacController (line 14) | @RequestMapping("pac")
method build (line 33) | @RequestMapping("pdown.pac")
FILE: main/src/main/java/org/pdown/gui/http/util/HttpHandlerUtil.java
class HttpHandlerUtil (line 15) | public class HttpHandlerUtil {
method writeJson (line 17) | public static void writeJson(Channel channel, Object obj) {
method buildJson (line 21) | public static FullHttpResponse buildJson(Object obj) {
method buildJson (line 25) | public static FullHttpResponse buildJson(Object obj, Include include) {
method buildContent (line 44) | public static FullHttpResponse buildContent(String content, String con...
FILE: main/src/main/java/org/pdown/gui/rest/HttpDownAppCallback.java
class HttpDownAppCallback (line 25) | public class HttpDownAppCallback extends HttpDownRestCallback {
method onStart (line 29) | @Override
method onResume (line 35) | @Override
method onPause (line 41) | @Override
method onError (line 47) | @Override
method onDone (line 53) | @Override
method commonHook (line 59) | private void commonHook(HttpDownBootstrap httpDownBootstrap, String ev...
method buildTaskInfo (line 104) | private Map<String, Object> buildTaskInfo(DownInfo downInfo) {
method clone (line 116) | private Object clone(Object source, Object target) {
FILE: main/src/main/java/org/pdown/gui/update/CheckUpdate.java
class CheckUpdate (line 10) | public class CheckUpdate {
method doCheck (line 14) | public static VersionInfo doCheck() {
FILE: main/src/main/java/org/pdown/gui/update/VersionInfo.java
class VersionInfo (line 3) | public class VersionInfo {
method getVersion (line 7) | public double getVersion() {
method setVersion (line 11) | public VersionInfo setVersion(double version) {
method getPath (line 16) | public String getPath() {
method setPath (line 20) | public VersionInfo setPath(String path) {
FILE: main/src/main/java/org/pdown/gui/util/AppUtil.java
class AppUtil (line 31) | public class AppUtil {
method checkIsInstalledCert (line 41) | public static boolean checkIsInstalledCert() throws Exception {
method refreshPAC (line 50) | public static void refreshPAC() throws IOException {
method startProxyServer (line 59) | public static void startProxyServer() throws IOException {
method download (line 67) | public static void download(String url, String path) throws IOException {
method fastDownload (line 113) | public static HttpDownBootstrap fastDownload(String url, File file, Ht...
FILE: main/src/main/java/org/pdown/gui/util/ConfigUtil.java
class ConfigUtil (line 7) | public class ConfigUtil {
method getString (line 18) | public static String getString(String key) {
method getBoolean (line 22) | public static boolean getBoolean(String key) {
method getInt (line 26) | public static int getInt(String key) {
method get (line 30) | static Object get(Map<String, Object> map, String key) {
method merge (line 42) | private static void merge(Map<String, Object> map1, Map<String, Object...
method getString (line 56) | private static String getString(Map<String, Object> map, String key) {
method getBoolean (line 60) | private static boolean getBoolean(Map<String, Object> map, String key) {
method getInt (line 64) | private static int getInt(Map<String, Object> map, String key) {
FILE: main/src/main/java/org/pdown/gui/util/ExecUtil.java
class ExecUtil (line 13) | public class ExecUtil {
method exec (line 18) | public static String exec(String... shell) throws IOException {
method execBlock (line 38) | public static void execBlock(String... shell) throws IOException {
method execBlockWithAdmin (line 55) | public static void execBlockWithAdmin(String shell) throws IOException {
method httpGet (line 77) | public static void httpGet(String url) throws IOException {
method main (line 86) | public static void main(String[] args) throws Exception {
FILE: main/src/main/java/org/pdown/gui/util/I18nUtil.java
class I18nUtil (line 18) | public class I18nUtil {
method takeLocale (line 51) | private static String takeLocale(String name) {
method getMessage (line 55) | public static String getMessage(String key, Object... args) {
method main (line 73) | public static void main(String[] args) {
FILE: runner/src/main/java/org/pdown/gui/Runner.java
class Runner (line 18) | public class Runner {
method main (line 27) | public static void main(String[] args) throws IOException {
method parseVmOptions (line 32) | private static List<String> parseVmOptions() {
method fork (line 57) | private static void fork() {
method alert (line 109) | private static void alert(String msg) {
Condensed preview — 90 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (308K chars).
[
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 133,
"preview": "<!--\n请根据以下模版提issue,不提供必要信息的issue将直接关闭\n日志文件在proxyee-down安装目录/main/log文件夹里\n-->\n### 问题描述(必要)\n### 版本号(必要)\n### 操作系统(必要)\n### 相"
},
{
"path": ".gitignore",
"chars": 186,
"preview": ".DS_Store\n\ntarget/\nlog/\n!.mvn/wrapper/maven-wrapper.jar\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.se"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 1971,
"preview": "## 续作出炉!!!\n\n新项目使用`golang`+`flutter`开发,支持所有平台的下载器,地址:https://github.com/GopeedLab/gopeed\n\n## 暂停维护此项目\n首先感谢大家支持和反馈才使得proxye"
},
{
"path": "front/.eslintrc.js",
"chars": 505,
"preview": "module.exports = {\n root: true,\n env: {\n node: true\n },\n extends: ['plugin:vue/essential', 'eslint:recommended'],"
},
{
"path": "front/.gitignore",
"chars": 226,
"preview": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn"
},
{
"path": "front/.postcssrc.js",
"chars": 58,
"preview": "module.exports = {\n plugins: {\n autoprefixer: {}\n }\n}"
},
{
"path": "front/.prettierrc",
"chars": 93,
"preview": "{\n \"eslintIntegration\": true,\n \"singleQuote\": true,\n \"semi\": false,\n \"printWidth\": 120\n}\n"
},
{
"path": "front/.vscode/settings.json",
"chars": 387,
"preview": "{\n \"vetur.format.defaultFormatter.html\": \"js-beautify-html\",\n \"vetur.format.defaultFormatterOptions\": {\n \"js-beauti"
},
{
"path": "front/babel.config.js",
"chars": 77,
"preview": "module.exports = {\r\n presets: ['@vue/app'],\r\n plugins: ['jsx-v-model']\r\n}\r\n"
},
{
"path": "front/package.json",
"chars": 1132,
"preview": "{\n \"name\": \"proxyee-down\",\n \"version\": \"3.0.0\",\n \"private\": true,\n \"scripts\": {\n \"start\": \"npm run serve\",\n \"s"
},
{
"path": "front/public/index.html",
"chars": 624,
"preview": "<!DOCTYPE html>\r\n<html>\r\n\r\n<head>\r\n <meta charset=\"utf-8\">\r\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n "
},
{
"path": "front/src/App.vue",
"chars": 3039,
"preview": "<template>\r\n <div id=\"app\">\r\n <i-menu mode=\"horizontal\"\r\n theme=\"dark\"\r\n :active-name=\"$route.path.substri"
},
{
"path": "front/src/common/http.js",
"chars": 529,
"preview": "import Vue from 'vue'\nimport axios from 'axios'\n\nexport default {\n build() {\n const client = axios.create()\n clie"
},
{
"path": "front/src/common/native.js",
"chars": 6366,
"preview": "import http from './http'\nimport axios from 'axios'\n\nconst client = http.build()\nconst clientNoSpin = axios.create()\n\n/*"
},
{
"path": "front/src/components/ExtensionSetting.vue",
"chars": 980,
"preview": "<template>\n <Form :label-width=\"70\">\n <FormItem v-for=\"(setting,index) in settings\"\n :key=\"index\"\n :label="
},
{
"path": "front/src/components/FileChoose/index.vue",
"chars": 1193,
"preview": "<template>\n <div class=\"file-choose\">\n <Input class=\"file-choose-input\"\n :value=\"value\"\n readonly\n di"
},
{
"path": "front/src/components/Table/index.vue",
"chars": 8180,
"preview": "<template>\n <section class=\"prye-tb\">\n <div class=\"tb-wrapper\">\n <div class=\"bg\"></div>\n <div class=\"tb-he"
},
{
"path": "front/src/components/Task/Create.vue",
"chars": 6234,
"preview": "<template>\n <Modal :title=\"$t('tasks.createTask')\"\n :value=\"visible\"\n @input=\"closeModal\"\n @on-visible-change="
},
{
"path": "front/src/components/Task/Resolve.vue",
"chars": 4610,
"preview": "<template>\n <Modal :title=\"$t('tasks.createTask')\"\n :value=\"value\"\n @input=\"$emit('input', arguments[0])\"\n :cl"
},
{
"path": "front/src/i18n/en-US.js",
"chars": 5378,
"preview": "export default {\n nav: {\n tasks: 'Tasks',\n extension: 'Extensions',\n setting: 'Settings',\n about: 'About',\n"
},
{
"path": "front/src/i18n/zh-CN.js",
"chars": 3842,
"preview": "export default {\n nav: {\n tasks: '任务管理',\n extension: '扩展管理',\n setting: '软件设置',\n about: '关于项目',\n support:"
},
{
"path": "front/src/i18n/zh-TW.js",
"chars": 3855,
"preview": "export default {\n nav: {\n tasks: '任務管理',\n extension: '擴充管理',\n setting: '軟體設定',\n about: '關於專案',\n support:"
},
{
"path": "front/src/main.js",
"chars": 3725,
"preview": "import Vue from 'vue'\r\nimport App from './App.vue'\r\nimport router from './router'\r\nimport store from './store'\r\nimport i"
},
{
"path": "front/src/router.js",
"chars": 841,
"preview": "import Vue from 'vue'\r\nimport Router from 'vue-router'\r\nimport Tasks from './views/Tasks.vue'\r\nimport Extension from './"
},
{
"path": "front/src/store.js",
"chars": 151,
"preview": "import Vue from 'vue'\r\nimport Vuex from 'vuex'\r\n\r\nVue.use(Vuex)\r\n\r\nexport default new Vuex.Store({\r\n state: {},\r\n muta"
},
{
"path": "front/src/views/About.vue",
"chars": 7153,
"preview": "<template>\n <div class=\"v-about\">\n <Card>\n <p slot=\"title\">{{ $t(\"about.project.title\") }}</p>\n <ul class="
},
{
"path": "front/src/views/Extension.vue",
"chars": 15937,
"preview": "<template>\n <div v-if=\"!certStatus\">\n <Card shadow>\n <p slot=\"title\">{{ $t('extension.conditions') }}</p>\n "
},
{
"path": "front/src/views/Setting.vue",
"chars": 10187,
"preview": "<template>\n <Form ref=\"form\"\n :label-width=\"100\"\n :model=\"form\"\n :rules=\"rules\"\n class=\"setting-form\">\n "
},
{
"path": "front/src/views/Support.vue",
"chars": 1523,
"preview": "<template>\n <div>\n <Card>\n <p slot=\"title\">如果觉得本软件不错的话,可以通过下面的二维码打赏作者,让作者有动力持续更新版本和修复BUG。</p>\n <div class="
},
{
"path": "front/src/views/Tasks.vue",
"chars": 10384,
"preview": "<template>\r\n <div class=\"tasks\">\r\n <div class=\"tasks-entry\">\r\n <Button type=\"info\"\r\n icon=\"plus\"\r\n "
},
{
"path": "front/vue.config.js",
"chars": 760,
"preview": "module.exports = {\r\n productionSourceMap: true, // Production environment does not generate source-map\r\n css: {\r\n s"
},
{
"path": "main/.gitignore",
"chars": 195,
"preview": "target/\n!.mvn/wrapper/maven-wrapper.jar\nsrc/main/resources/http\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.pr"
},
{
"path": "main/pom.xml",
"chars": 3967,
"preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaL"
},
{
"path": "main/src/main/java/org/pdown/gui/DownApplication.java",
"chars": 13011,
"preview": "package org.pdown.gui;\n\nimport java.awt.AWTException;\nimport java.awt.Desktop;\nimport java.awt.Dimension;\nimport java.aw"
},
{
"path": "main/src/main/java/org/pdown/gui/com/Browser.java",
"chars": 2838,
"preview": "package org.pdown.gui.com;\n\nimport javafx.geometry.HPos;\nimport javafx.geometry.VPos;\nimport javafx.scene.control.Contex"
},
{
"path": "main/src/main/java/org/pdown/gui/com/CheckboxMenuItemGroup.java",
"chars": 1425,
"preview": "package org.pdown.gui.com;\n\nimport java.awt.CheckboxMenuItem;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.Ite"
},
{
"path": "main/src/main/java/org/pdown/gui/com/Components.java",
"chars": 2128,
"preview": "package org.pdown.gui.com;\n\nimport java.io.File;\nimport javafx.geometry.Insets;\nimport javafx.scene.Group;\nimport javafx"
},
{
"path": "main/src/main/java/org/pdown/gui/content/PDownConfigContent.java",
"chars": 1454,
"preview": "package org.pdown.gui.content;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport java.io.File;\nimport java.n"
},
{
"path": "main/src/main/java/org/pdown/gui/entity/PDownConfigInfo.java",
"chars": 2562,
"preview": "package org.pdown.gui.entity;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport org.pdown.core.proxy.ProxyConf"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/ContentScript.java",
"chars": 650,
"preview": "package org.pdown.gui.extension;\n\nimport java.util.Arrays;\n\npublic class ContentScript {\n\n private String[] matches;\n "
},
{
"path": "main/src/main/java/org/pdown/gui/extension/ExtensionConfig.java",
"chars": 336,
"preview": "package org.pdown.gui.extension;\n\nimport java.util.List;\n\npublic class ExtensionConfig {\n\n //本地加载的扩展\n private List<Str"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/ExtensionContent.java",
"chars": 6862,
"preview": "package org.pdown.gui.extension;\n\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jac"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/ExtensionInfo.java",
"chars": 2283,
"preview": "package org.pdown.gui.extension;\n\nimport java.util.List;\n\npublic class ExtensionInfo {\n\n private String title; //扩展名称\n "
},
{
"path": "main/src/main/java/org/pdown/gui/extension/HookScript.java",
"chars": 1844,
"preview": "package org.pdown.gui.extension;\n\nimport java.util.Arrays;\n\npublic class HookScript {\n\n public static final String EVEN"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/Meta.java",
"chars": 1854,
"preview": "package org.pdown.gui.extension;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Map;\nimport org.pdow"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/Setting.java",
"chars": 1336,
"preview": "package org.pdown.gui.extension;\n\nimport java.util.Map;\n\npublic class Setting {\n\n private String name;\n private String"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/jsruntime/JavascriptEngine.java",
"chars": 2012,
"preview": "package org.pdown.gui.extension.jsruntime;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport javax.scri"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/Window.java",
"chars": 2065,
"preview": "package org.pdown.gui.extension.jsruntime.polyfill;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.a"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Console.java",
"chars": 648,
"preview": "package org.pdown.gui.extension.jsruntime.polyfill.property;\n\npublic class Console {\n\n public void log(Object object) {"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/Document.java",
"chars": 240,
"preview": "package org.pdown.gui.extension.jsruntime.polyfill.property;\n\npublic class Document {\n\n private String cookie;\n\n publi"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/jsruntime/polyfill/property/XMLHttpRequest.java",
"chars": 4218,
"preview": "package org.pdown.gui.extension.jsruntime.polyfill.property;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/intercept/AjaxIntercept.java",
"chars": 6623,
"preview": "package org.pdown.gui.extension.mitm.intercept;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.m"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/intercept/CookieIntercept.java",
"chars": 2453,
"preview": "package org.pdown.gui.extension.mitm.intercept;\n\nimport com.github.monkeywie.proxyee.intercept.HttpProxyIntercept;\nimpor"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/intercept/ScriptIntercept.java",
"chars": 3058,
"preview": "package org.pdown.gui.extension.mitm.intercept;\n\nimport com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipelin"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/intercept/SniffIntercept.java",
"chars": 8216,
"preview": "package org.pdown.gui.extension.mitm.intercept;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.m"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/server/PDownProxyServer.java",
"chars": 2642,
"preview": "package org.pdown.gui.extension.mitm.server;\n\nimport com.github.monkeywie.proxyee.exception.HttpProxyExceptionHandle;\nim"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/ssl/PDownCACertFactory.java",
"chars": 721,
"preview": "package org.pdown.gui.extension.mitm.ssl;\n\nimport com.github.monkeywie.proxyee.crt.CertUtil;\nimport com.github.monkeywie"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionCertUtil.java",
"chars": 5201,
"preview": "package org.pdown.gui.extension.mitm.util;\n\nimport com.github.monkeywie.proxyee.crt.CertUtil;\nimport java.io.File;\nimpor"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/util/ExtensionProxyUtil.java",
"chars": 7321,
"preview": "package org.pdown.gui.extension.mitm.util;\n\nimport com.sun.jna.Pointer;\nimport java.io.IOException;\nimport java.net.Inet"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/mitm/util/WinInet.java",
"chars": 9327,
"preview": "package org.pdown.gui.extension.mitm.util;\n\nimport com.sun.jna.Native;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.St"
},
{
"path": "main/src/main/java/org/pdown/gui/extension/util/ExtensionUtil.java",
"chars": 9255,
"preview": "package org.pdown.gui.extension.util;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.j"
},
{
"path": "main/src/main/java/org/pdown/gui/http/EmbedHttpServer.java",
"chars": 5903,
"preview": "package org.pdown.gui.http;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.Channel;\nimport io.netty"
},
{
"path": "main/src/main/java/org/pdown/gui/http/controller/ApiController.java",
"chars": 1705,
"preview": "package org.pdown.gui.http.controller;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.DefaultFullH"
},
{
"path": "main/src/main/java/org/pdown/gui/http/controller/DefaultController.java",
"chars": 3251,
"preview": "package org.pdown.gui.http.controller;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.DefaultFullH"
},
{
"path": "main/src/main/java/org/pdown/gui/http/controller/NativeController.java",
"chars": 18352,
"preview": "package org.pdown.gui.http.controller;\n\nimport com.fasterxml.jackson.annotation.JsonInclude.Include;\nimport com.fasterxm"
},
{
"path": "main/src/main/java/org/pdown/gui/http/controller/PacController.java",
"chars": 2280,
"preview": "package org.pdown.gui.http.controller;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.FullHttpRequ"
},
{
"path": "main/src/main/java/org/pdown/gui/http/util/HttpHandlerUtil.java",
"chars": 2208,
"preview": "package org.pdown.gui.http.util;\n\nimport com.fasterxml.jackson.annotation.JsonInclude.Include;\nimport com.fasterxml.jack"
},
{
"path": "main/src/main/java/org/pdown/gui/rest/HttpDownAppCallback.java",
"chars": 4766,
"preview": "package org.pdown.gui.rest;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.util.HashMap;\nimport java.u"
},
{
"path": "main/src/main/java/org/pdown/gui/update/CheckUpdate.java",
"chars": 1084,
"preview": "package org.pdown.gui.update;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.net.HttpURLConnection;\nim"
},
{
"path": "main/src/main/java/org/pdown/gui/update/VersionInfo.java",
"chars": 403,
"preview": "package org.pdown.gui.update;\n\npublic class VersionInfo {\n private double version;\n private String path;\n\n public dou"
},
{
"path": "main/src/main/java/org/pdown/gui/util/AppUtil.java",
"chars": 4422,
"preview": "package org.pdown.gui.util;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport ja"
},
{
"path": "main/src/main/java/org/pdown/gui/util/ConfigUtil.java",
"chars": 2043,
"preview": "package org.pdown.gui.util;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.yaml.snakeyaml.Yaml;\n\npublic c"
},
{
"path": "main/src/main/java/org/pdown/gui/util/ExecUtil.java",
"chars": 2409,
"preview": "package org.pdown.gui.util;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimpo"
},
{
"path": "main/src/main/java/org/pdown/gui/util/I18nUtil.java",
"chars": 2502,
"preview": "package org.pdown.gui.util;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport jav"
},
{
"path": "main/src/main/resources/application-dev.yml",
"chars": 154,
"preview": "logging:\n config: classpath:logback-dev.xml\nfront:\n port: 8080\napi:\n port: 7478\n#adminServer: http://127.0.0.1:9494/\n"
},
{
"path": "main/src/main/resources/application-prd.yml",
"chars": 115,
"preview": "logging:\n config: classpath:logback-prd.xml\nfront:\n port: -1\napi:\n port: 7478\nadminServer: http://api.pdown.org/"
},
{
"path": "main/src/main/resources/application.yml",
"chars": 99,
"preview": "version: 3.41\nspring:\n profiles:\n active: @environment@\n messages:\n basename: i18n/messages"
},
{
"path": "main/src/main/resources/banner.txt",
"chars": 524,
"preview": " _\n | |"
},
{
"path": "main/src/main/resources/extension/runtime.js",
"chars": 10091,
"preview": "(function () {\n var API_PORT = '${apiPort}'\n var FRONT_PORT = '${frontPort}'\n var REST_PORT = '26339'\n var a"
},
{
"path": "main/src/main/resources/i18n/messages_en-US.yml",
"chars": 343,
"preview": "gui:\n warning: Warning\n alert:\n startError: Startup error:{0}\n restPortBusy: Port 26339 is busy, please do not r"
},
{
"path": "main/src/main/resources/i18n/messages_zh-CN.yml",
"chars": 229,
"preview": "gui:\n warning: 警告\n alert:\n startError: 启动失败:{0}\n restPortBusy: 端口26339被占用,请勿重复启动软件\n noFreePort: 系统无法分配TCP端口\n "
},
{
"path": "main/src/main/resources/i18n/messages_zh-TW.yml",
"chars": 235,
"preview": "gui:\n warning: 警告\n alert:\n startError: 啟動失敗:{0}\n restPortBusy: 連接埠 26339 被占用,請勿重複啟動軟體\n noFreePort: 系統無法指派 TCP"
},
{
"path": "main/src/main/resources/logback-dev.xml",
"chars": 715,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n <appender name=\"stdout\" class=\"ch.qos.logback.core.ConsoleAppen"
},
{
"path": "main/src/main/resources/logback-prd.xml",
"chars": 1136,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n <appender name=\"stdout\" class=\"ch.qos.logback.core.ConsoleAppen"
},
{
"path": "pom.xml",
"chars": 769,
"preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaL"
},
{
"path": "runner/.gitignore",
"chars": 171,
"preview": "target/\n!.mvn/wrapper/maven-wrapper.jar\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springB"
},
{
"path": "runner/pom.xml",
"chars": 1504,
"preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaL"
},
{
"path": "runner/src/main/java/org/pdown/gui/Runner.java",
"chars": 3239,
"preview": "package org.pdown.gui;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.i"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the proxyee-down-org/proxyee-down GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 90 files (282.2 KB), approximately 72.9k tokens, and a symbol index with 363 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.