18 Commits
2.1.0 ... 2.2.0

Author SHA1 Message Date
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
49 changed files with 1375 additions and 723 deletions

View File

@@ -1,10 +1,11 @@
## res-downloader(爱享素材下载器) 【[点击加入群聊](https://qm.qq.com/q/W8mVeZideE)】
## res-downloader
### 爱享素材下载器【[加入群聊](https://qm.qq.com/q/mfDMSpCxQ4)】
🎯 基于 [electron-vite-vue](https://github.com/electron-vite/electron-vite-vue.git)
📦 操作简单、可获取不同类型的资源
🖥️ 支持Win10、Win11、Mac
🌐 支持视频、音频、图片、m3u8等网络资源下载
📦 操作简单、可获取不同类型的资源
🖥️ 支持Win10、Win11、Mac、Linux
🌐 支持视频、音频、图片、m3u8、直播流等常见网络资源拦截
💪 支持微信视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐等网络资源下载
👼 支持设置代理以获取特殊网络下的资源
👼 支持设置代理以获取特殊网络下的资源
## 软件下载
🆕 [github下载](https://github.com/putyy/res-downloader/releases)
@@ -21,7 +22,13 @@
![](public/show.webp)
## 常见问题
下载慢、大视频下载失败
m3u8预览和下载
> [下载](https://m3u8-down.gowas.cn/) [预览](https://m3u8play.com/)
直播流 预览和录制:
> [使用obs进行预览和录制]( https://obsproject.com/)
下载慢、大视频下载失败(最新版本以内置aria2下载器)
> 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮
>> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载
@@ -39,7 +46,8 @@ Win7无法使用
>> MAC: /Users/你的用户名称/.res-downloader@putyy/res-downloader-installed.lock
>> Win: C:\Users\Admin\.res-downloader@putyy/res-downloader-installed.lock
其他问题请留言 https://github.com/putyy/res-downloader/issues
其他问题
[github](https://github.com/putyy/res-downloader/issues) 、 [爱享论坛](https://s.gowas.cn/d/4089)
## 二次开发
> ps 打包慢的问题可以参考 https://www.putyy.com/articles/87
@@ -57,7 +65,13 @@ yarn run build --universal --mac
# 打包win
yarn run build --win
# 打包linux
yarn run build --linux
```
## 实现&初衷
通过代理网络抓包拦截响应,筛选出有用的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的只不过这些软件需要手动进行筛选对于小白用户上手还是有点难度本软件对部分资源做了特殊处理更适合大众用户所以就有了本项目。
## 免责声明
本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关!

6
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']
@@ -15,12 +18,11 @@ declare module 'vue' {
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow']
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']

View File

@@ -10,8 +10,7 @@
},
"files": [
"dist-electron",
"dist",
"electron/res/**/*"
"dist"
],
"mac": {
"icon": "electron/res/icon/mac.icns",
@@ -29,13 +28,48 @@
],
"extraResources": [
{
"from": "electron/res/mac/aria2/aria2.conf",
"to": "electron/res/mac/aria2/aria2.conf",
"from": "electron/res/darwin/aria2/aria2.conf",
"to": "electron/res/darwin/aria2/aria2.conf",
"filter": ["**/*"]
},
{
"from": "electron/res/mac/aria2/${arch}/aria2c",
"to": "electron/res/mac/aria2/aria2c",
"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": ["**/*"],
}
]

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

@@ -7,6 +7,8 @@ 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
//
@@ -56,6 +58,16 @@ 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)
app.on('window-all-closed', () => {
@@ -100,8 +112,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 +158,8 @@ function createPreviewWindow(parent: BrowserWindow) {
parent: parent,
width: 600,
height: 400,
minWidth: 600,
minHeight: 400,
show: false,
// paintWhenInitiallyHidden: false,
webPreferences: {
@@ -157,19 +173,16 @@ function createPreviewWindow(parent: BrowserWindow) {
previewWin.setTitle("预览")
previewWin.on("page-title-updated", (event) => {
// 阻止该事件
event.preventDefault()
})
previewWin.on("close", (event) => {
// 不关闭窗口
event.preventDefault()
// 影藏窗口
previewWin.hide()
})
}
function createArua2Process() {
function createAria2Process() {
// 根据操作系统选择 aria2 的路径
try {
let aria2Path, aria2Conf
@@ -178,17 +191,15 @@ function createArua2Process() {
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2c.exe")
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, "./win/aria2/aria2.conf")
} else {
aria2Path = path.join(CONFIG.EXECUTABLE_PATH, "./mac/aria2" + (CONFIG.IS_DEV ? `/${process.arch}` : '/') + "/aria2c");
aria2Conf = path.join(CONFIG.EXECUTABLE_PATH, "./mac/aria2/aria2.conf")
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`)
}
// 启动 aria2
console.log("启动 aria2")
aria2Process = spawn(aria2Path, [`--conf-path=${aria2Conf}`, `--rpc-listen-port=${CONFIG.ARIA_PORT}`], {
windowsHide: false,
stdio: CONFIG.IS_DEV ? 'pipe' : 'ignore'
});
if(!aria2Process){
console.log("启动 aria2 失败")
console.log("start aria2 error")
}
if (CONFIG.IS_DEV) {
aria2Process.stdout.on('data', (data) => {
@@ -198,16 +209,35 @@ function createArua2Process() {
console.log(`aria2 error: ${data}`);
});
}
console.log("aria2 成功启动")
} 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)
createArua2Process()
setWin(mainWindow, previewWin)
initConfig()
initIPC()
startServer(mainWindow)
createAria2Process()
})

View File

@@ -1,15 +1,18 @@
import {ipcMain, dialog, BrowserWindow, app, shell} from 'electron'
import {startServer} from './proxyServer'
import {installCert, checkCertInstalled} from './cert'
import {decodeWxFile, suffix, getCurrentDateTimeFormatted} 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 urlTool from "url";
import {closeProxy, setProxy} from "./setProxy";
import path from 'path'
let win: BrowserWindow
let previewWin: BrowserWindow
let isStartProxy = false
const aria2RpcClient = new Aria2RPC()
export default function initIPC() {
@@ -20,23 +23,54 @@ export default function initIPC() {
ipcMain.handle('invoke_init_app', (event, arg) => {
// 开始 初始化应用 安装证书相关
installCert(false).then(r => {
})
installCert(false)
})
ipcMain.handle('invoke_start_proxy', (event, arg) => {
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 (isStartProxy) {
return
if (!global.isStartProxy) {
dialog.showMessageBoxSync({
type: "error",
message: "代理未启动",
});
return false
}
isStartProxy = true
return startServer({
win: win,
upstreamProxy: arg.upstream_proxy ? arg.upstream_proxy : "",
setProxyErrorCallback: err => {
console.log('setProxyErrorCallback', err)
},
})
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
}
// let upstream_proxy = ""
// if (arg.upstream_proxy && !arg.upstream_proxy.includes(':8899')) {
// upstream_proxy = arg.upstream_proxy
// }
//
// global.isStartProxy = true
// return startServer({
// win: win,
// upstreamProxy: upstream_proxy,
// setProxyErrorCallback: err => {
// console.log('setProxyErrorCallback', err)
// },
// })
})
ipcMain.handle('invoke_select_down_dir', async (event, arg) => {
@@ -71,7 +105,16 @@ export default function initIPC() {
resolve(false);
});
}
if (quality !== "-1" && data.decode_key && data.file_format) {
if (quality === "0" && data.decode_key) {
const urlInfo = urlTool.parse(down_url, true);
console.log('urlInfo', urlInfo)
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"]
console.log("down_url:", down_url)
}
} else if (quality !== "-1" && data.decode_key && data.file_format) {
const format = data.file_format.split('#');
const qualityMap = [
format[0],
@@ -81,7 +124,7 @@ export default function initIPC() {
down_url += "&X-snsvideoflag=" + qualityMap[quality];
}
let fileName = data?.description ? data.description.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') : hexMD5(down_url);
fileName = fileName + "_" + getCurrentDateTimeFormatted() + suffix(data.type)
fileName = fileName + "_" + getCurrentDateTimeFormatted() + typeSuffix(data.type)[1]
let save_path_file = `${save_path}/${fileName}`
if (process.platform === 'win32') {
save_path_file = `${save_path}\\${fileName}`
@@ -93,18 +136,18 @@ export default function initIPC() {
return new Promise((resolve, reject) => {
if (down_url.includes("douyin")) {
headers['Referer'] = down_url
if (data?.referer) {
headers['Referer'] = data?.referer
}
aria2RpcClient.addUri([down_url], save_path, fileName, headers).then((response) => {
let currentGid = response.result // 保存当前下载的 gid
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);
const progress = aria2RpcClient.calculateDownloadProgress(status.result.bitfield)
win?.webContents.send('on_down_file_schedule', {schedule: `已下载${progress}%`})
} else {
clearInterval(progressIntervalId);
@@ -113,26 +156,26 @@ export default function initIPC() {
decodeWxFile(save_path_file, data.decode_key, save_path_file.replace(".mp4", "_wx.mp4")).then((res) => {
fs.unlink(save_path_file, (err) => {
})
resolve(res);
resolve(res)
}).catch((error) => {
console.log("err:", error)
resolve(false);
});
})
} else {
resolve({
fullFileName: save_path_file,
});
})
}
}
}).catch((error) => {
console.error(error);
clearInterval(progressIntervalId);
resolve(false);
console.error(error)
clearInterval(progressIntervalId)
resolve(false)
});
}, 1000);
}, 1000)
}).catch((error) => {
console.log("err:", error)
resolve(false);
resolve(false)
});
});
});

View File

@@ -1,20 +1,14 @@
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
global.videoList = {}
if (process.platform === 'win32') {
process.env.OPENSSL_BIN = CONFIG.OPEN_SSL_BIN_PATH
process.env.OPENSSL_CONF = CONFIG.OPEN_SSL_CNF_PATH
@@ -23,6 +17,7 @@ if (process.platform === 'win32') {
const resObject = {
url: "",
url_sign: "",
referer: "",
cover_url: "",
file_format: "",
platform: "",
@@ -35,207 +30,144 @@ const resObject = {
description: ""
}
const vv = hexMD5(pkg.version) + (CONFIG.IS_DEV ? Math.random() :"")
const vv = hexMD5(pkg.version) + (CONFIG.IS_DEV ? Math.random() : "")
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),
},
export function startServer(win) {
try {
let upstreamProxy = ""
if (global.resdConfig.proxy && !global.resdConfig.proxy.includes(':' + global.resdConfig.port)) {
upstreamProxy = global.resdConfig?.proxy
}
console.log("global.resdConfig.port:", global.resdConfig.port)
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
})
.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())
})
.on('error', err => {
console.error("hoXy err:", err);
})
intercept(proxy, win)
} catch (e) {
console.error("--------------proxy catch err--------------");
}
}
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: '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
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,
url: media.url + media.urlToken,
cover_url: media.coverUrl,
file_format: media.spec.map((res)=> res.fileFormat).join('#'),
referer: referer ? referer : "",
platform: urlInfo.hostname,
size: toSize(media.fileSize),
type: "video/mp4",
type_str: 'video',
decode_key: media.decodeKey,
description: req.json.description,
size: toSize(contentLength ? contentLength : 0),
type: contentType,
type_str: resType,
}))
} catch (e) {
log.log(e.toString())
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'channels.weixin.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.includes('/web/pages/feed') || req.url.includes('/web/pages/home')) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"')
res.statusCode = 200
}
},
)
proxy.intercept(
{
phase: 'response',
hostname: 'res.wx.qq.com',
as: 'string',
},
async (req, res) => {
if (req.url.endsWith('.js?v=' + vv)) {
res.string = res.string.replaceAll('.js"', '.js?v=' + vv + '"');
}
if (req.url.includes("web/web-finder/res/js/virtual_svg-icons-register.publish")) {
res.string = res.string.replace(/get\s*media\s*\(\)\s*\{/, `
get media(){
if(this.objectDesc){
fetch("https://res-downloader.666666.com", {
method: "POST",
mode: "no-cors",
body: JSON.stringify(this.objectDesc),
});
};
`)
}
}
);
proxy.intercept(
{
phase: 'response',
},
async (req, res) => {
try {
// 拦截响应
const ctype = res?._data?.headers?.['content-type']
const url_sign: string = hexMD5(req.fullUrl())
const res_url = req.fullUrl()
const urlInfo = urlTool.parse(res_url, true)
switch (ctype) {
case "video/mp4":
case "video/webm":
case "video/ogg":
case "video/x-msvideo":
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/x-flv":
case "video/3gpp":
case "video/x-matroska":
if (global.videoList.hasOwnProperty(url_sign) === false) {
global.videoList[url_sign] = res_url
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: toSize(res?._data?.headers?.['content-length'] ?? 0),
type: ctype,
type_str: 'video',
}))
}
break;
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/gif":
case "image/avif":
case "image/bmp":
case "image/tiff":
case "image/x-icon":
case "image/heic":
case "image/vnd.adobe.photoshop":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'image',
}))
break
case "audio/mpeg":
case "audio/wav":
case "audio/aiff":
case "audio/x-aiff":
case "audio/aac":
case "audio/ogg":
case "audio/flac":
case "audio/midi":
case "audio/x-midi":
case "audio/x-ms-wma":
case "audio/opus":
case "audio/webm":
case "audio/mp4":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'audio',
}))
break
case "application/vnd.apple.mpegurl":
case "application/x-mpegURL":
win.webContents.send('on_get_queue', Object.assign({}, resObject, {
url: res_url,
url_sign: url_sign,
platform: urlInfo.hostname,
size: res?._data?.headers?.['content-length'] ? toSize(res?._data?.headers?.['content-length']) : 0,
type: ctype,
type_str: 'm3u8',
}))
break
}
} catch (e) {
log.log(e.toString())
}
},
)
} catch (e) {
log.log("--------------proxy catch err--------------", e)
}
})
} 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': {

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({
@@ -32,7 +33,7 @@ function downloadFile(url, decodeKey, fullFileName, progressCallback) {
},
}
if (url.includes("douyin")){
if (url.includes("douyin")) {
config.headers['Referer'] = url
}
@@ -57,7 +58,7 @@ function downloadFile(url, decodeKey, fullFileName, progressCallback) {
});
}),
);
}else{
} else {
data.pipe(
fs.createWriteStream(fullFileName).on('finish', () => {
resolve({
@@ -97,8 +98,8 @@ 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":
case "video/webm":
case "video/ogg":
@@ -106,23 +107,25 @@ function suffix(type: string) {
case "video/mpeg":
case "video/quicktime":
case "video/x-ms-wmv":
case "video/x-flv":
case "video/3gpp":
case "video/x-matroska":
return ".mp4";
return ["video", ".mp4"];
case "audio/video":
case "video/x-flv":
return ["live", ".mp4"];
case "image/png":
case "image/webp":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/gif":
case "image/avif":
case "image/bmp":
case "image/tiff":
case "image/x-icon":
case "image/heic":
case "image/x-icon":
case "image/svg+xml":
case "image/vnd.adobe.photoshop":
return ".png";
return ["image", ".png"];
case "audio/mpeg":
case "audio/wav":
case "audio/aiff":
@@ -136,12 +139,25 @@ function suffix(type: string) {
case "audio/opus":
case "audio/webm":
case "audio/mp4":
return ".mp3";
case "audio/mp3":
case "audio/mp4;charset=UTF-8":
return ["audio", ".mp3"];
case "application/vnd.apple.mpegurl":
case "application/x-mpegURL":
return ".m3u8";
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 ["", ""]
}
function getCurrentDateTimeFormatted() {
@@ -157,4 +173,4 @@ function getCurrentDateTimeFormatted() {
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
export {downloadFile, toSize, decodeWxFile, suffix, getCurrentDateTimeFormatted}
export {downloadFile, toSize, decodeWxFile, typeSuffix, getCurrentDateTimeFormatted}

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.

0
electron/res/win/aria2/aria2c.exe Normal file → Executable file
View File

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;

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

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.

0
electron/res/win/openssl/openssl.cnf Normal file → Executable file
View File

BIN
electron/res/win/openssl/openssl.exe Normal file → Executable file

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

@@ -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": "2.1.0",
"version": "2.2.0",
"main": "dist-electron/main/index.js",
"description": "res-downloader(爱享素材下载器)支持视频号、小程序、抖音、快手、小红书、酷狗音乐、qq音乐、qq短视频等",
"author": "putyy@qq.com",
"homepage": "https://github.com/putyy/res-downloader",
"author": {
"name": "putyy",
"email": "putyy@qq.com"
},
"license": "MIT",
"private": true,
"keywords": [
@@ -54,8 +58,6 @@
},
"dependencies": {
"axios": "^1.5.0",
"electron-store": "^8.1.0",
"getmac": "^5.20.0",
"hoxy": "^3.3.1",
"tunnel-agent": "^0.6.0"
}

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,53 +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://s.gowas.cn/d/4089-quan-ping-tai-zi-yuan-xia-zai-ruan-jian"
})
break;
case 5:
ipcRenderer.invoke('invoke_open_default_browser', {
url: "https://www.ais.do/ivi/rr2GaZ"
})
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(4)") 问题反馈
div.line
a.item 推荐:
a.item(@click="jump(5)") Ai助手(免费)
a.item(@click="jump(2)") 网盘资源
a.item(@click="jump(3)") 图片无损压缩
a.item(@click="jump(6)") 软件源码
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

@@ -16,8 +16,8 @@ el-container
keep-alive(v-if="route.meta.keepAlive")
component(:is="Component")
component(v-else :is="Component")
el-footer
Footer
el-footer
Footer
</template>
<style lang="less" scoped>
@@ -27,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 = [
{

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

View File

@@ -1,13 +1,15 @@
<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: string,
url_sign: string,
referer: string,
cover_url: string,
size: any,
platform: string,
type: string,
@@ -18,26 +20,88 @@ interface resData {
description: string,
}
const tableData = ref<resData[]>([])
const resType = ref({
video: true,
audio: true,
image: false,
m3u8: false
const filtersAction = ref({
descInput: "",
descVisible: false,
descValue: "",
typeInput: [],
typeVisible: false,
typeValue: [],
})
const tableData = ref<resData[]>([])
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")
@@ -47,7 +111,7 @@ 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)
}
@@ -64,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(() => {
@@ -92,9 +158,14 @@ onUnmounted(() => {
})
watch(resType, (res, res1) => {
localStorageCache.set("res-type", resType.value, -1)
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
}
@@ -103,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({
@@ -119,7 +191,7 @@ const handleBatchDown = async () => {
text: '下载中',
background: 'rgba(0, 0, 0, 0.7)',
})
const quality = localStorageCache.get("quality") ? localStorageCache.get("quality") : -1
const quality = config?.quality ? config?.quality : -1
for (const item of multipleSelection.value) {
let downRes = await ipcRenderer.invoke('invoke_down_file', {
data: Object.assign({}, item),
@@ -136,9 +208,9 @@ const handleBatchDown = async () => {
multipleTableRef.value!.clearSelection()
}
const handleDown = async (index: number, row: any) => {
const 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: '请设置保存目录',
@@ -153,7 +225,7 @@ const handleDown = async (index: number, row: any) => {
background: 'rgba(0, 0, 0, 0.7)',
})
const quality = localStorageCache.get("quality") ? localStorageCache.get("quality") : -1
const quality = config?.quality ? config?.quality : -1
ipcRenderer.invoke('invoke_down_file', {
data: Object.assign({}, tableData.value[index]),
save_path: save_dir,
@@ -266,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="150px")
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 {{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(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)") 打开文件目录
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;
}

View File

@@ -4,10 +4,15 @@ import {ipcRenderer} from "electron"
import localStorageCache from "../common/localStorage"
import {ElMessage} from "element-plus"
const saveDir = ref("")
const upstream_proxy = ref("")
const upstream_proxy_old = ref("")
const quality = ref("-1")
const formData = ref({
save_dir: "",
quality: "-1",
proxy: "",
port: "8899",
})
const proxy_old = ref("")
const port_old = ref("")
const qualityOptions = ref([
{
value: '-1',
@@ -25,27 +30,30 @@ const qualityOptions = ref([
])
onMounted(() => {
saveDir.value = localStorageCache.get("save_dir") ? localStorageCache.get("save_dir") : ""
quality.value = localStorageCache.get("quality") ? localStorageCache.get("quality") : "-1"
upstream_proxy.value = localStorageCache.get("upstream_proxy") ? localStorageCache.get("upstream_proxy") : ""
upstream_proxy_old.value = upstream_proxy.value
const 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)
localStorageCache.set("quality", quality.value, -1)
if (upstream_proxy_old.value != upstream_proxy.value){
localStorageCache.set("resd_config", JSON.stringify(formData.value))
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: "保存成功",
type: 'success',
@@ -55,16 +63,20 @@ const onSetting = () => {
</script>
<template lang="pug">
el-form(style="max-width: 600px")
el-form-item(label="代理端口")
el-input(v-model="formData.port" placeholder="默认: 8899" )
el-form-item(label="保存位置")
el-link(@click="selectSaveDir") {{saveDir ? saveDir : '选择'}}
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="quality" placeholder="请选择")
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="upstream_proxy" placeholder="例如: http://127.0.0.1:7890 修改此项需重启本软件,如不清楚用途请勿设置。" )
el-input(v-model="formData.proxy" placeholder="例如: http://127.0.0.1:7890 修改此项需重启本软件,如不清楚用途请勿设置。" )
el-form-item
el-button(type="primary" @click="onSetting") 保存
</template>