30 Commits
1.0.5 ... 2.3.0

Author SHA1 Message Date
putyy
d9259bb259 Merge pull request #114 from putyy/dev
win7支持
2024-12-04 16:17:08 +08:00
putyy
8756e5357a 修改Md 2024-12-04 16:15:20 +08:00
putyy
4c09b8f3be x 2024-12-04 15:47:38 +08:00
putyy
6f3e361ee6 x 2024-12-04 15:46:57 +08:00
putyy
93d9dbe32b win7支持,修复目录缓存失效 2024-12-02 17:36:51 +08:00
putyy
fd89b7125c Merge pull request #106 from putyy/dev
Dev
2024-11-18 22:05:10 +08:00
putyy
bb0e97c402 优化 2024-11-18 22:04:46 +08:00
putyy
edd097855b 优化代理启动、增加table筛选等 2024-11-18 17:41:43 +08:00
putyy
4d35c44247 Merge pull request #97 from putyy/dev
完善content type
2024-10-29 10:52:35 +08:00
putyy
d7e34d9c21 完善content type 2024-10-29 10:51:58 +08:00
putyy
fc06c29759 Merge pull request #95 from putyy/dev
Dev
2024-10-23 17:31:27 +08:00
putyy
c65702e215 修改md version 2024-10-23 17:30:39 +08:00
putyy
96300164da 修改md version 2024-10-23 17:28:34 +08:00
putyy
1b35475302 完善类型 2024-10-23 10:24:16 +08:00
putyy
240bae9be9 修改md 2024-10-15 13:42:52 +08:00
putyy
46b1592a7f 更新版本号 2024-10-15 13:37:52 +08:00
putyy
1cae714fd2 Merge pull request #90 from putyy/dev
完善资源类型、优化资源类型选择、优化table固定表头等
2024-10-15 11:19:48 +08:00
putyy
5e63624955 完善资源类型、优化资源类型选择、优化table固定表头等 2024-10-15 11:19:17 +08:00
putyy
7584156262 Update README.md 2024-10-10 09:05:09 +08:00
putyy
d23c09748b 调整打包 2024-09-04 09:20:20 +08:00
putyy
936adca3f2 更新执行文件 2024-09-03 16:43:24 +08:00
putyy
37424da698 更新版本号 2024-09-03 16:02:56 +08:00
putyy
7fd4877087 升级openssl、linux测试版本 2024-09-03 15:59:48 +08:00
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
73 changed files with 1786 additions and 945 deletions

View File

@@ -1,57 +1,54 @@
# res-downloader
## res-downloader
### 爱享素材下载器【[加入群聊](https://qm.qq.com/q/mfDMSpCxQ4)】
🎯 基于 [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue.git)
📦 操作简单、可获取不同类型的资源
💪 支持获取视频、音频、图片、m3u8
🖥 支持获取视频号、抖音、快手、小红书、酷狗音乐、qq音乐、微信小程序等网络资源
🍊 支持设置代理以获取特殊网络下的资源
🖥️ 支持Windows、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
## 软件下载
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
🆕 [蓝奏云下载 密码:9vs5](https://wwjv.lanzoum.com/b04wgtfyb)
## 二次开发
> 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 --mac
# 打包win
yarn run build --win
```
## 使用方法
> 1. 打开本软件
> 2. 软件首页选择要获取的资源类型(默认选中的视频)
> 0. 安装时一定要同意安装证书文件、使用时一定要允许网络访问
> 1. 打开本软件 软件首页左上角点击 “启动代理”
> 2. 软件首页选择要获取的资源类型(默认选中的全部)
> 3. 打开要捕获的源, 如:视频号、网页、小程序等等
> 4. 返回软件首页即可看到要下载的资源
## 常见问题
> 1. 无法拦截获取
> > 手动检测系统代理是否设置正确 本软件代理地址: 127.0.0.1:8899
> 2. 关闭软件后无法正常上网
> > 手动关闭系统代理设置
> 3. 视频号抓取流程
> > 将需要下载的视频发给好友或者文件助手 再打开即可拦截,通常会出现解密下载按钮
> >
> > 大视频可以复制链接通过其他工具加速下载,然后再通过对应的视频操作项进行"视频解密"
> 4. 返回软件首页即可看到资源列表
## 软件截图
![](public/show.webp)
## 实现原理
> 通过代理网络抓包拦截响应筛选出有用的资源同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度所以就有了本项目这样的软件。
## 常见问题
m3u8: 预览和下载:
> [下载](https://m3u8-down.gowas.cn/) [预览](https://m3u8play.com/)
## 参考项目
直播流: 预览和录制:
> [使用obs进行预览和录制 使用教程自行百度]( https://obsproject.com/)
- [WeChatVideoDownloader](https://github.com/lecepin/WeChatVideoDownloader) 原项目是react写的本项目参考原项目用vue3重写了一下核心逻辑没什么变化主要是增加了一些新的功能再次感谢
下载慢、大视频下载失败(最新版本已内置aria2下载器)
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
打开本软件,无法正常拦截获取
> 检查系统代理是否正确设置 代理地址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
其他问题
[github](https://github.com/putyy/res-downloader/issues) 、 [爱享论坛](https://s.gowas.cn/d/4089)
## 实现 & 初衷
通过代理网络抓包拦截响应,筛选出有用的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度本软件对部分资源做了特殊处理更适合大众用户所以就有了本项目。
## 免责声明
本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!

7
components.d.ts vendored
View File

@@ -8,6 +8,9 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
@@ -18,7 +21,9 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElRow: typeof import('element-plus/es')['ElRow']
ElOption: typeof import('element-plus/es')['ElOption']
ElPopover: typeof import('element-plus/es')['ElPopover']
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,25 +3,80 @@
*/
{
"$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}"
},
"files": [
"dist-electron",
"dist",
"electron/res/**/*"
"dist"
],
"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/darwin/aria2/aria2.conf",
"to": "electron/res/darwin/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/darwin/aria2/${arch}/aria2c",
"to": "electron/res/darwin/aria2/aria2c",
"filter": ["**/*"],
}
]
},
"linux": {
"icon": "electron/res/icon/icon.png",
"artifactName": "${productName}_${version}.${arch}.${ext}",
"category": "Network",
"target": [
{
"target": "AppImage",
"arch": [
"x64",
"arm64",
"armv7l"
]
},
{
"target": "deb",
"arch": [
"x64",
"arm64",
"armv7l"
]
}
],
"extraResources": [
{
"from": "electron/res/linux/aria2/aria2.conf",
"to": "electron/res/linux/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/linux/aria2/${arch}/aria2c",
"to": "electron/res/linux/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 +85,13 @@
]
}
],
"artifactName": "${productName}_${version}.${ext}"
"extraResources": [
{
"from": "electron/res/win",
"to": "electron/res/win",
"filter": ["**/*"],
}
]
},
"nsis": {
"oneClick": false,
@@ -40,6 +101,7 @@
"deleteAppDataOnUninstall": false
},
"extraResources": [
"electron/res"
"electron/res/icon",
"electron/res/keys",
]
}

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

@@ -0,0 +1,45 @@
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) {
const totalPieces = bitfield.length * 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

@@ -9,39 +9,62 @@ export function checkCertInstalled() {
return fs.existsSync(CONFIG.INSTALL_CERT_FLAG)
}
export async function installCert(checkInstalled = true) {
if (checkInstalled && checkCertInstalled()) {
return;
}
mkdirp.sync(path.dirname(CONFIG.INSTALL_CERT_FLAG))
export function installCert(checkInstalled = true) {
try {
if (checkInstalled && checkCertInstalled()) {
return;
}
mkdirp.sync(path.dirname(CONFIG.INSTALL_CERT_FLAG))
if (process.platform === 'darwin') {
return new Promise((resolve, reject) => {
clipboard.writeText(
`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',
message: `命令已复制到剪贴板,粘贴命令到终端并运行以安装并信任证书`,
});
reject()
});
} else {
return new Promise((resolve: any, reject) => {
const result = spawn.sync(CONFIG.WIN_CERT_INSTALL_HELPER, [
'-c',
'-add',
CONFIG.CERT_PUBLIC_PATH,
'-s',
'root',
]);
if (result.stdout.toString().indexOf('Succeeded') > -1) {
fs.writeFileSync(CONFIG.INSTALL_CERT_FLAG, '')
resolve()
} else {
reject()
}
})
if (process.platform === 'darwin') {
handleMacCertInstallation()
} else if (process.platform === 'win32') {
handleWindowsCertInstallation()
} else {
handleOtherCertInstallation()
}
} catch (e) {
handleOtherCertInstallation()
}
}
// MacOS 证书安装处理
function handleMacCertInstallation() {
clipboard.writeText(
`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',
message: '命令已复制到剪贴板,粘贴到终端并运行以安装并信任证书',
});
}
// Linux 证书安装处理
function handleOtherCertInstallation() {
clipboard.writeText(CONFIG.CERT_PUBLIC_PATH);
dialog.showMessageBoxSync({
type: "info",
message: `请手动安装证书,证书文件路径:${CONFIG.CERT_PUBLIC_PATH} 已复制到剪贴板`,
});
}
// Windows 证书安装处理
function handleWindowsCertInstallation() {
const result = spawn.sync(CONFIG.WIN_CERT_INSTALL_HELPER, [
'-c',
'-add',
CONFIG.CERT_PUBLIC_PATH,
'-s',
'root',
]);
if (result.stdout.toString().includes('Succeeded')) {
fs.writeFileSync(CONFIG.INSTALL_CERT_FLAG, '');
} else {
handleOtherCertInstallation();
}
}

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,12 @@ 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'
import {startServer} from "./proxyServer";
import fs from "fs";
// The built directory structure
//
@@ -45,13 +51,26 @@ 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')
const url = process.env.VITE_DEV_SERVER_URL
const indexHtml = join(process.env.DIST, 'index.html')
global.videoList = {}
global.isStartProxy = false
global.isSettingProxy = false
global.resdConfig = {
save_dir: "",
quality: "-1",
proxy: "",
port: 8899,
}
// app.whenReady().then(createWindow)
// 修复部分机型GPU主窗口空白错误
app.disableHardwareAcceleration();
app.on('window-all-closed', () => {
mainWindow = null
@@ -77,21 +96,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() {
@@ -100,8 +114,10 @@ function createWindow() {
mainWindow = new BrowserWindow({
title: 'Main window',
icon: join(process.env.VITE_PUBLIC, 'favicon.ico'),
width: 800,
height: 600,
width: 1024,
minWidth: 960,
height: 768,
minHeight: 640,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
@@ -144,6 +160,8 @@ function createPreviewWindow(parent: BrowserWindow) {
parent: parent,
width: 600,
height: 400,
minWidth: 600,
minHeight: 400,
show: false,
// paintWhenInitiallyHidden: false,
webPreferences: {
@@ -157,21 +175,71 @@ function createPreviewWindow(parent: BrowserWindow) {
previewWin.setTitle("预览")
previewWin.on("page-title-updated", (event) => {
// 阻止该事件
event.preventDefault()
})
previewWin.on("close", (event) => {
// 不关闭窗口
event.preventDefault()
// 影藏窗口
previewWin.hide()
})
}
function createAria2Process() {
// 根据操作系统选择 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, `./${process.platform}/aria2` + (CONFIG.IS_DEV ? `/${process.arch}` : '/') + "/aria2c");
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, `./${process.platform}/aria2/aria2.conf`)
}
aria2Process = spawn(aria2Path, [`--conf-path=${aria2Conf}`, `--rpc-listen-port=${CONFIG.ARIA_PORT}`], {
windowsHide: false,
stdio: CONFIG.IS_DEV ? 'pipe' : 'ignore'
});
if(!aria2Process){
console.log("start aria2 error")
}
if (CONFIG.IS_DEV) {
aria2Process.stdout.on('data', (data) => {
console.log(`aria2: ${data}`);
});
aria2Process.stderr.on('data', (data) => {
console.log(`aria2 error: ${data}`);
});
}
} catch (e) {
console.log(`aria2 process start err`, e);
}
}
function initConfig(){
const configPath = path.join(app.getPath('userData'), 'resd_config.json')
if (!fs.existsSync(configPath)) {
return
}
const buff = fs.readFileSync(configPath);
if (buff) {
try {
const jsonData = JSON.parse(buff)
global.resdConfig = Object.assign({}, global.resdConfig, jsonData)
if (!global.resdConfig.port) {
global.resdConfig.port = 8899
}
} catch (parseErr) {
}
}
}
app.whenReady().then(() => {
initIPC()
createWindow()
createPreviewWindow(mainWindow)
setWin(mainWindow, previewWin)
})
initConfig()
initIPC()
startServer(mainWindow)
createAria2Process()
})

View File

@@ -1,45 +1,63 @@
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, typeSuffix, 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 {floor} from "lodash"
import urlTool from "url";
import {closeProxy, setProxy} from "./setProxy";
import path from 'path'
let getMac = require("getmac").default
let win: BrowserWindow
let previewWin: BrowserWindow
let isStartProxy = false
let aesKey = "as5d45as4d6qe6wqfar6gt4749q6y7w6h34v64tv7t37ty5qwtv6t6qv"
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)
})
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 => {
ipcMain.handle('invoke_set_config', (event, data) => {
const filePath = path.join(app.getPath('userData'), 'resd_config.json');
fs.writeFile(filePath, JSON.stringify(data), ()=>{})
global.resdConfig = Object.assign({}, global.resdConfig, data)
global.resdConfig.port = parseInt(global.resdConfig.port)
return true
})
},
})
ipcMain.handle('invoke_set_proxy', async (event, arg) => {
// 启动代理服务
if (!global.isStartProxy) {
dialog.showMessageBoxSync({
type: "error",
message: "代理未启动",
});
return false
}
try {
if (arg.proxy) {
await setProxy('127.0.0.1', global.resdConfig.port)
}else{
await closeProxy('127.0.0.1', global.resdConfig.port)
}
return true
} catch (err) {
console.error(err);
dialog.showMessageBoxSync({
type: "error",
message: err.toString(),
});
return false
}
})
ipcMain.handle('invoke_select_down_dir', async (event, arg) => {
@@ -61,63 +79,97 @@ 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);
});
}
let url_sign = hexMD5(down_url)
let save_path_file = `${save_path}/${url_sign}` + suffix(data.type)
if (process.platform === 'win32'){
save_path_file = `${save_path}\\${url_sign}` + suffix(data.type)
}
if (fs.existsSync(save_path_file)) {
return {fullFileName: save_path_file, totalLen: ""}
}
// 开始下载
return downloadFile(
down_url,
data.decode_key,
save_path_file,
(res) => {
win?.webContents.send('on_down_file_schedule', {schedule: floor(res * 100)})
if (quality === "0" && data.decode_key) {
const urlInfo = urlTool.parse(down_url, true);
if (urlInfo.query["token"] && urlInfo.query["encfilekey"]) {
down_url = urlInfo.protocol + "//" + urlInfo.hostname + urlInfo.pathname.replace("251/20302", "251/20304") +
"?encfilekey=" + urlInfo.query["encfilekey"] +
"&token=" + urlInfo.query["token"]
}
).catch(err => {
return false
})
})
ipcMain.handle('invoke_get_mac', async (event) => {
let mac = getMac()
if (mac === "") {
return ""
} else 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];
}
return CryptoJS.AES.encrypt(mac, CryptoJS.enc.Hex.parse(aesKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString()
})
let fileName = data?.description ? data.description.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') : hexMD5(down_url);
fileName = fileName + "_" + getCurrentDateTimeFormatted() + typeSuffix(data.type)[1]
let save_path_file = `${save_path}/${fileName}`
if (process.platform === 'win32') {
save_path_file = `${save_path}\\${fileName}`
}
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 new Promise((resolve, reject) => {
if (data?.referer) {
headers['Referer'] = data?.referer
}
aria2RpcClient.addUri([down_url], save_path, fileName, headers).then((response) => {
let currentGid = response.result
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("decodeWxFile err:", error)
resolve(false);
})
} else {
resolve({
fullFileName: save_path_file,
})
}
}
}).catch((error) => {
console.log("aria2RpcClient err:", error)
clearInterval(progressIntervalId)
resolve(false)
});
}, 1000)
}).catch((error) => {
console.log("aria2RpcClient.addUri err:", error)
resolve(false)
});
});
});
ipcMain.handle('invoke_resources_preview', async (event, {url}) => {
if (!url) {
return
}
console.log('url', url)
previewWin.loadURL(url).then(r => {
return
}).catch(res => {
@@ -127,20 +179,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()
app.relaunch()
app.exit()
})
}
export function setWin(w, p) {
win = w
previewWin = p
}
}

View File

@@ -1,264 +1,172 @@
import fs from 'fs'
import log from 'electron-log'
import CONFIG from './const'
import {closeProxy, setProxy} from './setProxy'
import {app} from "electron"
import * as urlTool from "url"
import {toSize} from "./utils"
import {toSize, typeSuffix} from "./utils"
// @ts-ignore
import {hexMD5} from '../../src/common/md5'
import pkg from '../../package.json'
const hoXy = require('hoxy')
const port = 8899
let 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 injection_script1 = `
setTimeout(() => {
let receiver_url = "https://res-downloader.666666.com";
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,
upstreamProxy,
setProxyErrorCallback = f => f,
}) {
return new Promise(async (resolve: any, reject) => {
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((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: 'request',
hostname: 'res-downloader.666666.com',
as: 'json',
},
(req, res) => {
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: '',
})
},
);
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.replace('</body>', '\n<script>' + injection_script1 + '</script>\n</body>');
res.statusCode = 200;
}
},
);
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
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: '',
})
}
break;
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/gif":
case "image/avif":
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: '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;
}
},
)
} catch (e) {
log.log("--------------proxy catch err--------------", e)
}
})
const resObject = {
url: "",
url_sign: "",
referer: "",
cover_url: "",
file_format: "",
platform: "",
size: "",
type: "video/mp4",
type_str: 'video',
progress_bar: "",
save_path: "",
decode_key: "",
description: ""
}
app.on('before-quit', async e => {
e.preventDefault()
const vv = hexMD5(pkg.version) + (CONFIG.IS_DEV ? Math.random() : "")
export function startServer(win) {
try {
await closeProxy()
log.log("--------------closeProxy success--------------")
} catch (error) {
let upstreamProxy = ""
if (global.resdConfig.proxy && !global.resdConfig.proxy.includes(':' + global.resdConfig.port)) {
upstreamProxy = global.resdConfig?.proxy
}
const proxy = hoXy.createServer({
upstreamProxy: upstreamProxy,
certAuthority: {
key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
},
})
.listen(global.resdConfig.port, () => {
global.isStartProxy = true
})
.on('error', err => {
console.error("hoXy err:", err);
})
intercept(proxy, win)
} catch (e) {
console.error("--------------proxy catch err--------------", e);
}
app.exit()
})
}
function intercept(proxy, win) {
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
}
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: media.url + media.urlToken,
cover_url: media.coverUrl,
referer: "",
file_format: media.spec.map((res) => res.fileFormat).join('#'),
platform: urlInfo.hostname,
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 contentType = res?._data?.headers?.['content-type']
const [resType, suffix] = typeSuffix(contentType)
if (resType) {
const url_sign: string = hexMD5(req.fullUrl())
const res_url = req.fullUrl()
const urlInfo = urlTool.parse(res_url, true)
const contentLength = res?._data?.headers?.['content-length']
if (global.videoList.hasOwnProperty(url_sign) === false) {
global.videoList[url_sign] = res_url
let referer = req?._data?.headers?.['referer']
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
referer: referer ? referer : "",
platform: urlInfo.hostname,
size: toSize(contentLength ? contentLength : 0),
type: contentType,
type_str: resType,
}))
}
}
} catch (e) {
log.log("--------------proxy response err--------------", e)
}
},
)
}

View File

@@ -2,6 +2,7 @@ import {exec} from 'child_process'
// @ts-ignore
import regedit from 'regedit'
import CONFIG from './const'
import {dialog} from "electron";
regedit.setExternalVBSLocation(CONFIG.REGEDIT_VBS_PATH)
@@ -34,6 +35,12 @@ export async function setProxy(host, port) {
});
}),
);
} else if (process.platform === 'linux') {
dialog.showMessageBoxSync({
type: "info",
message: `请手动设置系统代理 默认为: 127.0.0.1:8899`,
});
return new Promise((resolve, reject) => {})
} else {
const valuesToPut = {
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
@@ -81,6 +88,11 @@ export async function closeProxy() {
});
}),
);
} else if (process.platform === 'linux') {
dialog.showMessageBoxSync({
type: "info",
message: `请手动取消系统代理`,
});
} else {
const valuesToPut = {
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
@@ -125,4 +137,4 @@ function getMacAvailableNetworks() {
}
})
})
}
}

View File

@@ -1,8 +1,9 @@
import fs from 'fs'
import {Transform } from 'stream'
import {Transform} from 'stream'
import {getDecryptionArray} from '../wxjs/decrypt.js'
const axios = require('axios')
function xorTransform(decryptionArray) {
let processedBytes = 0;
return new Transform({
@@ -25,13 +26,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']
@@ -52,7 +58,7 @@ function downloadFile(url, decodeKey, fullFileName, progressCallback) {
});
}),
);
}else{
} else {
data.pipe(
fs.createWriteStream(fullFileName).on('finish', () => {
resolve({
@@ -92,30 +98,79 @@ function toSize(size: number) {
return size + 'b'
}
function suffix(type: string) {
switch (type) {
function typeSuffix(type: string) {
switch (type ? type.toLowerCase() : type) {
case "video/mp4":
return ".mp4";
case "video/webm":
case "video/ogg":
case "video/x-msvideo":
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/3gpp":
case "video/x-matroska":
return ["video", ".mp4"];
case "audio/video":
case "video/x-flv":
return ["live", ".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";
case "image/jpeg":
return ".jpeg";
case "image/jpg":
return ".jpg";
case "image/gif":
case "image/avif":
return ".avif";
case "image/bmp":
case "image/tiff":
case "image/heic":
case "image/x-icon":
case "image/svg+xml":
case "image/vnd.adobe.photoshop":
return ["image", ".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":
case "audio/mp3":
case "audio/mp4;charset=UTF-8":
return ["audio", ".mp3"];
case "application/vnd.apple.mpegurl":
case "application/x-mpegurl":
return ["m3u8", ".m3u8"];
case "application/pdf":
return ["pdf", ".pdf"];
case "application/vnd.ms-powerpoint":
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
return ["ppt", ".ppt"];
case "application/vnd.ms-excel":
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
return ["xls", ".xls"];
case "application/msword":
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
return ["doc", ".doc"];
}
return ""
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, typeSuffix, 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

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

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.

Binary file not shown.

Binary file not shown.

View File

@@ -1,126 +0,0 @@
LICENSE ISSUES
==============
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts. Actually both licenses are BSD-style
Open Source licenses. In case of any license issues related to OpenSSL
please contact openssl-core@openssl.org.
OpenSSL License
---------------
/* ====================================================================
* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
Original SSLeay License
-----------------------
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/

View File

@@ -1,59 +0,0 @@
=============================================================================
OpenSSL v1.0.2q Precompiled Binaries for Win32
-----------------------------------------------------------------------------
*** Release Information ***
Release Date: Nov 22, 2018
Author: Frederik A. Winkelsdorf (opendec.wordpress.com)
for the Indy Project (www.indyproject.org)
Requirements: Indy 10.5.5+ (SVN Version or Delphi 2009 and newer)
Dependencies: The libraries have no noteworthy dependencies
Installation: Copy both DLL files into your application directory
Supported OS: Windows 2000 up to Windows 10
-----------------------------------------------------------------------------
*** Legal Disclaimer ***
THIS SOFTWARE IS PROVIDED BY ITS AUTHOR AND THE INDY PROJECT "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
OpenSSL license terms are provided in the file "OpenSSL License.txt".
PLEASE CHECK IF YOU NEED TO COMPLY WITH EXPORT RESTRICTIONS FOR CRYPTOGRAPHIC
SOFTWARE AND/OR PATENTS.
-----------------------------------------------------------------------------
*** Build Information Win32 ***
Built with: Microsoft Visual C++ 2008 Express Edition
The Netwide Assembler (NASM) v2.11.08 Win32
Strawberry Perl v5.22.0.1 Win32 Portable
Windows PowerShell
FinalBuilder 7
Commands: perl configure VC-WIN32
ms\do_nasm
adjusted ms\ntdll.mak (replaced "/MD" with "/MT")
adjusted ms\version32.rc (Indy Information inserted)
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak test
editbin.exe /rebase:base=0x11000000 libeay32.dll
editbin.exe /rebase:base=0x12000000 ssleay32.dll
=============================================================================

Binary file not shown.

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

BIN
electron/res/win/aria2/aria2c.exe Executable file

Binary file not shown.

239
electron/res/win/openssl/CA.pl Executable file
View File

@@ -0,0 +1,239 @@
#!/usr/bin/env perl
# Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
#
# Wrapper around the ca to make it easier to use
#
# WARNING: do not edit!
# Generated by makefile from apps\CA.pl.in
use strict;
use warnings;
my $verbose = 1;
my @OPENSSL_CMDS = ("req", "ca", "pkcs12", "x509", "verify");
my $openssl = $ENV{'OPENSSL'} // "openssl";
$ENV{'OPENSSL'} = $openssl;
my $OPENSSL_CONFIG = $ENV{"OPENSSL_CONFIG"} // "";
# Command invocations.
my $REQ = "$openssl req $OPENSSL_CONFIG";
my $CA = "$openssl ca $OPENSSL_CONFIG";
my $VERIFY = "$openssl verify";
my $X509 = "$openssl x509";
my $PKCS12 = "$openssl pkcs12";
# Default values for various configuration settings.
my $CATOP = "./demoCA";
my $CAKEY = "cakey.pem";
my $CAREQ = "careq.pem";
my $CACERT = "cacert.pem";
my $CACRL = "crl.pem";
my $DAYS = "-days 365";
my $CADAYS = "-days 1095"; # 3 years
my $EXTENSIONS = "-extensions v3_ca";
my $POLICY = "-policy policy_anything";
my $NEWKEY = "newkey.pem";
my $NEWREQ = "newreq.pem";
my $NEWCERT = "newcert.pem";
my $NEWP12 = "newcert.p12";
# Commandline parsing
my %EXTRA;
my $WHAT = shift @ARGV || "";
@ARGV = parse_extra(@ARGV);
my $RET = 0;
# Split out "-extra-CMD value", and return new |@ARGV|. Fill in
# |EXTRA{CMD}| with list of values.
sub parse_extra
{
foreach ( @OPENSSL_CMDS ) {
$EXTRA{$_} = '';
}
my @result;
while ( scalar(@_) > 0 ) {
my $arg = shift;
if ( $arg !~ m/-extra-([a-z0-9]+)/ ) {
push @result, $arg;
next;
}
$arg =~ s/-extra-//;
die("Unknown \"-${arg}-extra\" option, exiting")
unless scalar grep { $arg eq $_ } @OPENSSL_CMDS;
$EXTRA{$arg} .= " " . shift;
}
return @result;
}
# See if reason for a CRL entry is valid; exit if not.
sub crl_reason_ok
{
my $r = shift;
if ($r eq 'unspecified' || $r eq 'keyCompromise'
|| $r eq 'CACompromise' || $r eq 'affiliationChanged'
|| $r eq 'superseded' || $r eq 'cessationOfOperation'
|| $r eq 'certificateHold' || $r eq 'removeFromCRL') {
return 1;
}
print STDERR "Invalid CRL reason; must be one of:\n";
print STDERR " unspecified, keyCompromise, CACompromise,\n";
print STDERR " affiliationChanged, superseded, cessationOfOperation\n";
print STDERR " certificateHold, removeFromCRL";
exit 1;
}
# Copy a PEM-format file; return like exit status (zero means ok)
sub copy_pemfile
{
my ($infile, $outfile, $bound) = @_;
my $found = 0;
open IN, $infile || die "Cannot open $infile, $!";
open OUT, ">$outfile" || die "Cannot write to $outfile, $!";
while (<IN>) {
$found = 1 if /^-----BEGIN.*$bound/;
print OUT $_ if $found;
$found = 2, last if /^-----END.*$bound/;
}
close IN;
close OUT;
return $found == 2 ? 0 : 1;
}
# Wrapper around system; useful for debugging. Returns just the exit status
sub run
{
my $cmd = shift;
print "====\n$cmd\n" if $verbose;
my $status = system($cmd);
print "==> $status\n====\n" if $verbose;
return $status >> 8;
}
if ( $WHAT =~ /^(-\?|-h|-help)$/ ) {
print STDERR <<EOF;
Usage:
CA.pl -newcert | -newreq | -newreq-nodes | -xsign | -sign | -signCA | -signcert | -crl | -newca [-extra-cmd parameter]
CA.pl -pkcs12 [certname]
CA.pl -verify certfile ...
CA.pl -revoke certfile [reason]
EOF
exit 0;
}
if ($WHAT eq '-newcert' ) {
# create a certificate
$RET = run("$REQ -new -x509 -keyout $NEWKEY -out $NEWCERT $DAYS"
. " $EXTRA{req}");
print "Cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT eq '-precert' ) {
# create a pre-certificate
$RET = run("$REQ -x509 -precert -keyout $NEWKEY -out $NEWCERT $DAYS"
. " $EXTRA{req}");
print "Pre-cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT =~ /^\-newreq(\-nodes)?$/ ) {
# create a certificate request
$RET = run("$REQ -new $1 -keyout $NEWKEY -out $NEWREQ $DAYS $EXTRA{req}");
print "Request is in $NEWREQ, private key is in $NEWKEY\n" if $RET == 0;
} elsif ($WHAT eq '-newca' ) {
# create the directory hierarchy
my @dirs = ( "${CATOP}", "${CATOP}/certs", "${CATOP}/crl",
"${CATOP}/newcerts", "${CATOP}/private" );
die "${CATOP}/index.txt exists.\nRemove old sub-tree to proceed,"
if -f "${CATOP}/index.txt";
die "${CATOP}/serial exists.\nRemove old sub-tree to proceed,"
if -f "${CATOP}/serial";
foreach my $d ( @dirs ) {
if ( -d $d ) {
warn "Directory $d exists" if -d $d;
} else {
mkdir $d or die "Can't mkdir $d, $!";
}
}
open OUT, ">${CATOP}/index.txt";
close OUT;
open OUT, ">${CATOP}/crlnumber";
print OUT "01\n";
close OUT;
# ask user for existing CA certificate
print "CA certificate filename (or enter to create)\n";
my $FILE;
$FILE = "" unless defined($FILE = <STDIN>);
$FILE =~ s{\R$}{};
if ($FILE ne "") {
copy_pemfile($FILE,"${CATOP}/private/$CAKEY", "PRIVATE");
copy_pemfile($FILE,"${CATOP}/$CACERT", "CERTIFICATE");
} else {
print "Making CA certificate ...\n";
$RET = run("$REQ -new -keyout ${CATOP}/private/$CAKEY"
. " -out ${CATOP}/$CAREQ $EXTRA{req}");
$RET = run("$CA -create_serial"
. " -out ${CATOP}/$CACERT $CADAYS -batch"
. " -keyfile ${CATOP}/private/$CAKEY -selfsign"
. " $EXTENSIONS"
. " -infiles ${CATOP}/$CAREQ $EXTRA{ca}") if $RET == 0;
print "CA certificate is in ${CATOP}/$CACERT\n" if $RET == 0;
}
} elsif ($WHAT eq '-pkcs12' ) {
my $cname = $ARGV[0];
$cname = "My Certificate" unless defined $cname;
$RET = run("$PKCS12 -in $NEWCERT -inkey $NEWKEY"
. " -certfile ${CATOP}/$CACERT -out $NEWP12"
. " -export -name \"$cname\" $EXTRA{pkcs12}");
print "PKCS #12 file is in $NEWP12\n" if $RET == 0;
} elsif ($WHAT eq '-xsign' ) {
$RET = run("$CA $POLICY -infiles $NEWREQ $EXTRA{ca}");
} elsif ($WHAT eq '-sign' ) {
$RET = run("$CA $POLICY -out $NEWCERT"
. " -infiles $NEWREQ $EXTRA{ca}");
print "Signed certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-signCA' ) {
$RET = run("$CA $POLICY -out $NEWCERT"
. " $EXTENSIONS -infiles $NEWREQ $EXTRA{ca}");
print "Signed CA certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-signcert' ) {
$RET = run("$X509 -x509toreq -in $NEWREQ -signkey $NEWREQ"
. " -out tmp.pem $EXTRA{x509}");
$RET = run("$CA $POLICY -out $NEWCERT"
. "-infiles tmp.pem $EXTRA{ca}") if $RET == 0;
print "Signed certificate is in $NEWCERT\n" if $RET == 0;
} elsif ($WHAT eq '-verify' ) {
my @files = @ARGV ? @ARGV : ( $NEWCERT );
foreach my $file (@files) {
# -CAfile quoted for VMS, since the C RTL downcases all unquoted
# arguments to C programs
my $status = run("$VERIFY \"-CAfile\" ${CATOP}/$CACERT $file $EXTRA{verify}");
$RET = $status if $status != 0;
}
} elsif ($WHAT eq '-crl' ) {
$RET = run("$CA -gencrl -out ${CATOP}/crl/$CACRL $EXTRA{ca}");
print "Generated CRL is in ${CATOP}/crl/$CACRL\n" if $RET == 0;
} elsif ($WHAT eq '-revoke' ) {
my $cname = $ARGV[0];
if (!defined $cname) {
print "Certificate filename is required; reason optional.\n";
exit 1;
}
my $reason = $ARGV[1];
$reason = " -crl_reason $reason"
if defined $reason && crl_reason_ok($reason);
$RET = run("$CA -revoke \"$cname\"" . $reason . $EXTRA{ca});
} else {
print STDERR "Unknown arg \"$WHAT\"\n";
print STDERR "Use -help for help.\n";
exit 1;
}
exit $RET;

BIN
electron/res/win/openssl/capi.dll Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

218
electron/res/win/openssl/progs.pl Executable file
View File

@@ -0,0 +1,218 @@
#! /usr/bin/env perl
# Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
# Generate progs.h file by looking for command mains in list of C files
# passed on the command line.
use strict;
use warnings;
use lib '.';
use configdata qw/@disablables %unified_info/;
my $opt = shift @ARGV;
die "Unrecognised option, must be -C or -H\n"
unless ($opt eq '-H' || $opt eq '-C');
my %commands = ();
my $cmdre = qr/^\s*int\s+([a-z_][a-z0-9_]*)_main\(\s*int\s+argc\s*,/;
my $apps_openssl = shift @ARGV;
my $YEAR = [gmtime($ENV{SOURCE_DATE_EPOCH} || time())]->[5] + 1900;
# because the program apps/openssl has object files as sources, and
# they then have the corresponding C files as source, we need to chain
# the lookups in %unified_info
my @openssl_source =
map { @{$unified_info{sources}->{$_}} }
grep { /\.o$/
&& !$unified_info{attributes}->{sources}->{$apps_openssl}->{$_}->{nocheck} }
@{$unified_info{sources}->{$apps_openssl}};
foreach my $filename (@openssl_source) {
open F, $filename or die "Couldn't open $filename: $!\n";
foreach ( grep /$cmdre/, <F> ) {
my @foo = /$cmdre/;
$commands{$1} = 1;
}
close F;
}
@ARGV = sort keys %commands;
if ($opt eq '-H') {
print <<"EOF";
/*
* WARNING: do not edit!
* Generated by apps/progs.pl
*
* Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "function.h"
EOF
foreach (@ARGV) {
printf "extern int %s_main(int argc, char *argv[]);\n", $_;
}
print "\n";
foreach (@ARGV) {
printf "extern const OPTIONS %s_options[];\n", $_;
}
print "\n";
print "extern FUNCTION functions[];\n";
}
if ($opt eq '-C') {
print <<"EOF";
/*
* WARNING: do not edit!
* Generated by apps/progs.pl
*
* Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "progs.h"
EOF
my %cmd_disabler = (
ciphers => "sock",
genrsa => "rsa",
gendsa => "dsa",
dsaparam => "dsa",
gendh => "dh",
dhparam => "dh",
ecparam => "ec",
);
my %cmd_deprecated = (
# The format of this table is:
# [0] = alternative command to use instead
# [1] = deprecented in this version
# [2] = preprocessor conditional for excluding irrespective of deprecation
# rsa => [ "pkey", "3_0", "rsa" ],
# genrsa => [ "genpkey", "3_0", "rsa" ],
rsautl => [ "pkeyutl", "3_0", "rsa" ],
# dhparam => [ "pkeyparam", "3_0", "dh" ],
# dsaparam => [ "pkeyparam", "3_0", "dsa" ],
# dsa => [ "pkey", "3_0", "dsa" ],
# gendsa => [ "genpkey", "3_0", "dsa" ],
# ec => [ "pkey", "3_0", "ec" ],
# ecparam => [ "pkeyparam", "3_0", "ec" ],
);
print "FUNCTION functions[] = {\n";
foreach my $cmd ( @ARGV ) {
my $str =
" {FT_general, \"$cmd\", ${cmd}_main, ${cmd}_options, NULL, NULL},\n";
if ($cmd =~ /^s_/) {
print "#ifndef OPENSSL_NO_SOCK\n${str}#endif\n";
} elsif (my $deprecated = $cmd_deprecated{$cmd}) {
my @dep = @{$deprecated};
my $daltprg = $dep[0];
my $dver = $dep[1];
my $dsys = $dep[2];
print "#if !defined(OPENSSL_NO_DEPRECATED_" . $dver . ")";
if ($dsys) {
print " && !defined(OPENSSL_NO_" . uc($dsys) . ")";
}
$dver =~ s/_/./g;
my $dalt = "\"" . $daltprg . "\", \"" . $dver . "\"";
$str =~ s/NULL, NULL/$dalt/;
print "\n${str}#endif\n";
} elsif (grep { $cmd eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($cmd) . "\n${str}#endif\n";
} elsif (my $disabler = $cmd_disabler{$cmd}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
my %md_disabler = (
blake2b512 => "blake2",
blake2s256 => "blake2",
);
foreach my $cmd (
"md2", "md4", "md5",
"sha1", "sha224", "sha256", "sha384",
"sha512", "sha512-224", "sha512-256",
"sha3-224", "sha3-256", "sha3-384", "sha3-512",
"shake128", "shake256",
"mdc2", "rmd160", "blake2b512", "blake2s256",
"sm3"
) {
my $str = " {FT_md, \"$cmd\", dgst_main, NULL, NULL},\n";
if (grep { $cmd eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($cmd) . "\n${str}#endif\n";
} elsif (my $disabler = $md_disabler{$cmd}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
my %cipher_disabler = (
des3 => "des",
desx => "des",
cast5 => "cast",
);
foreach my $cmd (
"aes-128-cbc", "aes-128-ecb",
"aes-192-cbc", "aes-192-ecb",
"aes-256-cbc", "aes-256-ecb",
"aria-128-cbc", "aria-128-cfb",
"aria-128-ctr", "aria-128-ecb", "aria-128-ofb",
"aria-128-cfb1", "aria-128-cfb8",
"aria-192-cbc", "aria-192-cfb",
"aria-192-ctr", "aria-192-ecb", "aria-192-ofb",
"aria-192-cfb1", "aria-192-cfb8",
"aria-256-cbc", "aria-256-cfb",
"aria-256-ctr", "aria-256-ecb", "aria-256-ofb",
"aria-256-cfb1", "aria-256-cfb8",
"camellia-128-cbc", "camellia-128-ecb",
"camellia-192-cbc", "camellia-192-ecb",
"camellia-256-cbc", "camellia-256-ecb",
"base64", "zlib", "brotli", "zstd",
"des", "des3", "desx", "idea", "seed", "rc4", "rc4-40",
"rc2", "bf", "cast", "rc5",
"des-ecb", "des-ede", "des-ede3",
"des-cbc", "des-ede-cbc","des-ede3-cbc",
"des-cfb", "des-ede-cfb","des-ede3-cfb",
"des-ofb", "des-ede-ofb","des-ede3-ofb",
"idea-cbc","idea-ecb", "idea-cfb", "idea-ofb",
"seed-cbc","seed-ecb", "seed-cfb", "seed-ofb",
"rc2-cbc", "rc2-ecb", "rc2-cfb","rc2-ofb", "rc2-64-cbc", "rc2-40-cbc",
"bf-cbc", "bf-ecb", "bf-cfb", "bf-ofb",
"cast5-cbc","cast5-ecb", "cast5-cfb","cast5-ofb",
"cast-cbc", "rc5-cbc", "rc5-ecb", "rc5-cfb", "rc5-ofb",
"sm4-cbc", "sm4-ecb", "sm4-cfb", "sm4-ofb", "sm4-ctr"
) {
my $str = " {FT_cipher, \"$cmd\", enc_main, enc_options, NULL},\n";
(my $algo = $cmd) =~ s/-.*//g;
if (grep { $algo eq $_ } @disablables) {
print "#ifndef OPENSSL_NO_" . uc($algo) . "\n${str}#endif\n";
} elsif (my $disabler = $cipher_disabler{$algo}) {
print "#ifndef OPENSSL_NO_" . uc($disabler) . "\n${str}#endif\n";
} else {
print $str;
}
}
print " {0, NULL, NULL, NULL, NULL}\n};\n";
}

200
electron/res/win/openssl/tsget.pl Executable file
View File

@@ -0,0 +1,200 @@
#!/usr/bin/env perl
# Copyright 2002-2018 The OpenSSL Project Authors. All Rights Reserved.
# Copyright (c) 2002 The OpenTSA Project. All rights reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use IO::Handle;
use Getopt::Std;
use File::Basename;
use WWW::Curl::Easy;
use vars qw(%options);
# Callback for reading the body.
sub read_body {
my ($maxlength, $state) = @_;
my $return_data = "";
my $data_len = length ${$state->{data}};
if ($state->{bytes} < $data_len) {
$data_len = $data_len - $state->{bytes};
$data_len = $maxlength if $data_len > $maxlength;
$return_data = substr ${$state->{data}}, $state->{bytes}, $data_len;
$state->{bytes} += $data_len;
}
return $return_data;
}
# Callback for writing the body into a variable.
sub write_body {
my ($data, $pointer) = @_;
${$pointer} .= $data;
return length($data);
}
# Initialise a new Curl object.
sub create_curl {
my $url = shift;
# Create Curl object.
my $curl = WWW::Curl::Easy::new();
# Error-handling related options.
$curl->setopt(CURLOPT_VERBOSE, 1) if $options{d};
$curl->setopt(CURLOPT_FAILONERROR, 1);
$curl->setopt(CURLOPT_USERAGENT,
"OpenTSA tsget.pl/openssl-3.3.1");
# Options for POST method.
$curl->setopt(CURLOPT_UPLOAD, 1);
$curl->setopt(CURLOPT_CUSTOMREQUEST, "POST");
$curl->setopt(CURLOPT_HTTPHEADER,
["Content-Type: application/timestamp-query",
"Accept: application/timestamp-reply,application/timestamp-response"]);
$curl->setopt(CURLOPT_READFUNCTION, \&read_body);
$curl->setopt(CURLOPT_HEADERFUNCTION, sub { return length($_[0]); });
# Options for getting the result.
$curl->setopt(CURLOPT_WRITEFUNCTION, \&write_body);
# SSL related options.
$curl->setopt(CURLOPT_SSLKEYTYPE, "PEM");
$curl->setopt(CURLOPT_SSL_VERIFYPEER, 1); # Verify server's certificate.
$curl->setopt(CURLOPT_SSL_VERIFYHOST, 2); # Check server's CN.
$curl->setopt(CURLOPT_SSLKEY, $options{k}) if defined($options{k});
$curl->setopt(CURLOPT_SSLKEYPASSWD, $options{p}) if defined($options{p});
$curl->setopt(CURLOPT_SSLCERT, $options{c}) if defined($options{c});
$curl->setopt(CURLOPT_CAINFO, $options{C}) if defined($options{C});
$curl->setopt(CURLOPT_CAPATH, $options{P}) if defined($options{P});
$curl->setopt(CURLOPT_RANDOM_FILE, $options{r}) if defined($options{r});
$curl->setopt(CURLOPT_EGDSOCKET, $options{g}) if defined($options{g});
# Setting destination.
$curl->setopt(CURLOPT_URL, $url);
return $curl;
}
# Send a request and returns the body back.
sub get_timestamp {
my $curl = shift;
my $body = shift;
my $ts_body;
local $::error_buf;
# Error-handling related options.
$curl->setopt(CURLOPT_ERRORBUFFER, "::error_buf");
# Options for POST method.
$curl->setopt(CURLOPT_INFILE, {data => $body, bytes => 0});
$curl->setopt(CURLOPT_INFILESIZE, length(${$body}));
# Options for getting the result.
$curl->setopt(CURLOPT_FILE, \$ts_body);
# Send the request...
my $error_code = $curl->perform();
my $error_string;
if ($error_code != 0) {
my $http_code = $curl->getinfo(CURLINFO_HTTP_CODE);
$error_string = "could not get timestamp";
$error_string .= ", http code: $http_code" unless $http_code == 0;
$error_string .= ", curl code: $error_code";
$error_string .= " ($::error_buf)" if defined($::error_buf);
} else {
my $ct = $curl->getinfo(CURLINFO_CONTENT_TYPE);
if (lc($ct) ne "application/timestamp-reply"
&& lc($ct) ne "application/timestamp-response") {
$error_string = "unexpected content type returned: $ct";
}
}
return ($ts_body, $error_string);
}
# Print usage information and exists.
sub usage {
print STDERR "usage: $0 -h <server_url> [-e <extension>] [-o <output>] ";
print STDERR "[-v] [-d] [-k <private_key.pem>] [-p <key_password>] ";
print STDERR "[-c <client_cert.pem>] [-C <CA_certs.pem>] [-P <CA_path>] ";
print STDERR "[-r <file:file...>] [-g <EGD_socket>] [<request>]...\n";
exit 1;
}
# ----------------------------------------------------------------------
# Main program
# ----------------------------------------------------------------------
# Getting command-line options (default comes from TSGET environment variable).
my $getopt_arg = "h:e:o:vdk:p:c:C:P:r:g:";
if (exists $ENV{TSGET}) {
my @old_argv = @ARGV;
@ARGV = split /\s+/, $ENV{TSGET};
getopts($getopt_arg, \%options) or usage;
@ARGV = @old_argv;
}
getopts($getopt_arg, \%options) or usage;
# Checking argument consistency.
if (!exists($options{h}) || (@ARGV == 0 && !exists($options{o}))
|| (@ARGV > 1 && exists($options{o}))) {
print STDERR "Inconsistent command line options.\n";
usage;
}
# Setting defaults.
@ARGV = ("-") unless @ARGV != 0;
$options{e} = ".tsr" unless defined($options{e});
# Processing requests.
my $curl = create_curl $options{h};
undef $/; # For reading whole files.
REQUEST: foreach (@ARGV) {
my $input = $_;
my ($base, $path) = fileparse($input, '\.[^.]*');
my $output_base = $base . $options{e};
my $output = defined($options{o}) ? $options{o} : $path . $output_base;
STDERR->printflush("$input: ") if $options{v};
# Read request.
my $body;
if ($input eq "-") {
# Read the request from STDIN;
$body = <STDIN>;
} else {
# Read the request from file.
open INPUT, "<" . $input
or warn("$input: could not open input file: $!\n"), next REQUEST;
$body = <INPUT>;
close INPUT
or warn("$input: could not close input file: $!\n"), next REQUEST;
}
# Send request.
STDERR->printflush("sending request") if $options{v};
my ($ts_body, $error) = get_timestamp $curl, \$body;
if (defined($error)) {
die "$input: fatal error: $error\n";
}
STDERR->printflush(", reply received") if $options{v};
# Write response.
if ($output eq "-") {
# Write to STDOUT.
print $ts_body;
} else {
# Write to file.
open OUTPUT, ">", $output
or warn("$output: could not open output file: $!\n"), next REQUEST;
print OUTPUT $ts_body;
close OUTPUT
or warn("$output: could not close output file: $!\n"), next REQUEST;
}
STDERR->printflush(", $output written.\n") if $options{v};
}
$curl->cleanup();

View File

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

Binary file not shown.

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<meta charset="UTF-8"/>
<link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
<title>爱享素材下载器</title>
</head>
<body>

View File

@@ -1,9 +1,13 @@
{
"name": "res-downloader",
"version": "1.0.5",
"version": "2.3.0",
"main": "dist-electron/main/index.js",
"description": "Electron + Vue + Vite 实现的资源下载软件,支持微信视频号下载、抖音视频下载、快手视频下载、酷狗音乐下载等",
"author": "putyy@qq.com",
"description": "res-downloader(爱享素材下载器),支持视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐、qq短视频等",
"homepage": "https://github.com/putyy/res-downloader",
"author": {
"name": "putyy",
"email": "putyy@qq.com"
},
"license": "MIT",
"private": true,
"keywords": [
@@ -19,8 +23,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": {
@@ -28,8 +32,8 @@
"@vitejs/plugin-vue": "^4.3.3",
"cross-spawn": "^7.0.3",
"crypto-js": "^4.1.1",
"electron": "^26.0.0",
"electron-builder": "^24.6.3",
"electron": "^22.3.27",
"electron-builder": "^23.6.0",
"electron-is-dev": "^2.0.0",
"electron-log": "^4.4.8",
"element-plus": "^2.3.12",
@@ -50,12 +54,10 @@
"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",
"tunnel-agent": "^0.6.0"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 35 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

@@ -1,8 +1,12 @@
<script setup lang="ts">
// @ts-ignore
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
</script>
<template>
<router-view></router-view>
<el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</template>
<style>

View File

@@ -1,5 +0,0 @@
import request from './request'
export function getPackageJson() {
return request.get("https://github.com/putyy/res-downloader/raw/main/package.json")
}

View File

@@ -1,71 +0,0 @@
import axios from 'axios'
import {ElMessage} from 'element-plus'
import {hexMD5} from "./md5"
import localStorageCache from "./localStorage"
import _ from "lodash"
class RequestService {
private axios: any;
private requestList: any;
constructor() {
let that = this
that.requestList = []
that.axios = axios.create({
timeout: 60000, // 请求超时时间毫秒
})
// 请求拦截
that.axios.interceptors.request.use(
function (config: any) {
if (config.url.slice(0, 8) !== "https://") {
config.url = import.meta.env.VITE_APP_API + "/" + config.url
}
return config
},
function (error: any) {
return Promise.reject(error)
}
)
// 响应拦截
that.axios.interceptors.response.use(
function (response: any) {
return response
},
function (error: any) {
// console.log(error)
return Promise.reject(error)
}
)
}
get(url: string, data?: any) {
return this.axios.get(url, {params: data}).catch((err:any)=>{
console.log('get-err', err)
})
}
post(url: string, data: any, isHandle?: any) {
isHandle = isHandle || true
if (isHandle){
data = Object.keys(data).map(item => {
let value = data[item];
if (_.isArray(value) || _.isObject(value)) {
value = JSON.stringify(value)
}
return encodeURIComponent(item) + '=' + encodeURIComponent(value)
}).join('&');
}
return this.axios.post(url, data).catch((err:any)=>{
console.log('post-err', err)
})
}
axiosObj() {
return this.axios
}
}
const request = new RequestService()
export default request

View File

@@ -2,52 +2,17 @@
import {ipcRenderer} from 'electron'
import pkg from '../../../package.json'
const v = pkg.version
const jump = (scene: number)=>{
switch (scene) {
case 1:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://s.gowas.cn/d/4089-quan-ping-tai-zi-yuan-xia-zai-ruan-jian"
})
break;
case 2:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://s.gowas.cn"
})
break;
case 3:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://i.gowas.cn"
})
break;
case 4:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://github.com/putyy/res-downloader/issues"
})
break;
case 5:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://haokawx.lot-ml.com/Product/Index/22550"
})
break;
case 6:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://github.com/putyy/res-downloader"
})
break;
}
const jump = (url: string)=>{
ipcRenderer.invoke('invoke_open_default_browser', {
url: url
})
}
</script>
<template lang="pug">
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)") 流量卡推荐
a.item(@click="jump(6)") 软件源码
a.item(@click="jump('https://s.gowas.cn/d/4089-quan-ping-tai-zi-yuan-xia-zai-ruan-jian')") 获取更新&问题反馈
a.item(@click="jump('https://github.com/putyy/res-downloader')") 软件源码
</template>
<style lang="less" scoped>

View File

@@ -12,9 +12,12 @@ el-container
Sidebar
el-container
el-main
router-view
el-footer
Footer
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>
<style lang="less" scoped>
@@ -24,4 +27,9 @@ el-container
.el-main {
text-align: center;
}
.el-footer{
margin: unset !important;
padding: unset !important;
height: auto !important;
}
</style>

View File

@@ -1,14 +1,12 @@
<script setup lang="ts">
import {inject, onMounted, ref, watch} from 'vue'
import localStorageCache from "../../common/localStorage";
const appName = "爱享素材"
const sidebarCollapse = ref(inject('sidebarCollapse'))
const defaultActive = ref("/index")
onMounted(() => {
let lastRoute = localStorageCache.get('last-route')
defaultActive.value = lastRoute ? lastRoute : "/index"
defaultActive.value = "/index"
})
</script>
<template lang="pug">

View File

@@ -1,6 +1,4 @@
import {createMemoryHistory, createRouter} from 'vue-router'
// @ts-ignore
import localStorageCache from "./common/localStorage"
const routes = [
{
@@ -13,16 +11,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

@@ -30,6 +30,7 @@ const str = "使用方法\n" +
" 2. 软件首页选择要获取的资源类型(默认选中的视频)\n" +
" 3. 打开要捕获的源, 如:视频号、网页、小程序等等\n" +
" 4. 返回软件首页即可看到要下载的资源\n" +
" 5. 直播流复制的链接如何使用可以使用obs或者ffmpeg命令\n" +
"常见问题\n" +
" 1. 无法拦截获取\n" +
" 手动检测系统代理是否设置正确 本软件代理地址: 127.0.0.1:8899\n" +
@@ -47,7 +48,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)") 在线下载
@@ -56,8 +57,7 @@ div.about
el-button(@click="jump(3)") 获取更新
div 4. 问题反馈 &nbsp;
el-button(@click="jump(4)") 点击前往
div.more
pre {{str}}
div.more {{str}}
</template>
@@ -74,7 +74,9 @@ div.about
white-space: pre-wrap;
}
.more{
width: 100%; /* 设置容器宽度 */
white-space: pre-wrap;
overflow-wrap: break-word; /* 允许在单词边界内换行 */
}
}
</style>
</style>

View File

@@ -1,47 +1,107 @@
<script setup lang="ts">
import {ref, onMounted, onUnmounted, watch} from "vue"
import {ref, onMounted, onUnmounted, watch, computed} from "vue"
import {ipcRenderer} from 'electron'
import {ElMessage, ElLoading, ElTable} from "element-plus"
import localStorageCache from "../common/localStorage"
import {Delete, Promotion} from "@element-plus/icons-vue";
import {Delete, Filter, Promotion} from "@element-plus/icons-vue"
interface resData {
url_sign: string,
url: string,
down_url: string,
high_url: string,
url_sign: string,
referer: string,
cover_url: 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 filtersAction = ref({
descInput: "",
descVisible: false,
descValue: "",
typeInput: [],
typeVisible: false,
typeValue: [],
})
const tableData = ref<resData[]>([])
const resType = ref({
video: true,
audio: true,
image: false,
m3u8: false
})
const filteredData = computed(() => {
if (filtersAction.value.descValue && filtersAction.value.typeValue.length === 0) {
return tableData.value.filter((item: resData) => {
return item.description.includes(filtersAction.value.descValue)
});
}
if (!filtersAction.value.descValue && filtersAction.value.typeValue.length > 0) {
return tableData.value.filter((item: resData) => {
// @ts-ignore
return filtersAction.value.typeValue.includes(item.type_str)
});
}
if (filtersAction.value.descValue && filtersAction.value.typeValue.length > 0) {
return tableData.value.filter((item: resData) => {
// @ts-ignore
return item.description.includes(filtersAction.value.descValue) && filtersAction.value.typeValue.includes(item.type_str)
});
}
return tableData.value
});
const isInitApp = ref(false)
const isSetProxy = ref(false)
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref<resData[]>([])
const loading = ref()
const resType = ref(["all"])
const typeOptions = ref([
{
value: "all",
label: "全部",
},
{
value: "image",
label: "图片",
}, {
value: "audio",
label: "音频"
}, {
value: "video",
label: "视频"
}, {
value: "m3u8",
label: "m3u8"
}, {
value: "live",
label: "直播流"
}, {
value: "xls",
label: "文档"
}, {
value: "doc",
label: "doc"
}, {
value: "pdf",
label: "pdf"
}
])
const typeFilters = ref(Array.from(typeOptions.value).slice(1))
const tableHeight = ref(400)
onMounted(() => {
let resTypeCache = localStorageCache.get("res-type")
let resTypeCache = localStorageCache.get("res-type-arr")
if (resTypeCache) {
resType.value = resTypeCache
resType.value = resTypeCache.split(",")
}
let tableDataCache = localStorageCache.get("res-table-data")
@@ -51,14 +111,14 @@ onMounted(() => {
ipcRenderer.on('on_get_queue', (res, data) => {
// @ts-ignore
if (resType.value.hasOwnProperty(data.type_str) && resType.value[data.type_str]) {
if (isSetProxy.value && resType.value.includes("all") || resType.value.includes(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}%`)
loading.value && loading.value.setText(`${data.schedule}%`)
})
ipcRenderer.invoke('invoke_app_is_init').then((isInit: boolean) => {
@@ -68,21 +128,23 @@ onMounted(() => {
}
})
loading.value = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
// loading.value = ElLoading.service({
// lock: true,
// text: 'Loading',
// background: 'rgba(0, 0, 0, 0.7)',
// })
ipcRenderer.invoke('invoke_start_proxy', {upstream_proxy: localStorageCache.get("upstream_proxy")}).then(() => {
loading.value.close()
}).catch((err) => {
ElMessage({
message: err,
type: 'warning',
})
loading.value.close()
})
// ipcRenderer.invoke('invoke_start_proxy', {upstream_proxy: localStorageCache.get("upstream_proxy")}).then(() => {
// loading.value.close()
// }).catch((err) => {
// ElMessage({
// message: err,
// type: 'warning',
// })
// loading.value.close()
// })
window.addEventListener("resize", handleResize);
handleResize()
})
onUnmounted(() => {
@@ -95,10 +157,15 @@ onUnmounted(() => {
})
})
watch(resType, (res, res1)=>{
localStorageCache.set("res-type", resType.value, -1)
watch(resType, (res, res1) => {
localStorageCache.set("res-type-arr", resType.value.join(","), -1)
}, {deep: true})
const handleResize = () => {
const height = document.documentElement.clientHeight || window.innerHeight;
tableHeight.value = height - 115
}
const handleSelectionChange = (val: resData[]) => {
multipleSelection.value = val
}
@@ -107,8 +174,9 @@ const handleBatchDown = async () => {
if (multipleSelection.value.length <= 0) {
return
}
const config = resdConfig()
let save_dir = localStorageCache.get("save_dir")
const save_dir = config?.save_dir
if (!save_dir) {
ElMessage({
@@ -123,24 +191,12 @@ const handleBatchDown = async () => {
text: '下载中',
background: 'rgba(0, 0, 0, 0.7)',
})
const quality = config?.quality ? config?.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) {
@@ -152,11 +208,9 @@ const handleBatchDown = async () => {
multipleTableRef.value!.clearSelection()
}
const handleDown = async (index: number, row: any, high: boolean) => {
let save_dir = localStorageCache.get("save_dir")
const handleDown = (index: number, row: any) => {
const config = resdConfig()
const save_dir = config?.save_dir
if (!save_dir) {
ElMessage({
message: '请设置保存目录',
@@ -171,34 +225,17 @@ const handleDown = async (index: number, row: any, high: boolean) => {
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.value.close()
localStorageCache.set("res-table-data", tableData.value, -1)
return
}
const quality = config?.quality ? config?.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
localStorageCache.set("res-table-data", tableData.value, -1)
}else{
} else {
ElMessage({
message: "下载失败",
type: 'warning',
@@ -233,7 +270,7 @@ const decodeWxFile = (index: number) => {
tableData.value[index].progress_bar = "100%"
tableData.value[index].save_path = res.fullFileName
localStorageCache.set("res-table-data", tableData.value, -1)
}else{
} else {
ElMessage({
message: "解密失败",
type: 'warning',
@@ -250,36 +287,42 @@ const decodeWxFile = (index: number) => {
}
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
})
@@ -295,59 +338,127 @@ const handleInitApp = () => {
})
}
const setProxy = ()=>{
isSetProxy.value = !isSetProxy.value
ipcRenderer.invoke('invoke_set_proxy', {proxy: isSetProxy.value}).then((res) => {
if (!res) {
ElMessage({
type: "warning",
message: "设置系统代理失败",
})
}
}).catch((err) => {
ElMessage({
type: "warning",
message: err,
})
})
}
const handleFilter = (type: string)=>{
if (type === "desc") {
filtersAction.value.descValue = filtersAction.value.descInput
filtersAction.value.descVisible = false
return
}
filtersAction.value.typeValue = filtersAction.value.typeInput
filtersAction.value.typeVisible = false
}
const resdConfig = ()=>{
const cache = localStorageCache.get("resd_config")
if (cache) {
return JSON.parse(cache)
}
return null
}
</script>
<template lang="pug">
el-container.container
el-header
el-row
div
el-button(type="primary" @click="handleBatchDown") 批量下载
el-button(v-if="isInitApp" @click="handleInitApp")
el-icon
Promotion
p 安装检测(如果看到此按钮说明软件安装未完成则需要手动点击此按钮)
el-button(@click="handleClear")
el-icon
Delete
p 清空列表
el-button(@click="resType.video=!resType.video" :type="resType.video ? 'primary' : 'info'" ) 视频
el-button(@click="resType.audio=!resType.audio" :type="resType.audio ? 'primary' : 'info'" ) 音频
el-button(@click="resType.image=!resType.image" :type="resType.image ? 'primary' : 'info'" ) 图片
el-button(@click="resType.m3u8=!resType.m3u8" :type="resType.m3u8 ? 'primary' : 'info'" ) m3u8
a(style="color: red") &nbsp;&nbsp;&nbsp;点击左边选项选择需要拦截的资源类型
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")
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" crossorigin="anonymous")
audio.audio(v-if="scope.row.type_str === 'audio'" controls preload="none")
source(:src="scope.row.down_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="主机地址")
el-table-column(prop="size" label="资源大小")
el-table-column(prop="save_path" label="保存目录")
el-table-column(prop="progress_bar" label="下载进度")
el-table-column(label="操作" width="135px" )
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="handlePreview(scope.$index, scope.row)") 窗口预览
el-button(link type="primary" @click="handleCopy(scope.row.down_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)") 打开文件目录
el-header(style="display:flex;align-items: center")
el-button(:type="isSetProxy ? 'primary' : 'info'" @click="setProxy") {{isSetProxy ? '关闭代理' : '启动代理'}}
el-button(type="primary" @click="handleBatchDown") 批量下载
el-button(v-if="isInitApp" @click="handleInitApp")
el-icon
Promotion
p 安装检测(如果看到此按钮说明软件安装未完成则需要手动点击此按钮)
el-button(@click="handleClear")
el-icon
Delete
p 清空列表
el-select(
v-model="resType"
multiple
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="3"
placeholder="资源拦截类型"
style="width: auto;min-width:130px"
)
el-option(v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value")
el-table(ref="multipleTableRef" @selection-change="handleSelectionChange" :data="filteredData" :height="tableHeight" max-height="100%" stripe)
el-table-column(type="selection")
el-table-column(label="预览" show-overflow-tooltip width="150px")
template(#header)
div(style="display:flex;align-items: center")
span(:style="filtersAction.descValue ? 'color: #409eff' : ''") 信息
el-popover(:visible="filtersAction.descVisible")
div
el-input(v-model="filtersAction.descInput" placeholder="请输入")
div(style="margin-top:10px;display:flex;justify-content: center;")
el-button(size="small" @click="filtersAction.descVisible = false") 关闭
el-button(size="small" type="primary" @click="handleFilter('desc')") 筛选
template(#reference)
el-icon(@click="filtersAction.descVisible = true" :color="filtersAction.typeValue.length > 0 ? '#409eff' : ''")
Filter
template(#default="scope")
div.show_res
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")
div(v-if="scope.row.type_str !== 'video' && scope.row.type_str !== 'image' && scope.row.type_str !== 'audio'") {{scope.row.type_str}}类型无法预览
div {{scope.row.description}}
el-table-column(prop="type_str" label="类型" show-overflow-tooltip)
template(#header)
div(style="display:flex;align-items: center")
span(:style="filtersAction.typeValue.length > 0 ? 'color: #409eff' : ''") 类型
el-popover(:visible="filtersAction.typeVisible")
div
el-checkbox-group(v-model="filtersAction.typeInput" style="display:flex;flex-direction: column;")
el-checkbox(v-for="item in typeFilters" :label="item.label" :value="item.value")
div(style="margin-top:10px;display:flex;justify-content: center;")
el-button(size="small" @click="filtersAction.typeVisible = false") 关闭
el-button(size="small" type="primary" @click="handleFilter('type')") 筛选
template(#reference)
el-icon(@click="filtersAction.typeVisible = true" :color="filtersAction.typeValue.length > 0 ? '#409eff' : ''")
Filter
el-table-column(prop="platform" label="主机地址")
el-table-column(prop="size" label="资源大小")
el-table-column(prop="save_path" label="保存目录" width="135px" :show-overflow-tooltip="true")
el-table-column(prop="progress_bar" label="下载进度")
el-table-column(label="操作" width="135px" )
template(#default="scope")
div.actions
template(v-if="scope.row.type_str !== 'm3u8' && scope.row.type_str !== 'live'" )
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)") 复制链接
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>
<style scoped lang="less">
.container {
padding: 0.5rem;
.el-table{
padding-top: 1rem;
}
.el-button {
margin: 0.1rem;
}
@@ -375,15 +486,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,46 +1,82 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {ipcRenderer} from "electron";
import localStorageCache from "../common/localStorage";
import {ElMessage} from "element-plus";
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 formData = ref({
save_dir: "",
quality: "-1",
proxy: "",
port: "8899",
})
const proxy_old = ref("")
const port_old = ref("")
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") : ""
upstream_proxy.value = localStorageCache.get("upstream_proxy") ? localStorageCache.get("upstream_proxy") : ""
upstream_proxy_old.value = upstream_proxy.value
const cache = localStorageCache.get("resd_config")
if (cache) {
formData.value = JSON.parse(cache)
}
proxy_old.value = formData.value.proxy
port_old.value = formData.value.port
})
const selectSaveDir = () => {
ipcRenderer.invoke('invoke_select_down_dir').then(save_path => {
if (save_path !== false) {
saveDir.value = save_path
ipcRenderer.invoke('invoke_select_down_dir').then(save_dir => {
console.log("save_dir", save_dir)
if (save_dir !== false) {
formData.value.save_dir = save_dir
}
})
}
const onSetting = () => {
localStorageCache.set("save_dir", saveDir.value, -1)
localStorageCache.set("upstream_proxy", upstream_proxy.value, -1)
if (upstream_proxy_old.value != upstream_proxy.value){
localStorageCache.set("resd_config", JSON.stringify(formData.value), -1)
ipcRenderer.invoke('invoke_set_config', Object.assign({}, formData.value))
if (proxy_old.value != formData.value.proxy || port_old.value != formData.value.port){
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-input(v-model="formData.port" placeholder="默认: 8899" )
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 修改此项需重启本软件" )
div(style="display:flex;flex-direction: row;align-items: center;")
el-input(v-model="formData.save_dir" placeholder="请选择" disabled )
el-button(style="margin-left: 10px;" type="primary" @click="selectSaveDir") 选择
el-form-item(label="视频号画质")
el-select(v-model="formData.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="formData.proxy" placeholder="例如: http://127.0.0.1:7890 修改此项需重启本软件,如不清楚用途请勿设置。" )
el-form-item
el-button(type="primary" @click="onSetting") 保存
</template>
</template>