From 5e63624955fbfa990e6cd2adbab0de9cfa645db3 Mon Sep 17 00:00:00 2001 From: putyy Date: Tue, 15 Oct 2024 11:19:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=B5=84=E6=BA=90=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E3=80=81=E4=BC=98=E5=8C=96=E8=B5=84=E6=BA=90=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E9=80=89=E6=8B=A9=E3=80=81=E4=BC=98=E5=8C=96table?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E8=A1=A8=E5=A4=B4=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 ++- electron/main/index.ts | 2 + electron/main/ipc.ts | 16 +++- electron/main/proxyServer.ts | 91 +++--------------- electron/main/utils.ts | 40 +++++--- src/components/layout/Footer.vue | 48 ++-------- src/components/layout/Index.vue | 9 +- src/views/About.vue | 8 +- src/views/Index.vue | 152 ++++++++++++++++++++----------- 9 files changed, 181 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 01fc6eb..ed340c1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -## res-downloader(爱享素材下载器) 【[加入群聊](https://qm.qq.com/q/mfDMSpCxQ4)】 +## 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音乐等网络资源下载 👼 支持设置代理以获取特殊网络下的资源 @@ -21,7 +22,7 @@ ![](public/show.webp) ## 常见问题 -下载慢、大视频下载失败 +下载慢、大视频下载失败(最新版本以内置aria2下载器) > 推荐使用如下工具加速下载,视频号可以下载完成后再到对应视频操作项选择 “视频解密(视频号)” 按钮 >> [Neat Download Manager](https://www.neatdownloadmanager.com/index.php/en/)、[Motrix](https://motrix.app/download)等软件进行下载 @@ -39,7 +40,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 @@ -59,5 +61,8 @@ yarn run build --universal --mac yarn run build --win ``` +## 实现&初衷 +通过代理网络抓包拦截响应,筛选出有用的资源, 同fiddler、charles等抓包软件、浏览器F12打开控制也能达到目的,只不过这些软件需要手动进行筛选,对于小白用户上手还是有点难度,本软件对部分资源做了特殊处理,更适合大众用户,所以就有了本项目。 + ## 免责声明 本软件用于学习研究使用,若因使用本软件造成的一切法律责任均与本人无关! diff --git a/electron/main/index.ts b/electron/main/index.ts index 98a0525..dcb1580 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -144,6 +144,8 @@ function createPreviewWindow(parent: BrowserWindow) { parent: parent, width: 600, height: 400, + minWidth: 600, + minHeight: 400, show: false, // paintWhenInitiallyHidden: false, webPreferences: { diff --git a/electron/main/ipc.ts b/electron/main/ipc.ts index d1a1174..4b988e1 100755 --- a/electron/main/ipc.ts +++ b/electron/main/ipc.ts @@ -1,11 +1,12 @@ 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"; let win: BrowserWindow let previewWin: BrowserWindow @@ -71,7 +72,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 +91,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}` diff --git a/electron/main/proxyServer.ts b/electron/main/proxyServer.ts index fc40ffd..b214313 100755 --- a/electron/main/proxyServer.ts +++ b/electron/main/proxyServer.ts @@ -3,7 +3,7 @@ import log from 'electron-log' import CONFIG from './const' import {setProxy} from './setProxy' 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' @@ -146,87 +146,24 @@ export async function startServer({win, upstreamProxy, setProxyErrorCallback = f 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": + 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 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', + size: toSize(contentLength ? contentLength : 0), + type: contentType, + type_str: resType, })) - 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()) diff --git a/electron/main/utils.ts b/electron/main/utils.ts index 0452d33..280c994 100755 --- a/electron/main/utils.ts +++ b/electron/main/utils.ts @@ -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,7 +98,7 @@ function toSize(size: number) { return size + 'b' } -function suffix(type: string) { +function typeSuffix(type: string) { switch (type) { case "video/mp4": case "video/webm": @@ -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,23 @@ function suffix(type: string) { case "audio/opus": case "audio/webm": case "audio/mp4": - return ".mp3"; + return ["audio", ".mp3"]; case "application/vnd.apple.mpegurl": case "application/x-mpegURL": - return ".m3u8"; + 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 +171,4 @@ function getCurrentDateTimeFormatted() { return `${year}${month}${day}${hours}${minutes}${seconds}`; } -export {downloadFile, toSize, decodeWxFile, suffix, getCurrentDateTimeFormatted} +export {downloadFile, toSize, decodeWxFile, typeSuffix, getCurrentDateTimeFormatted} diff --git a/src/components/layout/Footer.vue b/src/components/layout/Footer.vue index 1b0cd8f..8dc95d1 100755 --- a/src/components/layout/Footer.vue +++ b/src/components/layout/Footer.vue @@ -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 + }) } diff --git a/src/views/About.vue b/src/views/About.vue index 2cd1254..bb1fef0 100644 --- a/src/views/About.vue +++ b/src/views/About.vue @@ -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. 问题反馈   el-button(@click="jump(4)") 点击前往 - div.more - pre {{str}} + div.more {{str}} @@ -74,7 +74,9 @@ div.about white-space: pre-wrap; } .more{ - + width: 100%; /* 设置容器宽度 */ + white-space: pre-wrap; + overflow-wrap: break-word; /* 允许在单词边界内换行 */ } } diff --git a/src/views/Index.vue b/src/views/Index.vue index f5672ed..7d9d8c6 100755 --- a/src/views/Index.vue +++ b/src/views/Index.vue @@ -20,24 +20,51 @@ interface resData { const tableData = ref([]) -const resType = ref({ - video: true, - audio: true, - image: false, - m3u8: false -}) - const isInitApp = ref(false) const multipleTableRef = ref>() const multipleSelection = ref([]) 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 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 +74,7 @@ onMounted(() => { ipcRenderer.on('on_get_queue', (res, data) => { // @ts-ignore - if (resType.value.hasOwnProperty(data.type_str) && resType.value[data.type_str]) { + if (resType.value.includes("all") || resType.value.includes(data.type_str)) { tableData.value.push(data) localStorageCache.set("res-table-data", tableData.value, -1) } @@ -79,6 +106,8 @@ onMounted(() => { }) loading.value.close() }) + window.addEventListener("resize", handleResize); + handleResize() }) onUnmounted(() => { @@ -92,9 +121,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 } @@ -270,55 +304,63 @@ const handleInitApp = () => {