8 Commits
1.0.4 ... 2.1.0

Author SHA1 Message Date
putyy
619ac47962 更新配置 2024-08-30 20:43:12 +08:00
putyy
6b79585564 内置aria2下载、增加视频号画质设置、取消重复下载限制、优化下载命名等 2024-08-30 20:40:10 +08:00
putyy
c1f05876e3 优化拦截规则 2024-08-19 16:55:52 +08:00
putyy
2981518cf4 Update README.md 2024-07-24 09:47:48 +08:00
putyy
511111f874 V2.0重磅更新,所见即所得! 2024-07-18 17:28:26 +08:00
putyy
ca718deaad 1.优化文件下载优先标题命名 2.完善资源类型case 2024-06-05 14:04:00 +08:00
putyy
7a01544323 修改mac打包 2024-02-21 16:30:24 +08:00
putyy
3082faaadc 新增代理设置、更换证书、mac下增加http的代理设置、完善image content-type等 2024-02-19 15:27:10 +08:00
58 changed files with 1511 additions and 626 deletions

2
.gitignore vendored
View File

@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
temp
test
dist-ssr
dist-electron
release
@@ -28,4 +29,5 @@ release
# lockfile
package-lock.json
yarn.lock
pnpm-lock.yaml

View File

@@ -1,14 +1,45 @@
# res-downloader
## res-downloader(爱享素材下载器) 【[点击加入群聊](https://qm.qq.com/q/W8mVeZideE)】
🎯 基于 [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue.git)
📦 操作简单、可获取不同类型的资源
💪 支持获取视频、音频、图片、m3u8
🖥 支持获取视频号、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源
📦 操作简单、可获取不同类型的资源
🖥️ 支持Win10、Win11、Mac
🌐 支持视频、音频、图片、m3u8等网络资源下载
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
## 软件下载
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
## 使用方法
> 0. 安装一定要同意安装证书文件,安装一定要同意安装证书文件,安装一定要同意安装证书文件!
> 1. 打开本软件
> 2. 软件首页选择要获取的资源类型(默认选中的视频)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到资源列表
## 软件截图
![](public/show.webp)
## 常见问题
下载慢、大视频下载失败
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
Win7无法使用
> 软件不支持,也无计划支持
打开本软件,无法正常拦截获取
> 检查系统代理是否正确设置 代理地址127.0.0.1 端口8899
关闭软件后无法正常上网
> 手动关闭系统代理设置
打开本软件后无法上网
> 手动删除安装标识锁文件,之后再打开软件会进行检查证书是否正确安装
>> MAC: /Users/你的用户名称/.res-downloader@putyy/res-downloader-installed.lock
>> Win: C:\Users\Admin\.res-downloader@putyy/res-downloader-installed.lock
其他问题请留言 https://github.com/putyy/res-downloader/issues
## 二次开发
> ps 打包慢的问题可以参考 https://www.putyy.com/articles/87
@@ -22,30 +53,11 @@ yarn install
yarn run dev
# 打包mac
yarn run build --mac
yarn run build --universal --mac
# 打包win
yarn run build --win
```
## 软件截图
![](public/show.jpg)
## 使用方法
> 1. 打开本软件
> 2. 软件首页选择要获取的资源类型(默认选中的视频)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到要下载的资源
## 常见问题
> 1. 无法拦截获取
> > 手动检测系统代理是否设置正确 本软件代理地址: 127.0.0.1:8899
> 2. 关闭软件后无法正常上网
> > 手动关闭系统代理设置
## 实现原理
> 通过代理网络抓包拦截响应筛选出有用的资源同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度所以就有了本项目这样的软件。
## 参考项目
- [WeChatVideoDownloader](https://github.com/lecepin/WeChatVideoDownloader) 原项目是react写的本项目参考原项目用vue3重写了一下核心逻辑没什么变化主要是增加了一些新的功能再次感谢
## 免责声明
本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!

4
components.d.ts vendored
View File

@@ -14,10 +14,14 @@ declare module 'vue' {
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
Footer: typeof import('./src/components/layout/Footer.vue')['default']
Index: typeof import('./src/components/layout/Index.vue')['default']

View File

@@ -3,7 +3,7 @@
*/
{
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
"appId": "com.putyy.ResDownloader",
"appId": "com.putyy.res-downloader",
"asar": true,
"directories": {
"output": "release/${version}"
@@ -14,14 +14,35 @@
"electron/res/**/*"
],
"mac": {
"icon": "electron/res/icon/icons/mac/icon.icns",
"artifactName": "${productName}_${version}.${ext}",
"icon": "electron/res/icon/mac.icns",
"artifactName": "${productName}_${version}.${arch}.${ext}",
"singleArchFiles": "*",
"target": [
"dmg"
{
"target": "dmg",
"arch": [
'x64',
'arm64',
'universal'
]
}
],
"extraResources": [
{
"from": "electron/res/mac/aria2/aria2.conf",
"to": "electron/res/mac/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/mac/aria2/${arch}/aria2c",
"to": "electron/res/mac/aria2/aria2c",
"filter": ["**/*"],
}
]
},
"win": {
"icon": "electron/res/icon/icons/win/icon.ico",
"icon": "electron/res/icon/win.ico",
"artifactName": "${productName}_${version}.${ext}",
"target": [
{
"target": "nsis",
@@ -30,7 +51,13 @@
]
}
],
"artifactName": "${productName}_${version}.${ext}"
"extraResources": [
{
"from": "electron/res/win",
"to": "electron/res/win",
"filter": ["**/*"],
}
]
},
"nsis": {
"oneClick": false,
@@ -40,6 +67,7 @@
"deleteAppDataOnUninstall": false
},
"extraResources": [
"electron/res"
"electron/res/icon",
"electron/res/keys",
]
}

51
electron/main/aria2Rpc.ts Normal file
View File

@@ -0,0 +1,51 @@
const axios = require('axios')
import CONFIG from './const'
export class Aria2RPC {
constructor() {
this.url = `http://127.0.0.1:${CONFIG.ARIA_PORT}/jsonrpc`
this.id = 1
}
call(method, params) {
const requestData = {
jsonrpc: "2.0",
method: method,
params: params,
id: this.id++
};
return axios.post(this.url, requestData, {
headers: {
'Content-Type': 'application/json'
},
}).then((response)=>{
return response.data
})
}
addUri(uri, dir, filename, headers = {}) {
return this.call('aria2.addUri', [uri, {
dir: dir,
out: filename,
headers: headers,
}]);
}
tellStatus(gid) {
return this.call('aria2.tellStatus', [gid]);
}
calculateDownloadProgress(bitfield) {
// 将十六进制的 bitfield 转换为二进制字符串
const totalPieces = bitfield.length * 4; // 每个十六进制字符对应 4 位
const binaryString = bitfield.split('').map(hex => parseInt(hex, 16).toString(2).padStart(4, '0')).join('');
// 计算已下载的部分数
const downloadedPieces = binaryString.split('').filter(bit => bit === '1').length;
// 计算进度百分比
const progressPercentage = (downloadedPieces / totalPieces) * 100;
return progressPercentage.toFixed(2); // 保留两位小数
}
}

View File

@@ -21,7 +21,7 @@ export async function installCert(checkInstalled = true) {
`echo "输入本地登录密码" && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${CONFIG.CERT_PUBLIC_PATH}" && touch ${CONFIG.INSTALL_CERT_FLAG} && echo "安装完成"`,
)
dialog.showMessageBoxSync({
type: 'info',
type: "info",
message: `命令已复制到剪贴板,粘贴命令到终端并运行以安装并信任证书`,
});

View File

@@ -13,18 +13,18 @@ const EXECUTABLE_PATH = path.join(
)
const HOME_PATH = path.join(os.homedir(), '.res-downloader@putyy')
export default {
IS_DEV: isDev,
EXECUTABLE_PATH,
HOME_PATH,
CERT_PRIVATE_PATH: path.join(EXECUTABLE_PATH, './keys/private.pem'),
CERT_PUBLIC_PATH: path.join(EXECUTABLE_PATH, './keys/public.pem'),
INSTALL_CERT_FLAG: path.join(HOME_PATH, './installed.lock'),
WIN_CERT_INSTALL_HELPER: path.join(EXECUTABLE_PATH, './w_c.exe'),
APP_CN_NAME: '爱享素材下载器',
APP_EN_NAME: 'ResDownloader',
REGEDIT_VBS_PATH: path.join(EXECUTABLE_PATH, './regedit-vbs'),
OPEN_SSL_BIN_PATH: path.join(EXECUTABLE_PATH, './openssl/openssl.exe'),
OPEN_SSL_CNF_PATH: path.join(EXECUTABLE_PATH, './openssl/openssl.cnf'),
CERT_PRIVATE_PATH: path.join(EXECUTABLE_PATH, './keys/private.pem'),
CERT_PUBLIC_PATH: path.join(EXECUTABLE_PATH, './keys/public.pem'),
INSTALL_CERT_FLAG: path.join(HOME_PATH, './res-downloader-installed.lock'),
WIN_CERT_INSTALL_HELPER: path.join(EXECUTABLE_PATH, './win/w_c.exe'),
REGEDIT_VBS_PATH: path.join(EXECUTABLE_PATH, './win/regedit-vbs'),
OPEN_SSL_BIN_PATH: path.join(EXECUTABLE_PATH, './win/openssl/openssl.exe'),
OPEN_SSL_CNF_PATH: path.join(EXECUTABLE_PATH, './win/openssl/openssl.cnf'),
ARIA_PORT: "18899",
};

View File

@@ -3,6 +3,10 @@ import {release} from 'node:os'
import {join} from 'node:path'
import CONFIG from './const'
import initIPC, {setWin} from './ipc'
import {closeProxy} from "./setProxy"
import log from "electron-log"
import path from 'path'
import {spawn} from 'child_process'
// The built directory structure
//
@@ -45,6 +49,7 @@ process.on('unhandledRejection', () => {
let mainWindow: BrowserWindow | null = null
let previewWin: BrowserWindow | null = null
let aria2Process
// Here, you can also use other preload
const preload = join(__dirname, '../preload/index.js')
@@ -77,21 +82,16 @@ app.on('activate', () => {
}
})
// New window example arg: new windows url
ipcMain.handle('open-win', (_, arg) => {
const childWindow = new BrowserWindow({
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
},
})
if (process.env.VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${url}#${arg}`)
} else {
childWindow.loadFile(indexHtml, {hash: arg})
app.on('before-quit', async e => {
e.preventDefault()
try {
await closeProxy()
aria2Process && aria2Process.kill();
log.log("--------------closeProxy success--------------")
} catch (error) {
log.log("--------------proxy catch err--------------", error)
}
app.exit()
})
function createWindow() {
@@ -156,11 +156,6 @@ function createPreviewWindow(parent: BrowserWindow) {
// previewWin.hide()
previewWin.setTitle("预览")
previewWin.webContents.session.on('will-download', (event, item, webContents) => {
// console.log("取消下载")
item.cancel()
})
previewWin.on("page-title-updated", (event) => {
// 阻止该事件
event.preventDefault()
@@ -174,9 +169,45 @@ function createPreviewWindow(parent: BrowserWindow) {
})
}
function createArua2Process() {
// 根据操作系统选择 aria2 的路径
try {
let aria2Path, aria2Conf
if (process.platform === 'win32') {
// Windows
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2c.exe")
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2.conf")
} else {
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, "./mac/aria2" + (CONFIG.IS_DEV ? `/${process.arch}` : '/') + "/aria2c");
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, "./mac/aria2/aria2.conf")
}
// 启动 aria2
console.log("启动 aria2")
aria2Process = spawn(aria2Path, [`--conf-path=${aria2Conf}`, `--rpc-listen-port=${CONFIG.ARIA_PORT}`], {
windowsHide: false,
stdio: CONFIG.IS_DEV ? 'pipe' : 'ignore'
});
if(!aria2Process){
console.log("启动 aria2 失败")
}
if (CONFIG.IS_DEV) {
aria2Process.stdout.on('data', (data) => {
console.log(`aria2: ${data}`);
});
aria2Process.stderr.on('data', (data) => {
console.log(`aria2 error: ${data}`);
});
}
console.log("aria2 成功启动")
} catch (e) {
console.log(`aria2 process start err`, e);
}
}
app.whenReady().then(() => {
initIPC()
createWindow()
createPreviewWindow(mainWindow)
createArua2Process()
setWin(mainWindow, previewWin)
})
})

View File

@@ -1,44 +1,18 @@
import {ipcMain, dialog, BrowserWindow, app, shell} from 'electron'
import {startServer} from './proxyServer'
import {installCert, checkCertInstalled} from './cert'
import {downloadFile, decodeWxFile} from './utils'
import {decodeWxFile, suffix, getCurrentDateTimeFormatted} from './utils'
// @ts-ignore
import {hexMD5} from '../../src/common/md5'
import {Aria2RPC} from './aria2Rpc'
import fs from "fs"
import CryptoJS from 'crypto-js'
import {closeProxy, setProxy} from "./setProxy"
import log from "electron-log"
import {floor} from "lodash";
let getMac = require("getmac").default
let win: BrowserWindow
let previewWin: BrowserWindow
let isStartProxy = false
let isOpenProxy = false
let aesKey = "as5d45as4d6qe6wqfar6gt4749q6y7w6h34v64tv7t37ty5qwtv6t6qv"
const suffix = (type: string) => {
switch (type) {
case "video/mp4":
return ".mp4";
case "image/png":
return ".png";
case "image/webp":
return ".webp";
case "image/svg+xml":
return ".svg";
case "image/gif":
return ".gif";
case "audio/mpeg":
return ".mp3";
case "application/vnd.apple.mpegurl":
return ".m3u8";
}
}
const aria2RpcClient = new Aria2RPC()
export default function initIPC() {
ipcMain.handle('invoke_app_is_init', async (event, arg) => {
// 初始化应用 安装证书相关
return checkCertInstalled()
@@ -46,43 +20,23 @@ export default function initIPC() {
ipcMain.handle('invoke_init_app', (event, arg) => {
// 开始 初始化应用 安装证书相关
// console.log('invoke_init_app')
return installCert(false)
})
ipcMain.handle('invoke_start_proxy', async (event, arg) => {
// 启动代理服务
if (isStartProxy) {
if (isOpenProxy === false) {
isOpenProxy = true
setProxy('127.0.0.1', 8899)
.then(() => {
})
.catch((err) => {
})
}
return
}
isStartProxy = true
isOpenProxy = true
return startServer({
win: win,
setProxyErrorCallback: err => {
isStartProxy = false
isOpenProxy = false
},
installCert(false).then(r => {
})
})
ipcMain.handle('invoke_close_proxy', (event, arg) => {
// 关闭代理
try {
isOpenProxy = false
return closeProxy()
} catch (error) {
log.log("--------------closeProxy error--------------", error)
ipcMain.handle('invoke_start_proxy', (event, arg) => {
// 启动代理服务
if (isStartProxy) {
return
}
isStartProxy = true
return startServer({
win: win,
upstreamProxy: arg.upstream_proxy ? arg.upstream_proxy : "",
setProxyErrorCallback: err => {
console.log('setProxyErrorCallback', err)
},
})
})
ipcMain.handle('invoke_select_down_dir', async (event, arg) => {
@@ -104,52 +58,84 @@ export default function initIPC() {
return decodeWxFile(result?.[0], data.decode_key, result?.[0].replace(".mp4", "_解密.mp4"))
})
ipcMain.handle('invoke_file_exists', async (event, {save_path, url}) => {
let url_sign = hexMD5(url)
let res = fs.existsSync(`${save_path}/${url_sign}.mp4`)
return {is_file: res, fileName: `${save_path}/${url_sign}.mp4`}
ipcMain.handle('invoke_file_exists', async (event, {save_path, url, description}) => {
let fileName = description ? description.replace(/[^a-zA-Z\u4e00-\u9fa5]/g, '') : hexMD5(url);
let res = fs.existsSync(`${save_path}/${fileName}.mp4`)
return {is_file: res, fileName: `${save_path}/${fileName}.mp4`}
})
ipcMain.handle('invoke_down_file', async (event, {index, data, save_path, high}) => {
let down_url = data.down_url
if (high && data.high_url) {
down_url = data.high_url
}
ipcMain.handle('invoke_down_file', async (event, {data, save_path, quality}) => {
let down_url = data.url
if (!down_url) {
return false
return new Promise((resolve, reject) => {
resolve(false);
});
}
if (quality !== "-1" && data.decode_key && data.file_format) {
const format = data.file_format.split('#');
const qualityMap = [
format[0],
format[Math.floor(format.length / 2)],
format[format.length - 1]
];
down_url += "&X-snsvideoflag=" + qualityMap[quality];
}
let fileName = data?.description ? data.description.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') : hexMD5(down_url);
fileName = fileName + "_" + getCurrentDateTimeFormatted() + suffix(data.type)
let save_path_file = `${save_path}/${fileName}`
if (process.platform === 'win32') {
save_path_file = `${save_path}\\${fileName}`
}
let url_sign = hexMD5(down_url)
let save_path_file = `${save_path}/${url_sign}` + suffix(data.type)
if (fs.existsSync(save_path_file)) {
return {fullFileName: save_path_file, totalLen: ""}
let headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
}
// 开始下载
return downloadFile(
down_url,
data.decode_key,
save_path_file,
(res) => {
win?.webContents.send('on_down_file_schedule', {schedule: floor(res * 100)})
return new Promise((resolve, reject) => {
if (down_url.includes("douyin")) {
headers['Referer'] = down_url
}
).catch(err => {
// console.log('invoke_down_file:err', err)
return false
})
})
ipcMain.handle('invoke_get_mac', async (event) => {
let mac = getMac()
if (mac === "") {
return ""
}
return CryptoJS.AES.encrypt(mac, CryptoJS.enc.Hex.parse(aesKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString()
})
aria2RpcClient.addUri([down_url], save_path, fileName, headers).then((response) => {
let currentGid = response.result // 保存当前下载的 gid
let progressIntervalId = null
// // 开始定时查询下载进度
progressIntervalId = setInterval(() => {
aria2RpcClient.tellStatus(currentGid).then((status) => {
if (status.result.status !== "complete") {
const progress = aria2RpcClient.calculateDownloadProgress(status.result.bitfield);
win?.webContents.send('on_down_file_schedule', {schedule: `已下载${progress}%`})
} else {
clearInterval(progressIntervalId);
if (data.decode_key) {
win?.webContents.send('on_down_file_schedule', {schedule: `开始解密`})
decodeWxFile(save_path_file, data.decode_key, save_path_file.replace(".mp4", "_wx.mp4")).then((res) => {
fs.unlink(save_path_file, (err) => {
})
resolve(res);
}).catch((error) => {
console.log("err:", error)
resolve(false);
});
} else {
resolve({
fullFileName: save_path_file,
});
}
}
}).catch((error) => {
console.error(error);
clearInterval(progressIntervalId);
resolve(false);
});
}, 1000);
}).catch((error) => {
console.log("err:", error)
resolve(false);
});
});
});
ipcMain.handle('invoke_resources_preview', async (event, {url}) => {
if (!url) {
@@ -165,15 +151,32 @@ export default function initIPC() {
})
ipcMain.handle('invoke_open_default_browser', (event, {url}) => {
shell.openExternal(url).then(r => {})
shell.openExternal(url).then(r => {
})
})
ipcMain.handle('invoke_open_file_dir', (event, {save_path}) => {
shell.showItemInFolder(save_path)
})
ipcMain.handle('invoke_file_del', (event, {url_sign}) => {
if (url_sign === "all") {
global.videoList = {}
return
}
if (url_sign) {
delete global.videoList[url_sign]
return
}
})
ipcMain.handle('invoke_window_restart', (event) => {
app.relaunch()
app.exit()
})
}
export function setWin(w, p) {
win = w
previewWin = p
}
}

View File

@@ -7,255 +7,235 @@ import * as urlTool from "url"
import {toSize} from "./utils"
// @ts-ignore
import {hexMD5} from '../../src/common/md5'
import pkg from '../../package.json'
const hoXy = require('hoxy')
const port = 8899
let videoList = {}
global.videoList = {}
if (process.platform === 'win32') {
process.env.OPENSSL_BIN = CONFIG.OPEN_SSL_BIN_PATH
process.env.OPENSSL_CONF = CONFIG.OPEN_SSL_CNF_PATH
}
// setTimeout to allow working in macOS
// in windows: H5ExtTransfer:ok
// in macOS: finderH5ExtTransfer:ok
const resObject = {
url: "",
url_sign: "",
cover_url: "",
file_format: "",
platform: "",
size: "",
type: "video/mp4",
type_str: 'video',
progress_bar: "",
save_path: "",
decode_key: "",
description: ""
}
const injection_script1 = `
setTimeout(() => {
let receiver_url = "https://res-downloader.666666.com";
const vv = hexMD5(pkg.version) + (CONFIG.IS_DEV ? Math.random() :"")
function send_response_if_is_video(response) {
if (response == undefined) return;
if (!response["err_msg"].includes("H5ExtTransfer:ok")) return;
let value = JSON.parse(response["jsapi_resp"]["resp_json"]);
if (value["object"] == undefined || value["object"]["object_desc"] == undefined || value["object"]["object_desc"]["media"].length == 0) {
return;
}
let media = value["object"]["object_desc"]["media"][0];
let description = value["object"]["object_desc"]["description"].trim();
let video_data = {
"decode_key": media["decode_key"],
"url": media["url"]+media["url_token"],
"size": media["file_size"],
"description": description,
"uploader": value["object"]["nickname"]
};
fetch(receiver_url, {
method: "POST",
mode: "no-cors",
body: JSON.stringify(video_data),
}).then((resp) => {
// alert(\`video data for \${video_data["description"]} sent!\`);
});
}
function wrapper(name,origin) {
return function() {
let cmdName = arguments[0];
if (arguments.length == 3) {
let original_callback = arguments[2];
arguments[2] = async function () {
if (arguments.length == 1) {
send_response_if_is_video(arguments[0]);
}
return await original_callback.apply(this, arguments);
}
} else {
}
let result = origin.apply(this,arguments);
return result;
}
}
window.WeixinJSBridge.invoke = wrapper("WeixinJSBridge.invoke", window.WeixinJSBridge.invoke);
window.wvds = true;
}, 200);`;
export async function startServer({
win,
setProxyErrorCallback = f => f,
}) {
export async function startServer({win, upstreamProxy, setProxyErrorCallback = f => f,}) {
return new Promise(async (resolve: any, reject) => {
const proxy = hoXy.createServer({
try {
const proxy = hoXy.createServer({
upstreamProxy: upstreamProxy,
certAuthority: {
key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
},
})
.listen(port, () => {
setProxy('127.0.0.1', port)
.then(() => {
// log.log("--------------setProxy success--------------")
resolve()
})
.catch((err) => {
// log.log("--------------setProxy error--------------")
// setProxyErrorCallback(data);
setProxyErrorCallback({});
reject('设置代理失败');
});
})
.on('error', err => {
log.log("--------------proxy err--------------", err)
});
proxy.intercept(
{
phase: 'request',
hostname: 'res-downloader.666666.com',
as: 'json',
},
(req, res) => {
// console.log('req.json: ', req.json)
res.string = 'ok'
res.statusCode = 200
let url_sign: string = hexMD5(req.json.url)
let urlInfo = urlTool.parse(req.json.url, true)
win?.webContents?.send?.('on_get_queue', {
url_sign: url_sign,
url: req.json.url,
down_url: req.json.url,
high_url: '',
platform: urlInfo.hostname,
size: toSize(req.json.size ?? 0),
type: "video/mp4",
type_str: 'video',
progress_bar: '',
save_path: '',
downing: false,
decode_key: req.json.decode_key,
description: req.json.description,
uploader: '',
.listen(port, () => {
setProxy('127.0.0.1', port)
.then((res) => {
resolve()
})
.catch((err) => {
setProxyErrorCallback(err)
reject('setting proxy err: ' + err.toString())
});
})
.on('error', err => {
setProxyErrorCallback(err)
reject('proxy service err: ' + err.toString())
})
},
);
proxy.intercept(
{
phase: 'response',
hostname: 'channels.weixin.qq.com',
as: 'string',
},
async (req, res) => {
// console.log('inject[channels.weixin.qq.com] req.url:', req.url);
if (req.url.includes('/web/pages/feed') || req.url.includes('/web/pages/home')) {
res.string = res.string.replace('</body>', '\n<script>' + injection_script1 + '</script>\n</body>');
res.statusCode = 200;
// console.log('inject[channels.weixin.qq.com]:', req.url, res.string.length);
}
},
);
proxy.intercept(
{
phase: 'response',
},
async (req, res) => {
// 拦截响应
let ctype = res?._data?.headers?.['content-type']
let url_sign: string = hexMD5(req.fullUrl())
let res_url = req.fullUrl()
let urlInfo = urlTool.parse(res_url, true)
switch (ctype) {
case "video/mp4":
if (videoList.hasOwnProperty(url_sign) === false) {
videoList[url_sign] = req.fullUrl()
let high_url = ''
let down_url = res_url
// console.log('down_url', down_url)
win?.webContents?.send?.('on_get_queue', {
url_sign: url_sign,
url: down_url,
down_url: down_url,
high_url: high_url,
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'video',
progress_bar: '',
save_path: '',
downing: false,
decode_key: '',
description: '',
uploader: '',
})
proxy.intercept(
{
phase: 'request',
hostname: 'res-downloader.666666.com',
as: 'json',
},
(req, res) => {
res.string = 'ok'
res.statusCode = 200
try {
if (req.json?.media?.length <= 0) {
return
}
break;
case "image/png":
case "image/webp":
case "image/svg+xml":
case "image/gif":
win?.webContents?.send?.('on_get_queue', {
const media = req.json?.media[0]
const url_sign: string = hexMD5(media.url)
if (!media?.decodeKey || global.videoList.hasOwnProperty(url_sign) === true) {
return
}
const urlInfo = urlTool.parse(media.url, true)
global.videoList[url_sign] = media.url
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url_sign: url_sign,
url: res_url,
down_url: res_url,
high_url: '',
url: media.url + media.urlToken,
cover_url: media.coverUrl,
file_format: media.spec.map((res)=> res.fileFormat).join('#'),
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'image',
progress_bar: '',
save_path: '',
downing: false,
decode_key: '',
description: '',
uploader: '',
})
break;
case "audio/mpeg":
win?.webContents?.send?.('on_get_queue', {
url_sign: url_sign,
url: res_url,
down_url: res_url,
high_url: '',
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'audio',
progress_bar: '',
save_path: '',
downing: false,
decode_key: '',
description: '',
uploader: '',
})
break;
case "application/vnd.apple.mpegurl":
win.webContents?.send?.('on_get_queue', {
url_sign: url_sign,
url: res_url,
down_url: res_url,
high_url: '',
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'm3u8',
progress_bar: '',
save_path: '',
downing: false,
decode_key: '',
description: '',
uploader: '',
})
break;
size: toSize(media.fileSize),
type: "video/mp4",
type_str: 'video',
decode_key: media.decodeKey,
description: req.json.description,
}))
} catch (e) {
log.log(e.toString())
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'channels.weixin.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.includes('/web/pages/feed') || req.url.includes('/web/pages/home')) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"')
res.statusCode = 200
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'res.wx.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.endsWith('.js?v=' + vv)) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"');
}
if (req.url.includes("web/web-finder/res/js/virtual_svg-icons-register.publish")) {
res.string = res.string.replace(/get\s*media\s*\(\)\s*\{/, `
get media(){
if(this.objectDesc){
fetch("https://res-downloader.666666.com", {
method: "POST",
mode: "no-cors",
body: JSON.stringify(this.objectDesc),
});
};
`)
}
}
);
},
)
proxy.intercept(
{
phase: 'response',
},
async (req, res) => {
try {
// 拦截响应
const ctype = res?._data?.headers?.['content-type']
const url_sign: string = hexMD5(req.fullUrl())
const res_url = req.fullUrl()
const urlInfo = urlTool.parse(res_url, true)
switch (ctype) {
case "video/mp4":
case "video/webm":
case "video/ogg":
case "video/x-msvideo":
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/x-flv":
case "video/3gpp":
case "video/x-matroska":
if (global.videoList.hasOwnProperty(url_sign) === false) {
global.videoList[url_sign] = res_url
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'video',
}))
}
break;
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/gif":
case "image/avif":
case "image/bmp":
case "image/tiff":
case "image/x-icon":
case "image/heic":
case "image/vnd.adobe.photoshop":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'image',
}))
break
case "audio/mpeg":
case "audio/wav":
case "audio/aiff":
case "audio/x-aiff":
case "audio/aac":
case "audio/ogg":
case "audio/flac":
case "audio/midi":
case "audio/x-midi":
case "audio/x-ms-wma":
case "audio/opus":
case "audio/webm":
case "audio/mp4":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'audio',
}))
break
case "application/vnd.apple.mpegurl":
case "application/x-mpegURL":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'm3u8',
}))
break
}
} catch (e) {
log.log(e.toString())
}
},
)
} catch (e) {
log.log("--------------proxy catch err--------------", e)
}
})
}
app.on('before-quit', async e => {
e.preventDefault()
try {
await closeProxy()
log.log("--------------closeProxy success--------------")
} catch (error) {
}
app.exit()
})
}

View File

@@ -22,7 +22,13 @@ export async function setProxy(host, port) {
if (error) {
reject(null)
} else {
resolve(network)
exec(`networksetup -setwebproxy "${network}" ${host} ${port}`, error => {
if (error) {
reject(null)
} else {
resolve(network)
}
});
}
});
});
@@ -63,7 +69,13 @@ export async function closeProxy() {
if (error) {
reject(null)
} else {
resolve(network)
exec(`networksetup -setwebproxystate "${network}" off`, error => {
if (error) {
reject(null)
} else {
resolve(network)
}
});
}
});
});
@@ -113,4 +125,4 @@ function getMacAvailableNetworks() {
}
})
})
}
}

View File

@@ -1,6 +1,6 @@
import fs from 'fs'
import {Transform } from 'stream'
import {getDecryptionArray} from '../wxjs/decrypt'
import {getDecryptionArray} from '../wxjs/decrypt.js'
const axios = require('axios')
function xorTransform(decryptionArray) {
@@ -25,13 +25,18 @@ function downloadFile(url, decodeKey, fullFileName, progressCallback) {
if (decodeKey) {
xorStream = xorTransform(getDecryptionArray(decodeKey));
}
return axios.get(url, {
let config = {
responseType: 'stream',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
},
}).then(({data, headers}) => {
}
if (url.includes("douyin")){
config.headers['Referer'] = url
}
return axios.get(url, config).then(({data, headers}) => {
let currentLen = 0
const totalLen = headers['content-length']
@@ -92,4 +97,64 @@ function toSize(size: number) {
return size + 'b'
}
export {downloadFile, toSize, decodeWxFile}
function suffix(type: string) {
switch (type) {
case "video/mp4":
case "video/webm":
case "video/ogg":
case "video/x-msvideo":
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/x-flv":
case "video/3gpp":
case "video/x-matroska":
return ".mp4";
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/gif":
case "image/avif":
case "image/bmp":
case "image/tiff":
case "image/x-icon":
case "image/heic":
case "image/vnd.adobe.photoshop":
return ".png";
case "audio/mpeg":
case "audio/wav":
case "audio/aiff":
case "audio/x-aiff":
case "audio/aac":
case "audio/ogg":
case "audio/flac":
case "audio/midi":
case "audio/x-midi":
case "audio/x-ms-wma":
case "audio/opus":
case "audio/webm":
case "audio/mp4":
return ".mp3";
case "application/vnd.apple.mpegurl":
case "application/x-mpegURL":
return ".m3u8";
}
return ""
}
function getCurrentDateTimeFormatted() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始所以要加1
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
export {downloadFile, toSize, decodeWxFile, suffix, getCurrentDateTimeFormatted}

View File

@@ -25,12 +25,6 @@ const safeDOM = {
},
}
/**
* https://tobiasahlin.com/spinkit
* https://connoratherton.com/loaders
* https://projects.lukehaas.me/css-loaders
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = `loaders-css__square-spin`
const styleContent = `

View File

View File

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

@@ -1,27 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsmAqn3hYd/YZcrfgqM1Q6xgHI50EBckbOkfCqTWS1yVFZjLF
bMehWb9xGFZJD21A5sxl4xelIWblhety+YTVa/mn2CEJh3je069oeULfXdzhhHyf
/ci0IloJhvX+2RJ+176uTKKcWhuOtNVs5VeFoHDoUcISnTqkaVyWeeLfafgrOW7w
N8ip128nuBx19ylIygb/DELmjKRRCSpx2vOw2JErTM8L5r0f4eWdqiwBOwu0NHWy
Svh9YG8B31UPga4I8FbFhybOP9cQNQPafOSfjwuZoi5CAtyJbwT7KyII9iMD74bZ
1mTx2xokmQ2TeiCSKSF8Mx9/8Gq+95mzvvIbRwIDAQABAoIBAHNt++caj9WBclJk
X4Oc6eJYuDX5o+LCk1YRngy12IJVYiWScWPFg8p6MouXOsw63Sb92mksofWNirYw
+UQzC5FGC7G3H12FgFzoQ+lEtxscluuPYlFukfMw5L1rbzG14FNo145MJHXDI4Qu
ILwA+T4sEorl1fndOwvbmJzjjcQaeRNz7/R9e6QTOlZ2+IEMKnHSBXXGJbDj6mPN
+f1/ec6nVENdxazgRCi0xfinyft4Ipst93Eb+wGcpk+J43aF+0leWQCdl6Y9U1Lz
zpv5H5XOQdwpX+dpuioRp73zwPwIialq+hTUN28Bn9U1jW2tjxUl/vgIpjy1s94a
UipRwSECgYEA57vYB+wGnxQxY9IPpr9H/y3HciIwCnuOEsWBzjYe8sIqBif2tEpO
OgDZZMQY7+JJrDQbDRs442TuRjKhJ5hiW+MyoiFWaYkBBoNVM8RBTkIjHfrh+uB2
XT15FbEyyxo3n9QY610ZJFRnW4Uf5V0osjOqqUgQRrVXvamk6NQH6FkCgYEAxQ3v
jFYPL3EkZe1br6X0RM42ykGv5Di5Q6NnjpSPcyn9a2obA1cZuCd5S1lhrkuZGsdI
iFapeL+7vpts9gu9/ii9y+CgEKplOMmm0ZrChBKAcXMZvdDKV3y5SmTMZPas4X5i
hqNqatx9/J93sMYWc0CuoosDEJYKtSz8GE+1rJ8CgYAmp5rdl21zU7b5Y6zgr7+e
vVArpbBFz15fmzqP309CR0kjRb9NS6fI3SNmP5+5RBHt+7MXeJcAt3FXnFJtfGnL
0hY8HTuA1y2onHe17uLF3xpkgdj4NEEKRJrSF4DViEYHDyYo/JqZCMtE5OvxIp0L
PLsXCcJNSSqdpJKxk8zN4QKBgEuoxSAh7uStUWddUkXHt1kvwDO6MtmyuddxhxJk
kguKxMWYUNTgfXyKk3TN1caBOkDg4UWP2LQHEgPmU1jJO2K5q9362hpsAj9ilY2H
GUZygCSPKAQMhZQ/zDj3KM9fMxPFXfkKB5MOI8V6SQ9zjy0jWaoJK90TbvsPUZ/Y
Aw5LAoGADifZwCHPiXhTfJjOom2uBgXmL03yTXcCw4EDIX3ZR0sP6ACPQq4T4jxZ
UJLXLjOb2pzCq0c5+k0cG6ahYINq4tGOo+vQ9fDvhKg0nlf1FrzxSd7S12o+un2q
+U+dBllYIDlRMgMhXu9CxFDjUsCwPRmsBvmVZiH4XSs6QVnfn90=
-----END RSA PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcDt23t6ioBoHG
/Y2mOjxntWQa9dP3eNl+mAC6425DlEtyc6czNAIKuuM9wt+wAwDQAgrd5RaxdcpJ
H1JlMkEtBFkIkdn0Ag98D7nwlVA9ON3xQi5Bkl+sN/oWOE8lOwvNyNNT6ZPu3qUS
KY8SXZVY315daqz2eRWsm2otqjqbWGhh9c7FGHr3r9aAG08dyaO6OvK14GJIhNV+
UOPH5hDMMaxurDt8znaSUw93b7D++aEninGro/s2LY4G91dgM8i4t88UWobXpqs5
GMGTI0InLX2I66HkteH4RRfXXC9svA2CxN3yP294FIP7gdRQ1CGJeJcluzsjtx0V
i2G9vrT1AgMBAAECggEAF0obfQ4a82183qqHC0iui+tOpOvPeyl3G0bLDPx09wIC
2iITV//xF2GgGzE8q0wmEd2leMZ+GFn3BrYh6kPfUfxbz+RfxMtTCDZB34xt6YzT
MG1op9ft+DQUa7WZ6r7NCQJwGzllRqqZncp4MeFlpPo+6nQXyh4WhSYNnredbENE
uPZ63Kme4RZfMvtVso+XgAQM3oDih0onv1YitmNQpL9rRzlthTfybAT4737DBINq
zsmBNE6QIsXnSKpzo11OtDgof2QM9ac6eAXf73oTpDxfodwCotILytKn+8WYvlR+
T15uuknb4M3XI1FPVolkF4qtK5SLAAbVzV4DsCmuIQKBgQD6bTKKbL2huvU6dEKx
bgS079LfQUxxOTClgwkhVsMxRtvcPBnHYMAsPK4mnMhEh9x+TF6wxMx0pmhQluPI
ZULNBj/qdoiBL0RwVLA+9jgE0NeWB3XXFDsEavQBr9Q8CC0uzrsgsxFcvHpqqs2Q
RtngxRWtJP06D6mKC23s4YjDHwKBgQDg9KUCFqOmWcRXyeg9gYMC4jFFQw4lUQBd
sYpqSMHDw1b+T1W/dCPbwbxZL/+d8y930BYy9QYDtQwHdLyXCH0pHM7S6rfgr5xk
2Szd8xBUIqmeV/zcR00mTeQHJ1M50VHfclAVgZgkpWSoLwbX+bXyx/mfqLAtynZ5
yU9RfrT5awKBgQC0uJ8TlFvZXjFgyMvkfY/5/2R/ZwFCaFI573FkVNeyNP+vVNQJ
tUGZ6wSGqvg/tIgjwPtIuA0QVZLMLcgeMy1dBhiUHIxwJetO4V77YPaWSxx5kdKx
r1DT5FdI7FnOJNxufhQ/CdsKwJ3bYn3Mk8TiV3hIJnx0LR9dltfybeQjYwKBgDOY
6aApATBOtrJMJXC2HA61QwfX8Y6tnZ/f8RefyJHWZEXAfLKFORRWw5TRZZgdB247
1Furx81h4Xh0Vi1uTQb5DJdkLvjiTsTy60+dSMmDidQ/6ke8Mv3uL7dUVcqVMGpI
FgZYy0TcitHot3EiXZFqPN9aGc7m+XXFruPKZEgxAoGBAMA96jsow7CzulU+GRW8
Njg4zWuAEVErgPoNBcOXAVWLCTU/qGIEMNpZL6Ok34kf13pJDMjQ8eDuQHu5CSqf
0ul5Zy85fwfVq2IvNAyYT8eflQprTejFw22CHhfPBfADVW9ro8dK/Jw+J/31Vh7V
ILKEQKmPPzKs7kp/7Nz+2cT3
-----END PRIVATE KEY-----

View File

@@ -1,27 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsmAqn3hYd/YZcrfgqM1Q6xgHI50EBckbOkfCqTWS1yVFZjLF
bMehWb9xGFZJD21A5sxl4xelIWblhety+YTVa/mn2CEJh3je069oeULfXdzhhHyf
/ci0IloJhvX+2RJ+176uTKKcWhuOtNVs5VeFoHDoUcISnTqkaVyWeeLfafgrOW7w
N8ip128nuBx19ylIygb/DELmjKRRCSpx2vOw2JErTM8L5r0f4eWdqiwBOwu0NHWy
Svh9YG8B31UPga4I8FbFhybOP9cQNQPafOSfjwuZoi5CAtyJbwT7KyII9iMD74bZ
1mTx2xokmQ2TeiCSKSF8Mx9/8Gq+95mzvvIbRwIDAQABAoIBAHNt++caj9WBclJk
X4Oc6eJYuDX5o+LCk1YRngy12IJVYiWScWPFg8p6MouXOsw63Sb92mksofWNirYw
+UQzC5FGC7G3H12FgFzoQ+lEtxscluuPYlFukfMw5L1rbzG14FNo145MJHXDI4Qu
ILwA+T4sEorl1fndOwvbmJzjjcQaeRNz7/R9e6QTOlZ2+IEMKnHSBXXGJbDj6mPN
+f1/ec6nVENdxazgRCi0xfinyft4Ipst93Eb+wGcpk+J43aF+0leWQCdl6Y9U1Lz
zpv5H5XOQdwpX+dpuioRp73zwPwIialq+hTUN28Bn9U1jW2tjxUl/vgIpjy1s94a
UipRwSECgYEA57vYB+wGnxQxY9IPpr9H/y3HciIwCnuOEsWBzjYe8sIqBif2tEpO
OgDZZMQY7+JJrDQbDRs442TuRjKhJ5hiW+MyoiFWaYkBBoNVM8RBTkIjHfrh+uB2
XT15FbEyyxo3n9QY610ZJFRnW4Uf5V0osjOqqUgQRrVXvamk6NQH6FkCgYEAxQ3v
jFYPL3EkZe1br6X0RM42ykGv5Di5Q6NnjpSPcyn9a2obA1cZuCd5S1lhrkuZGsdI
iFapeL+7vpts9gu9/ii9y+CgEKplOMmm0ZrChBKAcXMZvdDKV3y5SmTMZPas4X5i
hqNqatx9/J93sMYWc0CuoosDEJYKtSz8GE+1rJ8CgYAmp5rdl21zU7b5Y6zgr7+e
vVArpbBFz15fmzqP309CR0kjRb9NS6fI3SNmP5+5RBHt+7MXeJcAt3FXnFJtfGnL
0hY8HTuA1y2onHe17uLF3xpkgdj4NEEKRJrSF4DViEYHDyYo/JqZCMtE5OvxIp0L
PLsXCcJNSSqdpJKxk8zN4QKBgEuoxSAh7uStUWddUkXHt1kvwDO6MtmyuddxhxJk
kguKxMWYUNTgfXyKk3TN1caBOkDg4UWP2LQHEgPmU1jJO2K5q9362hpsAj9ilY2H
GUZygCSPKAQMhZQ/zDj3KM9fMxPFXfkKB5MOI8V6SQ9zjy0jWaoJK90TbvsPUZ/Y
Aw5LAoGADifZwCHPiXhTfJjOom2uBgXmL03yTXcCw4EDIX3ZR0sP6ACPQq4T4jxZ
UJLXLjOb2pzCq0c5+k0cG6ahYINq4tGOo+vQ9fDvhKg0nlf1FrzxSd7S12o+un2q
+U+dBllYIDlRMgMhXu9CxFDjUsCwPRmsBvmVZiH4XSs6QVnfn90=
-----END RSA PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcDt23t6ioBoHG
/Y2mOjxntWQa9dP3eNl+mAC6425DlEtyc6czNAIKuuM9wt+wAwDQAgrd5RaxdcpJ
H1JlMkEtBFkIkdn0Ag98D7nwlVA9ON3xQi5Bkl+sN/oWOE8lOwvNyNNT6ZPu3qUS
KY8SXZVY315daqz2eRWsm2otqjqbWGhh9c7FGHr3r9aAG08dyaO6OvK14GJIhNV+
UOPH5hDMMaxurDt8znaSUw93b7D++aEninGro/s2LY4G91dgM8i4t88UWobXpqs5
GMGTI0InLX2I66HkteH4RRfXXC9svA2CxN3yP294FIP7gdRQ1CGJeJcluzsjtx0V
i2G9vrT1AgMBAAECggEAF0obfQ4a82183qqHC0iui+tOpOvPeyl3G0bLDPx09wIC
2iITV//xF2GgGzE8q0wmEd2leMZ+GFn3BrYh6kPfUfxbz+RfxMtTCDZB34xt6YzT
MG1op9ft+DQUa7WZ6r7NCQJwGzllRqqZncp4MeFlpPo+6nQXyh4WhSYNnredbENE
uPZ63Kme4RZfMvtVso+XgAQM3oDih0onv1YitmNQpL9rRzlthTfybAT4737DBINq
zsmBNE6QIsXnSKpzo11OtDgof2QM9ac6eAXf73oTpDxfodwCotILytKn+8WYvlR+
T15uuknb4M3XI1FPVolkF4qtK5SLAAbVzV4DsCmuIQKBgQD6bTKKbL2huvU6dEKx
bgS079LfQUxxOTClgwkhVsMxRtvcPBnHYMAsPK4mnMhEh9x+TF6wxMx0pmhQluPI
ZULNBj/qdoiBL0RwVLA+9jgE0NeWB3XXFDsEavQBr9Q8CC0uzrsgsxFcvHpqqs2Q
RtngxRWtJP06D6mKC23s4YjDHwKBgQDg9KUCFqOmWcRXyeg9gYMC4jFFQw4lUQBd
sYpqSMHDw1b+T1W/dCPbwbxZL/+d8y930BYy9QYDtQwHdLyXCH0pHM7S6rfgr5xk
2Szd8xBUIqmeV/zcR00mTeQHJ1M50VHfclAVgZgkpWSoLwbX+bXyx/mfqLAtynZ5
yU9RfrT5awKBgQC0uJ8TlFvZXjFgyMvkfY/5/2R/ZwFCaFI573FkVNeyNP+vVNQJ
tUGZ6wSGqvg/tIgjwPtIuA0QVZLMLcgeMy1dBhiUHIxwJetO4V77YPaWSxx5kdKx
r1DT5FdI7FnOJNxufhQ/CdsKwJ3bYn3Mk8TiV3hIJnx0LR9dltfybeQjYwKBgDOY
6aApATBOtrJMJXC2HA61QwfX8Y6tnZ/f8RefyJHWZEXAfLKFORRWw5TRZZgdB247
1Furx81h4Xh0Vi1uTQb5DJdkLvjiTsTy60+dSMmDidQ/6ke8Mv3uL7dUVcqVMGpI
FgZYy0TcitHot3EiXZFqPN9aGc7m+XXFruPKZEgxAoGBAMA96jsow7CzulU+GRW8
Njg4zWuAEVErgPoNBcOXAVWLCTU/qGIEMNpZL6Ok34kf13pJDMjQ8eDuQHu5CSqf
0ul5Zy85fwfVq2IvNAyYT8eflQprTejFw22CHhfPBfADVW9ro8dK/Jw+J/31Vh7V
ILKEQKmPPzKs7kp/7Nz+2cT3
-----END PRIVATE KEY-----

View File

@@ -1,17 +1,23 @@
-----BEGIN CERTIFICATE-----
MIICuDCCAaACCQC7PQmrxgWOlTANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJs
ZWNlcGluLTIwMjItMDUtMTkwIBcNMjIwNTE5MTI1NjA0WhgPMzAyMTA5MTkxMjU2
MDRaMB0xGzAZBgNVBAMMEmxlY2VwaW4tMjAyMi0wNS0xOTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALJgKp94WHf2GXK34KjNUOsYByOdBAXJGzpHwqk1
ktclRWYyxWzHoVm/cRhWSQ9tQObMZeMXpSFm5YXrcvmE1Wv5p9ghCYd43tOvaHlC
313c4YR8n/3ItCJaCYb1/tkSfte+rkyinFobjrTVbOVXhaBw6FHCEp06pGlclnni
32n4Kzlu8DfIqddvJ7gcdfcpSMoG/wxC5oykUQkqcdrzsNiRK0zPC+a9H+Hlnaos
ATsLtDR1skr4fWBvAd9VD4GuCPBWxYcmzj/XEDUD2nzkn48LmaIuQgLciW8E+ysi
CPYjA++G2dZk8dsaJJkNk3ogkikhfDMff/BqvveZs77yG0cCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEADymHk+wLJAdv3p+4hHo57VLaBtwVYXc5oRUbUzgMYTTtPWIs
xuILEqXftMspt6PzdEt0V1WeCWNyypsAbur/CKpAOoVjBDPIo09TiYnYIn9xt5wQ
AmR5kVEZheuazcvzW3C9NAY1T6QDmxNvFCiCXRbtklOg2HqFDZX+pkj8CylQ9TDk
rroUg17b/FD1ds1uyPXzucEWfxqkOaujvsCnzrbFs9luB5VfM+QzLU+l9QRN9Tmj
z7CpGuP6vKvhXJLUjXkZ0q5JyL5wEAe6Ttbu+c/8HhPFKQsW6q/lQSDo0v0LGDrd
ikjWXhSrVjd8+qTTVgia/UNqv/wi+bkWnVdRzQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUFAnC6268dp/z1DR9E1UepiWgWzkwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25ncWluZzESMBAGA1UEBwwJ
Q2hvbmdxaW5nMQ4wDAYDVQQKDAVnb3dhczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVu
dDERMA8GA1UEAwwIZ293YXMuY24wIBcNMjQwMjE4MDIwOTI2WhgPMjEyNDAxMjUw
MjA5MjZaMHAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlDaG9uZ3FpbmcxEjAQBgNV
BAcMCUNob25ncWluZzEOMAwGA1UECgwFZ293YXMxFjAUBgNVBAsMDUlUIERlcGFy
dG1lbnQxETAPBgNVBAMMCGdvd2FzLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA3A7dt7eoqAaBxv2Npjo8Z7VkGvXT93jZfpgAuuNuQ5RLcnOnMzQC
CrrjPcLfsAMA0AIK3eUWsXXKSR9SZTJBLQRZCJHZ9AIPfA+58JVQPTjd8UIuQZJf
rDf6FjhPJTsLzcjTU+mT7t6lEimPEl2VWN9eXWqs9nkVrJtqLao6m1hoYfXOxRh6
96/WgBtPHcmjujryteBiSITVflDjx+YQzDGsbqw7fM52klMPd2+w/vmhJ4pxq6P7
Ni2OBvdXYDPIuLfPFFqG16arORjBkyNCJy19iOuh5LXh+EUX11wvbLwNgsTd8j9v
eBSD+4HUUNQhiXiXJbs7I7cdFYthvb609QIDAQABo1MwUTAdBgNVHQ4EFgQUdI8p
aY1A47rWCRvQKSTRCCk6FoMwHwYDVR0jBBgwFoAUdI8paY1A47rWCRvQKSTRCCk6
FoMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArMCAfqidgXL7
cW5TAZTCqnUeKzbbqMJgk6iFsma8scMRsUXz9ZhF0UVf98376KvoJpy4vd81afbi
TehQ8wVBuKTtkHeh/MkXMWC/FU4HqSjtvxpic2+Or5dMjIrfa5VYPgzfqNaBIUh4
InD5lo8b/n5V+jdwX7RX9VYAKug6QZlCg5YSKIvgNRChb36JmrGcvsp5R0Vejnii
e3oowvgwikqm6XR6BEcRpPkztqcKST7jPFGHiXWsAqiibc+/plMW9qebhfMXEGhQ
5yVNeSxX2zqasZvP/fRy+3I5iVilxtKvJuVpPZ0UZzGS0CJ/lF67ntibktiPa3sR
D8HixYbEDg==
-----END CERTIFICATE-----

View File

@@ -1,17 +1,23 @@
-----BEGIN CERTIFICATE-----
MIICuDCCAaACCQC7PQmrxgWOlTANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJs
ZWNlcGluLTIwMjItMDUtMTkwIBcNMjIwNTE5MTI1NjA0WhgPMzAyMTA5MTkxMjU2
MDRaMB0xGzAZBgNVBAMMEmxlY2VwaW4tMjAyMi0wNS0xOTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALJgKp94WHf2GXK34KjNUOsYByOdBAXJGzpHwqk1
ktclRWYyxWzHoVm/cRhWSQ9tQObMZeMXpSFm5YXrcvmE1Wv5p9ghCYd43tOvaHlC
313c4YR8n/3ItCJaCYb1/tkSfte+rkyinFobjrTVbOVXhaBw6FHCEp06pGlclnni
32n4Kzlu8DfIqddvJ7gcdfcpSMoG/wxC5oykUQkqcdrzsNiRK0zPC+a9H+Hlnaos
ATsLtDR1skr4fWBvAd9VD4GuCPBWxYcmzj/XEDUD2nzkn48LmaIuQgLciW8E+ysi
CPYjA++G2dZk8dsaJJkNk3ogkikhfDMff/BqvveZs77yG0cCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEADymHk+wLJAdv3p+4hHo57VLaBtwVYXc5oRUbUzgMYTTtPWIs
xuILEqXftMspt6PzdEt0V1WeCWNyypsAbur/CKpAOoVjBDPIo09TiYnYIn9xt5wQ
AmR5kVEZheuazcvzW3C9NAY1T6QDmxNvFCiCXRbtklOg2HqFDZX+pkj8CylQ9TDk
rroUg17b/FD1ds1uyPXzucEWfxqkOaujvsCnzrbFs9luB5VfM+QzLU+l9QRN9Tmj
z7CpGuP6vKvhXJLUjXkZ0q5JyL5wEAe6Ttbu+c/8HhPFKQsW6q/lQSDo0v0LGDrd
ikjWXhSrVjd8+qTTVgia/UNqv/wi+bkWnVdRzQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIUFAnC6268dp/z1DR9E1UepiWgWzkwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25ncWluZzESMBAGA1UEBwwJ
Q2hvbmdxaW5nMQ4wDAYDVQQKDAVnb3dhczEWMBQGA1UECwwNSVQgRGVwYXJ0bWVu
dDERMA8GA1UEAwwIZ293YXMuY24wIBcNMjQwMjE4MDIwOTI2WhgPMjEyNDAxMjUw
MjA5MjZaMHAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlDaG9uZ3FpbmcxEjAQBgNV
BAcMCUNob25ncWluZzEOMAwGA1UECgwFZ293YXMxFjAUBgNVBAsMDUlUIERlcGFy
dG1lbnQxETAPBgNVBAMMCGdvd2FzLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA3A7dt7eoqAaBxv2Npjo8Z7VkGvXT93jZfpgAuuNuQ5RLcnOnMzQC
CrrjPcLfsAMA0AIK3eUWsXXKSR9SZTJBLQRZCJHZ9AIPfA+58JVQPTjd8UIuQZJf
rDf6FjhPJTsLzcjTU+mT7t6lEimPEl2VWN9eXWqs9nkVrJtqLao6m1hoYfXOxRh6
96/WgBtPHcmjujryteBiSITVflDjx+YQzDGsbqw7fM52klMPd2+w/vmhJ4pxq6P7
Ni2OBvdXYDPIuLfPFFqG16arORjBkyNCJy19iOuh5LXh+EUX11wvbLwNgsTd8j9v
eBSD+4HUUNQhiXiXJbs7I7cdFYthvb609QIDAQABo1MwUTAdBgNVHQ4EFgQUdI8p
aY1A47rWCRvQKSTRCCk6FoMwHwYDVR0jBBgwFoAUdI8paY1A47rWCRvQKSTRCCk6
FoMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArMCAfqidgXL7
cW5TAZTCqnUeKzbbqMJgk6iFsma8scMRsUXz9ZhF0UVf98376KvoJpy4vd81afbi
TehQ8wVBuKTtkHeh/MkXMWC/FU4HqSjtvxpic2+Or5dMjIrfa5VYPgzfqNaBIUh4
InD5lo8b/n5V+jdwX7RX9VYAKug6QZlCg5YSKIvgNRChb36JmrGcvsp5R0Vejnii
e3oowvgwikqm6XR6BEcRpPkztqcKST7jPFGHiXWsAqiibc+/plMW9qebhfMXEGhQ
5yVNeSxX2zqasZvP/fRy+3I5iVilxtKvJuVpPZ0UZzGS0CJ/lF67ntibktiPa3sR
D8HixYbEDg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,25 @@
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=3
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
check-certificate=false

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,25 @@
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=3
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
check-certificate=false

Binary file not shown.

View File

BIN
electron/res/win/w_c.exe Normal file

Binary file not shown.

616
override/hoxy/lib/cycle.js Normal file
View File

@@ -0,0 +1,616 @@
/*
* Copyright (c) 2015 by Greg Reimer <gregreimer@gmail.com>
* MIT License. See mit-license.txt for more info.
*/
'use strict';
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _get = require('babel-runtime/helpers/get')['default'];
var _inherits = require('babel-runtime/helpers/inherits')['default'];
var _Promise = require('babel-runtime/core-js/promise')['default'];
var _regeneratorRuntime = require('babel-runtime/regenerator')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _request = require('./request');
var _request2 = _interopRequireDefault(_request);
var _response = require('./response');
var _response2 = _interopRequireDefault(_response);
var _streams = require('./streams');
var _streams2 = _interopRequireDefault(_streams);
var _await = require('await');
var _await2 = _interopRequireDefault(_await);
var _mkdirp = require('mkdirp');
var _mkdirp2 = _interopRequireDefault(_mkdirp);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _nodeStatic = require('node-static');
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _https = require('https');
var _https2 = _interopRequireDefault(_https);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _util = require('util');
var _util2 = _interopRequireDefault(_util);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _zlib = require('zlib');
var _zlib2 = _interopRequireDefault(_zlib);
var _events = require('events');
var _co = require('co');
var _co2 = _interopRequireDefault(_co);
var _uglyAdapter = require('ugly-adapter');
var _uglyAdapter2 = _interopRequireDefault(_uglyAdapter);
var _wait = require('./wait');
var _wait2 = _interopRequireDefault(_wait);
var _task = require('./task');
var _task2 = _interopRequireDefault(_task);
var _urlPath = require('./url-path');
const url = require("url");
var _urlPath2 = _interopRequireDefault(_urlPath);
var staticServer = (function () {
var getStatic = (function () {
var statics = {};
return function (docroot) {
var stat = statics[docroot];
if (!stat) {
stat = statics[docroot] = new _nodeStatic.Server(docroot);
}
return stat;
};
})();
// Start up the server and serve out of various docroots.
var server = _http2['default'].createServer(function (req, resp) {
var docroot = req.headers['x-hoxy-static-docroot'];
var pDocroot = new _urlPath2['default'](docroot);
var stat = getStatic(pDocroot.toSystemPath());
stat.serve(req, resp);
}).listen(0, 'localhost');
return server;
})();
var httpsOverHttpAgent, httpsOverHttpsAgent, httpOverHttpsAgent;
var tunnelAgent = require('tunnel-agent');
var ptGetTunnelAgent = function (requestIsSSL, externalProxyUrl) {
var urlObject = url.parse(externalProxyUrl);
var protocol = urlObject.protocol || 'http:';
var port = urlObject.port;
if (!port) {
port = protocol === 'http:' ? 80 : 443;
}
var hostname = urlObject.hostname || 'localhost';
if (requestIsSSL) {
if (protocol === 'http:') {
if (!httpsOverHttpAgent) {
httpsOverHttpAgent = tunnelAgent.httpsOverHttp({
proxy: {
host: hostname,
port: port
}
});
}
return httpsOverHttpAgent;
} else {
if (!httpsOverHttpsAgent) {
httpsOverHttpsAgent = tunnelAgent.httpsOverHttps({
proxy: {
host: hostname,
port: port
}
});
}
return httpsOverHttpsAgent;
}
} else {
if (protocol === 'http:') {
// if (!httpOverHttpAgent) {
// httpOverHttpAgent = tunnelAgent.httpOverHttp({
// proxy: {
// host: hostname,
// port: port
// }
// });
// }
return false;
} else {
if (!httpOverHttpsAgent) {
httpOverHttpsAgent = tunnelAgent.httpOverHttps({
proxy: {
host: hostname,
port: port
}
});
}
return httpOverHttpsAgent;
}
}
}
var ProvisionableRequest = (function () {
function ProvisionableRequest(opts) {
_classCallCheck(this, ProvisionableRequest);
this._respProm = (0, _task2['default'])();
var h = /https/i.test(opts.protocol) ? _https2['default'] : _http2['default'];
if (opts.proxy) {
// var proxyInfo = _url2['default'].parse(opts.proxy),
// proxyPort = proxyInfo.port,
// proxyHostname = proxyInfo.hostname,
// proxyPath = 'http://' + opts.hostname + (opts.port ? ':' + opts.port : '') + opts.path;
// opts.hostname = proxyHostname;
// opts.port = proxyPort;
// opts.path = proxyPath;
opts.agent = ptGetTunnelAgent(/https/i.test(opts.protocol), opts.proxy);
// console.log('opts.agent', opts.proxy)
opts.proxy = ''
}
this._writable = h.request(opts, this._respProm.resolve);
this._writable.on('error', this._respProm.reject);
}
/*
* This check() function made me scratch my head when I came back to
* it months later. It simply does too many things. It still isn't perfect,
* but hopefully now this beast is slightly easier to follow. It returns
* a promise on a boolean indicating whether or not the passed file was
* created. IF the strategy is NOT 'mirror' it resolves false since 'mirror'
* is the only strategy that creates files. Otherwise if the file exists
* it resolves false. Otherwise it has a side effect of creating the file
* by requesting out to the remote server and writing the result to the
* file, then resolves true.
*/
_createClass(ProvisionableRequest, [{
key: 'send',
value: function send(readable) {
var _this = this;
return new _Promise(function (resolve, reject) {
if (!readable || typeof readable === 'string') {
_this._writable.end(readable || '', resolve);
} else {
readable.on('error', reject);
readable.on('end', resolve);
readable.pipe(_this._writable);
}
});
}
}, {
key: 'receive',
value: function receive() {
return this._respProm;
}
}]);
return ProvisionableRequest;
})();
function check(strategy, file, req, upstreamProxy) {
var parsed = _url2['default'].parse(file);
file = parsed.pathname; // stripped of query string.
return (0, _co2['default'])(_regeneratorRuntime.mark(function callee$1$0() {
var provReq, mirrResp, writeToFile, gunzip;
return _regeneratorRuntime.wrap(function callee$1$0$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
if (!(strategy !== 'mirror')) {
context$2$0.next = 2;
break;
}
return context$2$0.abrupt('return', false);
case 2:
context$2$0.prev = 2;
context$2$0.next = 5;
return (0, _uglyAdapter2['default'])(_fs2['default'].stat, file);
case 5:
return context$2$0.abrupt('return', false);
case 8:
context$2$0.prev = 8;
context$2$0.t0 = context$2$0['catch'](2);
case 10:
context$2$0.next = 12;
return (0, _uglyAdapter2['default'])(_mkdirp2['default'], _path2['default'].dirname(file));
case 12:
provReq = new ProvisionableRequest({
protocol: req.protocol,
proxy: upstreamProxy,
method: 'GET',
hostname: req.hostname,
port: req.port,
path: req.url
});
provReq.send();
context$2$0.next = 16;
return provReq.receive();
case 16:
mirrResp = context$2$0.sent;
if (!(mirrResp.statusCode !== 200)) {
context$2$0.next = 19;
break;
}
throw new Error('mirroring failed: ' + req.fullUrl() + ' => ' + mirrResp.statusCode);
case 19:
writeToFile = _fs2['default'].createWriteStream(file);
if (mirrResp.headers['content-encoding'] === 'gzip') {
gunzip = _zlib2['default'].createGunzip();
mirrResp = mirrResp.pipe(gunzip);
}
context$2$0.next = 23;
return new _Promise(function (resolve, reject) {
mirrResp.pipe(writeToFile);
mirrResp.on('end', resolve);
writeToFile.on('error', reject);
});
case 23:
case 'end':
return context$2$0.stop();
}
}, callee$1$0, this, [[2, 8]]);
}));
}
// ---------------------------
var Cycle = (function (_EventEmitter) {
_inherits(Cycle, _EventEmitter);
function Cycle(proxy) {
var _this2 = this;
_classCallCheck(this, Cycle);
_get(Object.getPrototypeOf(Cycle.prototype), 'constructor', this).call(this);
this._proxy = proxy;
this._request = new _request2['default']();
this._response = new _response2['default']();
this._request.on('log', function (log) {
return _this2.emit('log', log);
});
this._response.on('log', function (log) {
return _this2.emit('log', log);
});
}
_createClass(Cycle, [{
key: 'data',
value: function data(name, val) {
if (!this._userData) {
this._userData = {};
}
if (arguments.length === 2) {
this._userData[name] = val;
}
return this._userData[name];
}
}, {
key: 'serve',
value: function serve(opts) {
return _co2['default'].call(this, _regeneratorRuntime.mark(function callee$2$0() {
var req, resp, _opts, docroot, path, strategy, headers, pDocroot, pPath, pFullPath, fullSysPath,
created, staticResp, code, useResponse, isError, message;
return _regeneratorRuntime.wrap(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
req = this._request;
resp = this._response;
if (typeof opts === 'string') {
opts = {path: opts};
}
opts = _lodash2['default'].extend({
docroot: _path2['default'].sep,
path: _url2['default'].parse(req.url).pathname,
strategy: 'replace'
}, opts);
_opts = opts;
docroot = _opts.docroot;
path = _opts.path;
strategy = _opts.strategy;
headers = _lodash2['default'].extend({
'x-hoxy-static-docroot': docroot
}, req.headers);
delete headers['if-none-match'];
delete headers['if-modified-since'];
// Now call the static file service.
pDocroot = new _urlPath2['default'](docroot), pPath = new _urlPath2['default'](path), pFullPath = pPath.rootTo(pDocroot), fullSysPath = pFullPath.toSystemPath();
context$3$0.next = 14;
return check(strategy, fullSysPath, req, this._proxy._upstreamProxy);
case 14:
created = context$3$0.sent;
if (created) {
this.emit('log', {
level: 'info',
message: 'copied ' + req.fullUrl() + ' to ' + fullSysPath
});
}
context$3$0.next = 18;
return new _Promise(function (resolve, reject) {
var addr = staticServer.address();
_http2['default'].get({
hostname: addr.address,
port: addr.port,
headers: headers,
path: pPath.toUrlPath()
}, resolve).on('error', reject);
});
case 18:
staticResp = context$3$0.sent;
code = staticResp.statusCode, useResponse = undefined, isError = undefined;
if (/^2\d\d$/.test(code)) {
useResponse = true;
} else if (/^4\d\d$/.test(code)) {
if (strategy === 'replace') {
useResponse = true;
} else if (strategy === 'mirror') {
isError = true;
}
} else {
isError = true; // nope
}
if (!isError) {
context$3$0.next = 26;
break;
}
message = _util2['default'].format('Failed to serve static file: %s => %s. Static server returned %d. Strategy: %s', req.fullUrl(), fullSysPath, staticResp.statusCode, strategy);
throw new Error(message);
case 26:
if (useResponse) {
resp._setHttpSource(staticResp);
}
case 27:
case 'end':
return context$3$0.stop();
}
}, callee$2$0, this);
}));
}
}, {
key: '_setPhase',
value: function _setPhase(phase) {
this._phase = this._request.phase = this._response.phase = phase;
}
/*
* This returns a promise on a partially fulfilled request
* (an instance of class ProvisionableRequest). At the time
* the promise is fulfilled, the request is in a state where
* it's been fully piped out, but nothing received. It's up
* to the caller of this function to call receive() on it, thus
* getting a promise on the serverResponse object. That enables
* hoxy to implement the 'request-sent' phase.
*/
}, {
key: '_sendToServer',
value: function _sendToServer() {
var req = this._request._finalize(),
resp = this._response,
upstreamProxy = this._proxy._upstreamProxy,
source = req._source,
pSlow = this._proxy._slow || {},
rSlow = req.slow() || {},
latency = rSlow.latency || 0;
if (resp._populated) {
return _Promise.resolve(undefined);
}
return _co2['default'].call(this, _regeneratorRuntime.mark(function callee$2$0() {
var provisionableReq, brake, groupedBrake;
return _regeneratorRuntime.wrap(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
provisionableReq = new ProvisionableRequest({
protocol: req.protocol,
proxy: upstreamProxy,
hostname: req.hostname,
port: req.port || req._getDefaultPort(),
method: req.method,
path: req.url,
headers: req.headers
});
if (!(latency > 0)) {
context$3$0.next = 4;
break;
}
context$3$0.next = 4;
return (0, _wait2['default'])(latency);
case 4:
if (rSlow.rate > 0) {
brake = _streams2['default'].brake(rSlow.rate);
source = source.pipe(brake);
}
if (pSlow.rate) {
groupedBrake = pSlow.rate.throttle();
source = source.pipe(groupedBrake);
}
if (pSlow.up) {
groupedBrake = pSlow.up.throttle();
source = source.pipe(groupedBrake);
}
req._tees().forEach(function (writable) {
return source.pipe(writable);
});
context$3$0.next = 10;
return provisionableReq.send(source);
case 10:
return context$3$0.abrupt('return', provisionableReq);
case 11:
case 'end':
return context$3$0.stop();
}
}, callee$2$0, this);
}));
}
}, {
key: '_sendToClient',
value: function _sendToClient(outResp) {
var resp = this._response._finalize(),
source = resp._source,
rSlow = resp.slow() || {},
pSlow = this._proxy._slow || {},
rLatency = rSlow.latency || 0,
pLatency = pSlow.latency || 0,
latency = Math.max(pLatency, rLatency);
return _co2['default'].call(this, _regeneratorRuntime.mark(function callee$2$0() {
var brake, groupedBrake, tees;
return _regeneratorRuntime.wrap(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
if (!(latency > 0)) {
context$3$0.next = 3;
break;
}
context$3$0.next = 3;
return (0, _wait2['default'])(latency);
case 3:
outResp.writeHead(resp.statusCode, resp.headers);
if (rSlow.rate > 0) {
brake = _streams2['default'].brake(rSlow.rate);
source = source.pipe(brake);
}
if (pSlow.rate) {
groupedBrake = pSlow.rate.throttle();
source = source.pipe(groupedBrake);
}
if (pSlow.down) {
groupedBrake = pSlow.down.throttle();
source = source.pipe(groupedBrake);
}
tees = resp._tees();
tees.forEach(function (writable) {
return source.pipe(writable);
});
context$3$0.next = 11;
return new _Promise(function (resolve, reject) {
source.on('error', reject);
source.on('end', resolve);
source.pipe(outResp);
});
case 11:
case 'end':
return context$3$0.stop();
}
}, callee$2$0, this);
}));
}
}, {
key: '_start',
value: function _start() {
// for now, an immediately-kept promise
return (0, _await2['default'])('started').keep('started');
}
}]);
return Cycle;
})(_events.EventEmitter);
exports['default'] = Cycle;
module.exports = exports['default'];
// file does not exist, so continue
// TODO: test coverage for mkdirp
// TODO: test coverage
// First, get all our ducks in a row WRT to
// options, setting variables, etc.
// return the outer promise
// wait for it all to pipe out

View File

@@ -1,8 +1,8 @@
{
"name": "res-downloader",
"version": "1.0.4",
"version": "2.1.0",
"main": "dist-electron/main/index.js",
"description": "Electron + Vue + Vite 实现的资源下载软件,支持微信视频号下载、抖音视频下载、快手视频下载、酷狗音乐下载等",
"description": "res-downloader(爱享素材下载器),支持视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐、qq短视频等",
"author": "putyy@qq.com",
"license": "MIT",
"private": true,
@@ -19,8 +19,8 @@
}
},
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build && electron-builder",
"dev": "node script/hoxy.ts && vite",
"build": "node script/hoxy.ts && vue-tsc --noEmit && vite build && electron-builder",
"preview": "vite preview"
},
"devDependencies": {
@@ -50,12 +50,13 @@
"vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vue-tsc": "^1.8.8"
"vue-tsc": "^2.0.29"
},
"dependencies": {
"axios": "^1.5.0",
"electron-store": "^8.1.0",
"getmac": "^5.20.0",
"hoxy": "^3.3.1"
"hoxy": "^3.3.1",
"tunnel-agent": "^0.6.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
public/show.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

7
script/hoxy.ts Normal file
View File

@@ -0,0 +1,7 @@
const fs = require('fs');
const path = require('path');
const currentDir = process.cwd();
// 移动文件
fs.copyFile(currentDir + '/override/hoxy/lib/cycle.js', currentDir + '/node_modules/hoxy/lib/cycle.js', fs.constants.COPYFILE_FICLONE, (err) => {
});

View File

@@ -21,12 +21,12 @@ const jump = (scene: number)=>{
break;
case 4:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://github.com/putyy/res-downloader/issues"
url: "https://s.gowas.cn/d/4089-quan-ping-tai-zi-yuan-xia-zai-ruan-jian"
})
break;
case 5:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://haokawx.lot-ml.com/Product/Index/22550"
url: "https://www.ais.do/ivi/rr2GaZ"
})
break;
case 6:
@@ -42,11 +42,12 @@ div.line
a.item 当前版本: {{v}}
a.item 站长邮箱: gowas.work@gmail.com
a.item(@click="jump(1)") 获取更新
a.item(@click="jump(2)") 云盘资源
div.line
a.item(@click="jump(3)") 图片无损压缩
a.item(@click="jump(4)") 问题反馈
a.item(@click="jump(5)") 流量卡推荐
div.line
a.item 推荐:
a.item(@click="jump(5)") Ai助手(免费)
a.item(@click="jump(2)") 网盘资源
a.item(@click="jump(3)") 图片无损压缩
a.item(@click="jump(6)") 软件源码
</template>

View File

@@ -12,7 +12,10 @@ el-container
Sidebar
el-container
el-main
router-view
router-view(v-slot="{ Component, route }")
keep-alive(v-if="route.meta.keepAlive")
component(:is="Component")
component(v-else :is="Component")
el-footer
Footer
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {inject, onMounted, ref, watch} from 'vue'
import localStorageCache from "../../common/localStorage";
import localStorageCache from "../../common/localStorage"
const appName = "爱享素材"
const sidebarCollapse = ref(inject('sidebarCollapse'))

View File

@@ -13,16 +13,19 @@ const routes = [
{
path: '/index',
name: 'Index',
meta: {keepAlive: true},
component: () => import('./views/Index.vue'),
},
{
path: '/about',
name: 'about',
meta: {keepAlive: true},
component: () => import('./views/About.vue'),
},
{
path: '/setting',
name: 'Setting',
meta: {keepAlive: true},
component: () => import('./views/Setting.vue'),
},
]

View File

@@ -35,6 +35,10 @@ const str = "使用方法\n" +
" 手动检测系统代理是否设置正确 本软件代理地址: 127.0.0.1:8899\n" +
" 2. 关闭软件后无法正常上网\n" +
" 手动关闭系统代理设置\n" +
" 3. 视频号抓取流程\n" +
" 将需要下载的视频发给好友或者文件助手 再打开该视频即可拦截到,通常软件界面对应视频会出现标题描述、对应操作会出现解密下载按钮\n" +
" 大视频可以复制链接通过其他工具加速下载,然后再通过对应的视频操作项进行\"视频解密\"\n" +
"实现原理\n" +
" 通过代理网络抓包拦截响应,筛选出有用的资源,\n" +
" 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的\n" +
@@ -43,7 +47,7 @@ const str = "使用方法\n" +
</script>
<template lang="pug">
div.about
div 1. 本软件免费代码已开源不会以任何形式收取费用&nbsp;
div 1. 本软件免费代码已开源不会以任何形式收取费用&nbsp;
el-button(@click="jump(3)") 查看源码
div 2. m3u8复制的链接如何使用? &nbsp;
el-button(@click="jump(1)") 在线下载
@@ -73,4 +77,4 @@ div.about
}
}
</style>
</style>

View File

@@ -1,26 +1,21 @@
<script setup lang="ts">
import {ref, onMounted} from "vue"
import {ref, onMounted, onUnmounted, watch} from "vue"
import {ipcRenderer} from 'electron'
import {onUnmounted} from "@vue/runtime-core"
import {ElMessage, ElLoading, ElTable} from "element-plus"
import localStorageCache from "../common/localStorage"
import {Delete, Promotion} from "@element-plus/icons-vue";
import {Delete, Promotion} from "@element-plus/icons-vue"
interface resData {
url_sign: string,
url: string,
down_url: string,
high_url: string,
url_sign: string,
size: any,
platform: string,
type: string,
type_str: string,
progress_bar: any,
save_path: string,
downing: boolean,
decode_key: string,
description: string,
uploader: string,
}
const tableData = ref<resData[]>([])
@@ -37,6 +32,7 @@ const isInitApp = ref(false)
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref<resData[]>([])
const loading = ref()
onMounted(() => {
let resTypeCache = localStorageCache.get("res-type")
@@ -49,36 +45,39 @@ onMounted(() => {
tableData.value = tableDataCache
}
ipcRenderer.on('on_get_queue', (res, data) => {
// @ts-ignore
if (resType.value.hasOwnProperty(data.type_str) && resType.value[data.type_str]) {
tableData.value.push(data)
localStorageCache.set("res-table-data", tableData.value, -1)
}
})
ipcRenderer.on('on_down_file_schedule', (res: any, data: any) => {
loading.value && loading.value.setText(`${data.schedule}%`)
})
ipcRenderer.invoke('invoke_app_is_init').then((isInit: boolean) => {
if (!isInit) {
if (!isInit && !isInitApp.value) {
isInitApp.value = true
ipcRenderer.invoke('invoke_init_app')
}
})
let loading = ElLoading.service({
loading.value = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
ipcRenderer.invoke('invoke_start_proxy').then(() => {
loading.close()
ipcRenderer.on('on_get_queue', (res, data) => {
// @ts-ignore
if (resType.value.hasOwnProperty(data.type_str) && resType.value[data.type_str]) {
tableData.value.push(data)
localStorageCache.set("res-table-data", tableData.value, -1)
}
return
})
ipcRenderer.invoke('invoke_start_proxy', {upstream_proxy: localStorageCache.get("upstream_proxy")}).then(() => {
loading.value.close()
}).catch((err) => {
// console.log('invoke_start_proxy err', err)
ElMessage({
message: err,
type: 'warning',
})
loading.close()
loading.value.close()
})
})
@@ -90,14 +89,12 @@ onUnmounted(() => {
ipcRenderer.removeListener('on_down_file_schedule', (res) => {
// console.log(res)
})
// ipcRenderer.invoke('invoke_close_proxy').then((res) => {
// })
localStorageCache.set("res-table-data", tableData.value, -1)
localStorageCache.set("res-type", resType.value, -1)
})
watch(resType, (res, res1) => {
localStorageCache.set("res-type", resType.value, -1)
}, {deep: true})
const handleSelectionChange = (val: resData[]) => {
multipleSelection.value = val
}
@@ -117,33 +114,17 @@ const handleBatchDown = async () => {
return
}
let loading = ElLoading.service({
loading.value = ElLoading.service({
lock: true,
text: '下载中',
background: 'rgba(0, 0, 0, 0.7)',
})
ipcRenderer.on('on_down_file_schedule', (res: any, data: any) => {
loading.setText(`已下载 ${data.schedule}%`)
})
const quality = localStorageCache.get("quality") ? localStorageCache.get("quality") : -1
for (const item of multipleSelection.value) {
let result = await ipcRenderer.invoke('invoke_file_exists', {
save_path: save_dir,
url: item.url,
})
if (result.is_file) {
item.progress_bar = "100%"
item.save_path = result.fileName
continue
}
let downRes = await ipcRenderer.invoke('invoke_down_file', {
index: 0,
data: Object.assign({}, item),
save_path: save_dir,
high: false
quality: quality,
})
if (downRes !== false) {
@@ -151,15 +132,13 @@ const handleBatchDown = async () => {
item.save_path = downRes.fullFileName
}
}
loading.close()
loading.value.close()
multipleTableRef.value!.clearSelection()
}
const handleDown = async (index: number, row: any, high: boolean) => {
let save_dir = localStorageCache.get("save_dir")
const handleDown = async (index: number, row: any) => {
const save_dir = localStorageCache.get("save_dir")
if (!save_dir) {
ElMessage({
message: '请设置保存目录',
@@ -168,59 +147,40 @@ const handleDown = async (index: number, row: any, high: boolean) => {
return
}
let loading = ElLoading.service({
loading.value = ElLoading.service({
lock: true,
text: '下载中',
background: 'rgba(0, 0, 0, 0.7)',
})
let result = await ipcRenderer.invoke('invoke_file_exists', {
save_path: save_dir,
url: (high && row.high_url) ? row.high_url : row.url,
})
if (result.is_file) {
tableData.value[index].progress_bar = "100%"
tableData.value[index].save_path = result.fileName
ElMessage({
message: "文件已存在(" + result.fileName + ")",
type: 'warning',
})
loading.close()
return
}
ipcRenderer.on('on_down_file_schedule', (res: any, data: any) => {
loading.setText(`已下载 ${data.schedule}%`)
})
const quality = localStorageCache.get("quality") ? localStorageCache.get("quality") : -1
ipcRenderer.invoke('invoke_down_file', {
index: index,
data: Object.assign({}, tableData.value[index]),
save_path: save_dir,
high: high
quality: quality,
}).then((res) => {
if (res !== false) {
tableData.value[index].progress_bar = "100%"
tableData.value[index].save_path = res.fullFileName
}else{
localStorageCache.set("res-table-data", tableData.value, -1)
} else {
ElMessage({
message: "下载失败",
type: 'warning',
})
}
loading.close()
loading.value.close()
}).catch((err) => {
ElMessage({
message: "下载失败",
type: 'warning',
})
loading.close()
loading.value.close()
})
}
const decodeWxFile = (index: number) => {
let loading = ElLoading.service({
loading.value = ElLoading.service({
lock: true,
text: "解密中",
background: 'rgba(0, 0, 0, 0.7)',
@@ -237,52 +197,60 @@ const decodeWxFile = (index: number) => {
})
tableData.value[index].progress_bar = "100%"
tableData.value[index].save_path = res.fullFileName
}else{
localStorageCache.set("res-table-data", tableData.value, -1)
} else {
ElMessage({
message: "解密失败",
type: 'warning',
})
}
loading.close()
loading.value.close()
}).catch((err) => {
ElMessage({
message: "解密失败",
type: 'warning',
})
loading.close()
loading.value.close()
})
}
const handlePreview = (index: number, row: any) => {
ipcRenderer.invoke('invoke_resources_preview', {url: row.down_url}).catch(() => {
ipcRenderer.invoke('invoke_resources_preview', {url: row.url}).catch(() => {
})
}
const handleClear = () => {
tableData.value = []
localStorageCache.del("res-table-data")
ipcRenderer.invoke('invoke_file_del', {
url_sign: "all"
})
}
const handleCopy = (text: string) => {
let el = document.createElement('input')
el.setAttribute('value', text)
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
ElMessage({
message: "复制成功",
type: 'success',
})
let el = document.createElement('input')
el.setAttribute('value', text)
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
ElMessage({
message: "复制成功",
type: 'success',
})
}
const handleDel = (index: number)=>{
const handleDel = (index: number) => {
let arr = tableData.value
arr.splice(index, 1);
tableData.value = arr
localStorageCache.set("res-table-data", tableData.value, -1)
ipcRenderer.invoke('invoke_file_del', {
url_sign: tableData.value[index].url_sign
})
}
const openFileDir = (index: number)=>{
const openFileDir = (index: number) => {
ipcRenderer.invoke('invoke_open_file_dir', {
save_path: tableData.value[index].save_path
})
@@ -322,13 +290,13 @@ el-container.container
el-main
el-table(ref="multipleTableRef" @selection-change="handleSelectionChange" :data="tableData" max-height="100%" stripe)
el-table-column(type="selection")
el-table-column(label="预览" show-overflow-tooltip width="300px")
el-table-column(label="预览" show-overflow-tooltip width="150px")
template(#default="scope")
div.show_res
video.video(v-if="scope.row.type_str === 'video'" :src="scope.row.down_url" controls preload="none") 您的浏览器不支持 video 标签
img.img(v-if="scope.row.type_str === 'image'" :src="scope.row.down_url")
video.video(v-if="scope.row.type_str === 'video'" :src="scope.row.url" controls preload="none")
img.img(v-if="scope.row.type_str === 'image'" :src="scope.row.url" crossorigin="anonymous")
audio.audio(v-if="scope.row.type_str === 'audio'" controls preload="none")
source(:src="scope.row.down_url" :type="scope.row.type")
source(:src="scope.row.url" :type="scope.row.type")
div {{scope.row.description}}
el-table-column(prop="type_str" label="类型" show-overflow-tooltip)
el-table-column(prop="platform" label="主机地址")
@@ -339,10 +307,10 @@ el-container.container
template(#default="scope")
div.actions
template(v-if="scope.row.type_str !== 'm3u8'" )
el-button(v-if="!scope.row.save_path" link type="primary" @click="handleDown(scope.$index, scope.row, false)") {{scope.row.decode_key ? "解密下载(视频号)" : "下载"}}
el-button(v-if="scope.row.decode_key" link type="primary" @click="decodeWxFile(scope.$index)") 视频解密(视频号)
el-button(link type="primary" @click="handleDown(scope.$index, scope.row)") {{scope.row.decode_key || scope.row.decryptor_array ? "解密下载(视频号)" : "下载"}}
el-button(v-if="scope.row.decode_key || scope.row.decryptor_array" link type="primary" @click="decodeWxFile(scope.$index)") 视频解密(视频号)
el-button(link type="primary" @click="handlePreview(scope.$index, scope.row)") 窗口预览
el-button(link type="primary" @click="handleCopy(scope.row.down_url)") 复制链接
el-button(link type="primary" @click="handleCopy(scope.row.url)") 复制链接
el-button(link type="primary" @click="handleDel(scope.$index)") 删除
el-button(v-if="scope.row.save_path" link type="primary" @click="openFileDir(scope.$index)") 打开文件目录
</template>
@@ -378,15 +346,17 @@ el-container.container
}
}
.show_res{
width: 100%;
height: auto;
.img{
.show_res {
.img {
max-height: 200px;
}
.video {
width: auto;
max-height: 200px;
}
}
.actions{
.actions {
display: flex;
flex-direction: column;
align-items: flex-start;

View File

@@ -1,15 +1,36 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {ipcRenderer} from "electron";
import localStorageCache from "../common/localStorage";
import {ElMessage} from "element-plus";
onMounted(() => {
saveDir.value = localStorageCache.get("save_dir")
saveDir.value = !saveDir.value ? "" : saveDir.value
})
import {onMounted, ref} from "vue"
import {ipcRenderer} from "electron"
import localStorageCache from "../common/localStorage"
import {ElMessage} from "element-plus"
const saveDir = ref("")
const upstream_proxy = ref("")
const upstream_proxy_old = ref("")
const quality = ref("-1")
const qualityOptions = ref([
{
value: '-1',
label: '默认(推荐)'
}, {
value: '0',
label: '高画质'
}, {
value: '1',
label: '中画质'
}, {
value: '2',
label: '低画质'
}
])
onMounted(() => {
saveDir.value = localStorageCache.get("save_dir") ? localStorageCache.get("save_dir") : ""
quality.value = localStorageCache.get("quality") ? localStorageCache.get("quality") : "-1"
upstream_proxy.value = localStorageCache.get("upstream_proxy") ? localStorageCache.get("upstream_proxy") : ""
upstream_proxy_old.value = upstream_proxy.value
})
const selectSaveDir = () => {
ipcRenderer.invoke('invoke_select_down_dir').then(save_path => {
if (save_path !== false) {
@@ -20,17 +41,30 @@ const selectSaveDir = () => {
const onSetting = () => {
localStorageCache.set("save_dir", saveDir.value, -1)
localStorageCache.set("upstream_proxy", upstream_proxy.value, -1)
localStorageCache.set("quality", quality.value, -1)
if (upstream_proxy_old.value != upstream_proxy.value){
ipcRenderer.invoke('invoke_window_restart')
}
ElMessage({
message: "操作成功",
message: "保存成功",
type: 'success',
})
}
</script>
<template lang="pug">
el-form
el-form(style="max-width: 600px")
el-form-item(label="保存位置")
el-button.select-dir(@click="selectSaveDir") {{saveDir ? saveDir : '选择'}}
el-link(@click="selectSaveDir") {{saveDir ? saveDir : '选择'}}
el-form-item(label="视频号画质")
el-select(v-model="quality" placeholder="请选择")
el-option( v-for="item in qualityOptions"
:key="item.value"
:label="item.label"
:value="item.value")
el-form-item(label="特殊代理")
el-input(v-model="upstream_proxy" placeholder="例如: http://127.0.0.1:7890 修改此项需重启本软件,如不清楚用途请勿设置。" )
el-form-item
el-button(type="primary" @click="onSetting") 保存
</template>
</template>