4 Commits
2.0.0 ... 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
46 changed files with 373 additions and 137 deletions

View File

@@ -1,5 +1,6 @@
# V2.0重磅更新,所见即所得!
## res-downloader(爱享素材下载器)
## res-downloader(爱享素材下载器) 【[点击加入群聊](https://qm.qq.com/q/W8mVeZideE)】
🎯 基于 [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue.git)
📦 操作简单、可获取不同类型的资源
🖥️ 支持Win10、Win11、Mac
🌐 支持视频、音频、图片、m3u8等网络资源下载
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
@@ -38,8 +39,25 @@ Win7无法使用
>> MAC: /Users/你的用户名称/.res-downloader@putyy/res-downloader-installed.lock
>> Win: C:\Users\Admin\.res-downloader@putyy/res-downloader-installed.lock
#### 更多问题见: [issues](https://github.com/putyy/res-downloader/issues)、[爱享论坛](https://s.gowas.cn/d/4089-quan-ping-tai-zi-yuan-xia-zai-ruan-jian-zui-xin-ban-v106/171)
其他问题请留言 https://github.com/putyy/res-downloader/issues
## 二次开发
> ps 打包慢的问题可以参考 https://www.putyy.com/articles/87
```sh
git clone https://github.com/putyy/res-downloader
cd res-downloader
yarn install
yarn run dev
# 打包mac
yarn run build --universal --mac
# 打包win
yarn run build --win
```
## 免责声明
本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!
```

3
components.d.ts vendored
View File

@@ -15,10 +15,13 @@ declare module 'vue' {
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,7 +14,7 @@
"electron/res/**/*"
],
"mac": {
"icon": "electron/res/icon/icons/mac/icon.icns",
"icon": "electron/res/icon/mac.icns",
"artifactName": "${productName}_${version}.${arch}.${ext}",
"singleArchFiles": "*",
"target": [
@@ -22,13 +22,27 @@
"target": "dmg",
"arch": [
'x64',
'arm64'
'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",
@@ -37,7 +51,13 @@
]
}
],
"artifactName": "${productName}_${version}.${ext}"
"extraResources": [
{
"from": "electron/res/win",
"to": "electron/res/win",
"filter": ["**/*"],
}
]
},
"nsis": {
"oneClick": false,
@@ -47,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

@@ -17,13 +17,14 @@ export default {
IS_DEV: isDev,
EXECUTABLE_PATH,
HOME_PATH,
APP_CN_NAME: '爱享素材下载器',
APP_EN_NAME: 'ResDownloader',
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, './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'),
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() {
@@ -169,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,26 +1,27 @@
import {ipcMain, dialog, BrowserWindow, app, shell} from 'electron'
import {startServer} from './proxyServer'
import {installCert, checkCertInstalled} from './cert'
import {downloadFile, decodeWxFile, suffix} 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 {floor} from "lodash"
let win: BrowserWindow
let previewWin: BrowserWindow
let isStartProxy = false
const aria2RpcClient = new Aria2RPC()
export default function initIPC() {
ipcMain.handle('invoke_app_is_init', async (event, arg) => {
// 初始化应用 安装证书相关
return checkCertInstalled()
})
ipcMain.handle('invoke_init_app', (event, arg) => {
ipcMain.handle('invoke_init_app', (event, arg) => {
// 开始 初始化应用 安装证书相关
installCert(false).then(r => {})
installCert(false).then(r => {
})
})
ipcMain.handle('invoke_start_proxy', (event, arg) => {
@@ -33,7 +34,7 @@ export default function initIPC() {
win: win,
upstreamProxy: arg.upstream_proxy ? arg.upstream_proxy : "",
setProxyErrorCallback: err => {
console.log('setProxyErrorCallback', err)
},
})
})
@@ -63,33 +64,77 @@ export default function initIPC() {
return {is_file: res, fileName: `${save_path}/${fileName}.mp4`}
})
ipcMain.handle('invoke_down_file', async (event, {data, save_path}) => {
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);
let save_path_file = `${save_path}/${fileName}` + suffix(data.type)
fileName = fileName + "_" + getCurrentDateTimeFormatted() + suffix(data.type)
let save_path_file = `${save_path}/${fileName}`
if (process.platform === 'win32') {
save_path_file = `${save_path}\\${fileName}` + suffix(data.type)
save_path_file = `${save_path}\\${fileName}`
}
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("err:", err)
return false
})
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}) => {
@@ -106,7 +151,8 @@ 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}) => {
@@ -114,7 +160,7 @@ export default function initIPC() {
})
ipcMain.handle('invoke_file_del', (event, {url_sign}) => {
if (url_sign === "all"){
if (url_sign === "all") {
global.videoList = {}
return
}
@@ -125,8 +171,8 @@ export default function initIPC() {
})
ipcMain.handle('invoke_window_restart', (event) => {
app.relaunch()
app.exit()
app.relaunch()
app.exit()
})
}

View File

@@ -23,6 +23,8 @@ if (process.platform === 'win32') {
const resObject = {
url: "",
url_sign: "",
cover_url: "",
file_format: "",
platform: "",
size: "",
type: "video/mp4",
@@ -71,12 +73,12 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
res.string = 'ok'
res.statusCode = 200
try {
if (!req.json?.description || req.json?.media?.length <= 0) {
if (req.json?.media?.length <= 0) {
return
}
const media = req.json?.media[0]
const url_sign: string = hexMD5(media.url)
if (global.videoList.hasOwnProperty(url_sign) === true) {
if (!media?.decodeKey || global.videoList.hasOwnProperty(url_sign) === true) {
return
}
const urlInfo = urlTool.parse(media.url, true)
@@ -84,11 +86,13 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url_sign: url_sign,
url: media.url + media.urlToken,
cover_url: media.coverUrl,
file_format: media.spec.map((res)=> res.fileFormat).join('#'),
platform: urlInfo.hostname,
size: media?.fileSize ? toSize(media.fileSize) : 0,
size: toSize(media.fileSize),
type: "video/mp4",
type_str: 'video',
decode_key: media?.decodeKey ? media?.decodeKey : '',
decode_key: media.decodeKey,
description: req.json.description,
}))
} catch (e) {
@@ -114,6 +118,7 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
proxy.intercept(
{
phase: 'response',
hostname: 'res.wx.qq.com',
as: 'string',
},
async (req, res) => {
@@ -121,14 +126,6 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"');
}
if (req.url.includes("web/web-finder/res/js/virtual_svg-icons-register.publish")) {
// console.log(res.string.match(/return\s*\{\s*width:([\s\S]*?)scalingInfo:([\s\S]*?)\}/))
// res.string = res.string.replace(
// /return\s*{\s*width:(.*?)scalingInfo:(.*?)\s*}/,
// `var mediaInfo = {width:$1scalingInfo:$2};
// console.log("mediaInfo", mediaInfo);
// console.log("this.objectDesc", this.objectDesc);
// return mediaInfo;`
// )
res.string = res.string.replace(/get\s*media\s*\(\)\s*\{/, `
get media(){
if(this.objectDesc){
@@ -231,7 +228,6 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
type_str: 'm3u8',
}))
break
}
} catch (e) {
log.log(e.toString())
@@ -242,14 +238,4 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f
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

@@ -125,4 +125,4 @@ function getMacAvailableNetworks() {
}
})
})
}
}

View File

@@ -144,4 +144,17 @@ function suffix(type: string) {
return ""
}
export {downloadFile, toSize, decodeWxFile, suffix}
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

View File

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 199 KiB

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.

View File

@@ -1,8 +1,8 @@
{
"name": "res-downloader",
"version": "2.0.0",
"version": "2.1.0",
"main": "dist-electron/main/index.js",
"description": "res-downloader(爱享素材下载器)支持视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐下载等",
"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,7 +50,7 @@
"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",

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

@@ -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

@@ -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

@@ -54,7 +54,7 @@ onMounted(() => {
})
ipcRenderer.on('on_down_file_schedule', (res: any, data: any) => {
loading.value && loading.value.setText(`已下载 ${data.schedule}%`)
loading.value && loading.value.setText(`${data.schedule}%`)
})
ipcRenderer.invoke('invoke_app_is_init').then((isInit: boolean) => {
@@ -119,22 +119,12 @@ const handleBatchDown = async () => {
text: '下载中',
background: 'rgba(0, 0, 0, 0.7)',
})
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', {
data: Object.assign({}, item),
save_path: save_dir,
quality: quality,
})
if (downRes !== false) {
@@ -148,9 +138,7 @@ const handleBatchDown = async () => {
const handleDown = async (index: number, row: any) => {
let save_dir = localStorageCache.get("save_dir")
const save_dir = localStorageCache.get("save_dir")
if (!save_dir) {
ElMessage({
message: '请设置保存目录',
@@ -165,26 +153,11 @@ const handleDown = async (index: number, row: any) => {
background: 'rgba(0, 0, 0, 0.7)',
})
let result = await ipcRenderer.invoke('invoke_file_exists', {
save_path: save_dir,
url: row.url,
description: row.description
})
if (result.is_file) {
tableData.value[index].progress_bar = "100%"
tableData.value[index].save_path = result.fileName
ElMessage({
message: "文件已存在(" + result.fileName + ")",
type: 'warning',
})
loading.value.close()
localStorageCache.set("res-table-data", tableData.value, -1)
return
}
const quality = localStorageCache.get("quality") ? localStorageCache.get("quality") : -1
ipcRenderer.invoke('invoke_down_file', {
data: Object.assign({}, tableData.value[index]),
save_path: save_dir
save_path: save_dir,
quality: quality,
}).then((res) => {
if (res !== false) {
tableData.value[index].progress_bar = "100%"
@@ -317,10 +290,10 @@ 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.url" controls preload="none") 您的浏览器不支持 video 标签
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.url" :type="scope.row.type")
@@ -334,7 +307,7 @@ 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)") {{scope.row.decode_key || scope.row.decryptor_array ? "解密下载(视频号)" : "下载"}}
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.url)") 复制链接
@@ -374,12 +347,13 @@ el-container.container
}
.show_res {
width: 100%;
height: auto;
.img {
max-height: 200px;
}
.video {
width: auto;
max-height: 200px;
}
}
.actions {

View File

@@ -7,9 +7,26 @@ 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
})
@@ -25,22 +42,29 @@ 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(@click="selectSaveDir") {{saveDir ? saveDir : '选择'}}
el-form-item(label="代理服务")
el-input(v-model="upstream_proxy" placeholder="例如: http://127.0.0.1:7890 修改此项需重启本软件" )
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>